XML Transformation Framework Guide - .NET Implementation¶
Overview¶
This guide documents a comprehensive framework for mapping and transforming XML documents in .NET. The framework provides three distinct approaches to handle different scenarios, from simple field mappings to complex type-safe transformations.
Table of Contents¶
- Framework Architecture
- Option A: Configuration-Based (XPath)
- Option B: Strongly-Typed Object Mapping
- Option C: Hybrid Approach
- Comparison Matrix
- Implementation Considerations
- Usage Guidelines
Framework Architecture¶
Core Concepts¶
The framework is built around several key abstractions:
- Mapping Profiles: Define the transformation rules
- Transformation Engines: Execute the mappings
- Namespace Management: Handle XML namespaces consistently
- Value Transformers: Apply custom logic during mapping
Design Principles¶
- Separation of Concerns: Mapping configuration is separate from transformation logic
- Extensibility: Easy to add custom transformers and validators
- Type Safety: Where possible, leverage compile-time type checking
- Performance: Consider streaming for large documents
- Error Handling: Clear error messages with context
Option A: Configuration-Based (XPath)¶
When to Use¶
- Working with dynamic or frequently changing XML schemas
- Configuration-driven transformation requirements
- Need to support multiple source/target formats without code changes
- XPath expressions are sufficient for your mapping needs
Key Components¶
1. XPathMapping Class¶
public class XPathMapping
{
public string SourceXPath { get; set; }
public string TargetXPath { get; set; }
public Func<string, string> Transformer { get; set; }
public bool IsAttribute { get; set; }
}
Represents a single mapping rule from source to target with optional transformation.
2. XmlMappingProfile Class¶
public class XmlMappingProfile
{
public void CreateMap(string sourceXPath, string targetXPath,
Func<string, string> transformer = null, bool isAttribute = false)
}
Container for all mapping rules. Use the fluent API to define mappings.
3. XPathTransformationEngine Class¶
public class XPathTransformationEngine
{
public XPathTransformationEngine(XmlMappingProfile profile,
Dictionary<string, string> namespaces = null)
public XDocument Transform(XDocument source)
}
Executes the transformation using XPath queries and namespace management.
Usage Example¶
// Define mapping profile
var profile = new XmlMappingProfile();
profile.CreateMap("//Person/Name", "/root/Employee/FullName");
profile.CreateMap("//Person/Age", "/root/Employee/YearsOld",
age => (int.Parse(age) + 1).ToString()); // Transform age
profile.CreateMap("//Person/Id", "/root/Employee/@employeeId",
isAttribute: true);
// Setup namespaces if needed
var namespaces = new Dictionary<string, string>
{
{ "ns", "http://example.com/schema" }
};
// Create engine and transform
var engine = new XPathTransformationEngine(profile, namespaces);
var result = engine.Transform(sourceXml);
Advantages¶
- No need for predefined classes
- Easy to modify mappings without recompilation
- Can be driven by external configuration (JSON, XML, database)
- Works well with unknown or dynamic schemas
Limitations¶
- No compile-time safety
- XPath can become complex for nested structures
- Performance overhead for very large documents
- Limited support for complex object relationships
Option B: Strongly-Typed Object Mapping¶
When to Use¶
- Working with well-defined, stable XML schemas
- XSD schemas are available and classes can be generated
- Need type safety and IntelliSense support
- Complex business logic during transformation
- Integration with existing domain models
Key Components¶
1. IObjectMapper Interface¶
public interface IObjectMapper<TSource, TTarget>
{
TTarget Map(TSource source);
}
Defines the contract for mapping between two types.
2. ObjectMappingEngine Class¶
public class ObjectMappingEngine<TSource, TTarget>
{
public ObjectMappingEngine(IObjectMapper<TSource, TTarget> mapper)
public XDocument Transform(XDocument sourceXml)
public string TransformToString(string sourceXmlString)
}
Handles XML serialization/deserialization and delegates mapping to the mapper.
3. Source and Target Classes¶
Define your classes with XML attributes:
public class SourcePerson
{
[XmlElement("FirstName")]
public string FirstName { get; set; }
[XmlElement("LastName")]
public string LastName { get; set; }
[XmlElement("BirthDate")]
public DateTime BirthDate { get; set; }
}
public class TargetEmployee
{
[XmlAttribute("id")]
public string Id { get; set; }
[XmlElement("FullName")]
public string FullName { get; set; }
[XmlElement("Age")]
public int Age { get; set; }
}
Usage Example¶
// Implement mapper
public class PersonToEmployeeMapper : IObjectMapper<SourcePerson, TargetEmployee>
{
public TargetEmployee Map(SourcePerson source)
{
return new TargetEmployee
{
Id = Guid.NewGuid().ToString(),
FullName = $"{source.FirstName} {source.LastName}",
Age = CalculateAge(source.BirthDate),
ContactEmail = source.Email
};
}
private int CalculateAge(DateTime birthDate)
{
var today = DateTime.Today;
var age = today.Year - birthDate.Year;
if (birthDate.Date > today.AddYears(-age)) age--;
return age;
}
}
// Use the engine
var mapper = new PersonToEmployeeMapper();
var engine = new ObjectMappingEngine<SourcePerson, TargetEmployee>(mapper);
var result = engine.TransformToString(sourceXmlString);
Advantages¶
- Full type safety and compile-time checking
- IntelliSense support for easier development
- Clear, readable mapping code
- Easy to unit test
- Can leverage existing .NET serialization attributes
- Good integration with AutoMapper or similar libraries
Limitations¶
- Requires class definitions upfront
- Less flexible for schema changes
- More code to maintain
- Not suitable for dynamic schemas
Option C: Hybrid Approach¶
When to Use¶
- Complex documents with different sections requiring different strategies
- Some parts have known schemas, others are dynamic
- Need maximum flexibility
- Performance-critical sections benefit from different approaches
- Gradual migration from XPath to type-safe mapping
Key Components¶
1. MappingStrategy Enum¶
public enum MappingStrategy
{
XPath,
ObjectBased,
Custom
}
Defines which strategy to use for a particular mapping rule.
2. HybridMappingRule Class¶
public class HybridMappingRule
{
public string Name { get; set; }
public MappingStrategy Strategy { get; set; }
public string SourceXPath { get; set; }
public string TargetXPath { get; set; }
public Func<XElement, object> CustomExtractor { get; set; }
public Func<object, XElement> CustomBuilder { get; set; }
public Type SourceType { get; set; }
public Type TargetType { get; set; }
public Delegate ObjectMapper { get; set; }
}
Flexible rule that can represent any of the three strategies.
3. HybridMappingProfile Class¶
public class HybridMappingProfile
{
public void AddXPathMapping(string name, string sourceXPath, string targetXPath)
public void AddObjectMapping<TSource, TTarget>(string name,
string sourceXPath, Func<TSource, TTarget> mapper)
public void AddCustomMapping(string name,
Func<XElement, object> extractor,
Func<object, XElement> builder)
}
Allows mixing different mapping strategies in one profile.
4. HybridTransformationEngine Class¶
public class HybridTransformationEngine
{
public HybridTransformationEngine(HybridMappingProfile profile)
public XDocument Transform(XDocument source)
}
Intelligently applies the appropriate strategy for each rule.
Usage Example¶
var profile = new HybridMappingProfile();
// Simple field mapping with XPath
profile.AddXPathMapping("basic-name", "//Person/Name", "/root/Name");
// Type-safe object mapping for complex sections
profile.AddObjectMapping<SourcePerson, TargetEmployee>(
"person-to-employee",
"//SourcePerson",
person => new PersonToEmployeeMapper().Map(person));
// Custom logic for calculated fields
profile.AddCustomMapping(
"item-count",
source => source.Descendants("Items").Count(),
count => new XElement("ItemCount", count));
// Custom aggregation logic
profile.AddCustomMapping(
"total-amount",
source => source.Descendants("LineItem")
.Sum(x => decimal.Parse(x.Element("Amount").Value)),
total => new XElement("TotalAmount", total));
var engine = new HybridTransformationEngine(profile);
var result = engine.Transform(sourceXml);
Advantages¶
- Maximum flexibility - use the right tool for each job
- Gradual migration path from XPath to type-safe
- Can optimize different sections differently
- Mix simple and complex transformations
- Extensible with custom strategies
Limitations¶
- More complex to understand and maintain
- Slight performance overhead from strategy dispatching
- Requires more careful design decisions
- Can become cluttered if not well organized
Comparison Matrix¶
| Aspect | Option A (XPath) | Option B (Strongly-Typed) | Option C (Hybrid) |
|---|---|---|---|
| Type Safety | None | Full | Partial |
| Flexibility | High | Low | Very High |
| Performance | Medium | High | Medium |
| Complexity | Low | Medium | High |
| Maintenance | Easy | Medium | Complex |
| Schema Changes | Easy | Difficult | Easy |
| Learning Curve | Low | Low | Medium |
| IntelliSense | No | Yes | Partial |
| Testing | Medium | Easy | Medium |
| Best For | Dynamic schemas | Known schemas | Mixed requirements |
Implementation Considerations¶
Namespace Handling¶
Always provide proper namespace management:
var namespaces = new Dictionary<string, string>
{
{ "soap", "http://schemas.xmlsoap.org/soap/envelope/" },
{ "ns1", "http://example.com/schema1" },
{ "ns2", "http://example.com/schema2" }
};
Use namespace prefixes consistently in XPath expressions:
profile.CreateMap("//ns1:Person/ns1:Name", "/root/Employee/FullName");
Performance Optimization¶
For large documents, consider:
- Streaming: Use
XmlReaderandXmlWriterinstead of loading entire document - Lazy evaluation: Process nodes on-demand
- Caching: Cache compiled XPath expressions
- Parallel processing: For batch transformations
Example streaming approach:
using (var reader = XmlReader.Create(sourceStream))
using (var writer = XmlWriter.Create(targetStream))
{
// Process nodes as they're read
}
Error Handling¶
Implement comprehensive error handling:
try
{
var result = engine.Transform(source);
}
catch (XPathException ex)
{
// Handle XPath-specific errors
Console.WriteLine($"XPath error: {ex.Message}");
}
catch (InvalidOperationException ex)
{
// Handle serialization errors
Console.WriteLine($"Serialization error: {ex.Message}");
}
catch (Exception ex)
{
// Handle general errors
Console.WriteLine($"Transformation error: {ex.Message}");
}
Validation¶
Add XSD validation before and after transformation:
public static bool ValidateXml(XDocument doc, string schemaPath)
{
var schemas = new XmlSchemaSet();
schemas.Add("", schemaPath);
bool isValid = true;
doc.Validate(schemas, (sender, e) =>
{
isValid = false;
Console.WriteLine($"Validation error: {e.Message}");
});
return isValid;
}
Logging and Diagnostics¶
Implement logging for troubleshooting:
public class LoggingTransformationEngine
{
private readonly ILogger _logger;
public XDocument Transform(XDocument source)
{
_logger.LogInformation("Starting transformation");
_logger.LogDebug($"Source XML: {source}");
var result = PerformTransformation(source);
_logger.LogInformation("Transformation completed");
_logger.LogDebug($"Target XML: {result}");
return result;
}
}
Usage Guidelines¶
Choosing the Right Approach¶
Use Option A (XPath) when: - Schemas change frequently - Working with legacy systems - Need configuration-driven transformations - Simple field-to-field mappings - External stakeholders define mappings
Use Option B (Strongly-Typed) when: - XSD schemas are available - Need type safety and IntelliSense - Complex business logic in transformations - Integration with existing domain models - Team prefers object-oriented approach
Use Option C (Hybrid) when: - Document has sections with different characteristics - Migrating from XPath to type-safe approach - Need to optimize different sections differently - Complex requirements with varied transformation logic - Maximum flexibility is required
Best Practices¶
- Start Simple: Begin with the simplest approach that meets your needs
- Test Thoroughly: Unit test each mapping rule
- Document Mappings: Keep clear documentation of transformation logic
- Version Control: Track changes to mapping configurations
- Performance Test: Benchmark with realistic data volumes
- Handle Nulls: Always check for null/missing elements
- Use Namespaces: Properly handle XML namespaces
- Validate: Add validation before and after transformation
- Error Context: Provide meaningful error messages with XPath context
- Reusability: Create reusable transformers for common patterns
Common Patterns¶
Pattern 1: Conditional Mapping¶
profile.CreateMap("//Person/Status", "/root/Employee/IsActive",
status => status.ToLower() == "active" ? "true" : "false");
Pattern 2: Aggregation¶
profile.AddCustomMapping(
"order-total",
source => source.Descendants("LineItem")
.Sum(x => decimal.Parse(x.Element("Price").Value)),
total => new XElement("OrderTotal", total));
Pattern 3: Lookup/Reference Data¶
var statusMap = new Dictionary<string, string>
{
{ "A", "Active" },
{ "I", "Inactive" },
{ "P", "Pending" }
};
profile.CreateMap("//Person/StatusCode", "/root/Employee/Status",
code => statusMap.ContainsKey(code) ? statusMap[code] : "Unknown");
Pattern 4: Complex Object Construction¶
profile.AddObjectMapping<SourceOrder, TargetOrder>(
"order-mapping",
"//Order",
order => new TargetOrder
{
OrderId = order.Id,
Customer = new Customer
{
Name = order.CustomerName,
Email = order.CustomerEmail
},
Items = order.LineItems.Select(li => new OrderItem
{
ProductId = li.ProductCode,
Quantity = li.Qty,
Price = li.UnitPrice
}).ToList()
});
Testing Strategy¶
[TestClass]
public class TransformationTests
{
[TestMethod]
public void Transform_SimpleMapping_Success()
{
// Arrange
var profile = new XmlMappingProfile();
profile.CreateMap("//Person/Name", "/root/Employee/FullName");
var engine = new XPathTransformationEngine(profile);
var source = XDocument.Parse("<People><Person><Name>John</Name></Person></People>");
// Act
var result = engine.Transform(source);
// Assert
Assert.AreEqual("John", result.XPathSelectElement("/root/Employee/FullName").Value);
}
[TestMethod]
public void Transform_WithTransformer_AppliesTransformation()
{
// Test transformation logic
}
[TestMethod]
[ExpectedException(typeof(XPathException))]
public void Transform_InvalidXPath_ThrowsException()
{
// Test error handling
}
}
Extension Points¶
Custom Transformers¶
Create reusable transformers:
public static class CommonTransformers
{
public static Func<string, string> ToUpperCase =>
value => value?.ToUpper();
public static Func<string, string> FormatDate(string format) =>
value => DateTime.Parse(value).ToString(format);
public static Func<string, string> DefaultIfEmpty(string defaultValue) =>
value => string.IsNullOrEmpty(value) ? defaultValue : value;
}
// Usage
profile.CreateMap("//Person/Name", "/root/Employee/Name",
CommonTransformers.ToUpperCase);
Custom Validators¶
Add validation rules:
public interface IValidationRule
{
bool Validate(XDocument document, out string error);
}
public class RequiredElementRule : IValidationRule
{
private readonly string _xpath;
public RequiredElementRule(string xpath)
{
_xpath = xpath;
}
public bool Validate(XDocument document, out string error)
{
var element = document.XPathSelectElement(_xpath);
if (element == null)
{
error = $"Required element not found: {_xpath}";
return false;
}
error = null;
return true;
}
}
Plugin Architecture¶
Support external mapping providers:
public interface IMappingProvider
{
HybridMappingProfile LoadMappings();
}
public class JsonMappingProvider : IMappingProvider
{
private readonly string _configPath;
public HybridMappingProfile LoadMappings()
{
var json = File.ReadAllText(_configPath);
// Parse JSON and build profile
return profile;
}
}
Resources and Next Steps¶
Recommended Reading¶
Tools¶
- XPath Tester: Online tools to test XPath expressions
- XML Spy: IDE for XML development
- Visual Studio XML Tools: Built-in schema validation and IntelliSense
Next Steps¶
- Choose the appropriate option for your use case
- Implement a proof of concept with sample data
- Add comprehensive unit tests
- Performance test with realistic data volumes
- Add logging and diagnostics
- Document your mapping rules
- Deploy and monitor in production
Conclusion¶
This framework provides three powerful approaches to XML transformation in .NET. Choose the option that best fits your requirements:
- Option A for flexibility and dynamic schemas
- Option B for type safety and maintainability
- Option C for complex scenarios requiring multiple strategies
All three options can coexist in the same solution, allowing you to use the best tool for each transformation scenario.
Last Updated: January 2026 Framework Version: 1.0
Complete Implementation Code¶
Below is the complete, production-ready implementation of all three options. This code can be copied directly into your .NET project.
Namespace Structure¶
YourProject/
├── XmlMapping/
│ ├── ConfigurationBased/
│ │ ├── XPathMapping.cs
│ │ ├── XmlMappingProfile.cs
│ │ └── XPathTransformationEngine.cs
│ ├── StronglyTyped/
│ │ ├── IObjectMapper.cs
│ │ ├── ObjectMappingEngine.cs
│ │ └── Example models and mappers
│ ├── Hybrid/
│ │ ├── MappingStrategy.cs
│ │ ├── HybridMappingRule.cs
│ │ ├── HybridMappingProfile.cs
│ │ └── HybridTransformationEngine.cs
│ └── Examples/
│ └── UsageExamples.cs
Full Implementation¶
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml;
using System.Xml.Linq;
using System.Xml.Serialization;
using System.Xml.XPath;
// ============================================================================
// OPTION A: Configuration-Based XPath Mapping
// ============================================================================
namespace XmlMapping.ConfigurationBased
{
/// <summary>
/// Represents a single mapping rule from source XPath to target XPath
/// </summary>
public class XPathMapping
{
public string SourceXPath { get; set; }
public string TargetXPath { get; set; }
public Func<string, string> Transformer { get; set; }
public bool IsAttribute { get; set; }
}
/// <summary>
/// Profile that contains all mapping rules for XPath-based transformation
/// </summary>
public class XmlMappingProfile
{
private readonly List<XPathMapping> _mappings = new List<XPathMapping>();
/// <summary>
/// Creates a mapping from source XPath to target XPath
/// </summary>
/// <param name="sourceXPath">XPath expression to locate source data</param>
/// <param name="targetXPath">XPath expression for target location</param>
/// <param name="transformer">Optional transformation function</param>
/// <param name="isAttribute">Whether the target is an XML attribute</param>
public void CreateMap(string sourceXPath, string targetXPath,
Func<string, string> transformer = null, bool isAttribute = false)
{
_mappings.Add(new XPathMapping
{
SourceXPath = sourceXPath,
TargetXPath = targetXPath,
Transformer = transformer ?? (x => x),
IsAttribute = isAttribute
});
}
public IEnumerable<XPathMapping> GetMappings() => _mappings;
}
/// <summary>
/// Engine that executes XPath-based transformations
/// </summary>
public class XPathTransformationEngine
{
private readonly XmlMappingProfile _profile;
private readonly XmlNamespaceManager _nsManager;
/// <summary>
/// Initializes the transformation engine
/// </summary>
/// <param name="profile">Mapping profile with transformation rules</param>
/// <param name="namespaces">Optional XML namespaces dictionary</param>
public XPathTransformationEngine(XmlMappingProfile profile,
Dictionary<string, string> namespaces = null)
{
_profile = profile;
_nsManager = new XmlNamespaceManager(new NameTable());
if (namespaces != null)
{
foreach (var ns in namespaces)
{
_nsManager.AddNamespace(ns.Key, ns.Value);
}
}
}
/// <summary>
/// Transforms source XML document to target XML document
/// </summary>
public XDocument Transform(XDocument source)
{
var target = new XDocument(new XElement("root"));
var mappings = _profile.GetMappings();
foreach (var mapping in mappings)
{
var sourceNodes = source.XPathSelectElements(mapping.SourceXPath, _nsManager);
foreach (var sourceNode in sourceNodes)
{
var value = sourceNode.Value;
var transformedValue = mapping.Transformer(value);
SetTargetValue(target, mapping.TargetXPath, transformedValue,
mapping.IsAttribute);
}
}
return target;
}
private void SetTargetValue(XDocument target, string xpath, string value,
bool isAttribute)
{
var pathParts = xpath.Split('/').Where(p => !string.IsNullOrEmpty(p)).ToArray();
XElement current = target.Root;
// Navigate/create path to target location
for (int i = 0; i < pathParts.Length - 1; i++)
{
var part = pathParts[i].TrimStart('@');
var child = current.Element(part);
if (child == null)
{
child = new XElement(part);
current.Add(child);
}
current = child;
}
var lastPart = pathParts[pathParts.Length - 1];
// Set attribute or element value
if (isAttribute || lastPart.StartsWith("@"))
{
var attrName = lastPart.TrimStart('@');
current.SetAttributeValue(attrName, value);
}
else
{
var elem = current.Element(lastPart);
if (elem == null)
{
current.Add(new XElement(lastPart, value));
}
else
{
elem.Value = value;
}
}
}
}
}
// ============================================================================
// OPTION B: Strongly-Typed Object Mapping
// ============================================================================
namespace XmlMapping.StronglyTyped
{
// Example source class
public class SourcePerson
{
[XmlElement("FirstName")]
public string FirstName { get; set; }
[XmlElement("LastName")]
public string LastName { get; set; }
[XmlElement("BirthDate")]
public DateTime BirthDate { get; set; }
[XmlElement("Email")]
public string Email { get; set; }
}
// Example target class
public class TargetEmployee
{
[XmlAttribute("id")]
public string Id { get; set; }
[XmlElement("FullName")]
public string FullName { get; set; }
[XmlElement("Age")]
public int Age { get; set; }
[XmlElement("ContactEmail")]
public string ContactEmail { get; set; }
}
/// <summary>
/// Interface for mapping between two object types
/// </summary>
public interface IObjectMapper<TSource, TTarget>
{
TTarget Map(TSource source);
}
/// <summary>
/// Engine that handles XML serialization and object mapping
/// </summary>
public class ObjectMappingEngine<TSource, TTarget>
where TSource : class
where TTarget : class, new()
{
private readonly IObjectMapper<TSource, TTarget> _mapper;
private readonly XmlSerializer _sourceSerializer;
private readonly XmlSerializer _targetSerializer;
public ObjectMappingEngine(IObjectMapper<TSource, TTarget> mapper)
{
_mapper = mapper;
_sourceSerializer = new XmlSerializer(typeof(TSource));
_targetSerializer = new XmlSerializer(typeof(TTarget));
}
/// <summary>
/// Transforms XML document using object mapping
/// </summary>
public XDocument Transform(XDocument sourceXml)
{
// Deserialize XML to source object
TSource sourceObj;
using (var reader = sourceXml.CreateReader())
{
sourceObj = (TSource)_sourceSerializer.Deserialize(reader);
}
// Map to target object
var targetObj = _mapper.Map(sourceObj);
// Serialize target object to XML
var targetXml = new XDocument();
using (var writer = targetXml.CreateWriter())
{
_targetSerializer.Serialize(writer, targetObj);
}
return targetXml;
}
/// <summary>
/// Transforms XML string to XML string
/// </summary>
public string TransformToString(string sourceXmlString)
{
var sourceDoc = XDocument.Parse(sourceXmlString);
var targetDoc = Transform(sourceDoc);
return targetDoc.ToString();
}
}
/// <summary>
/// Example mapper implementation
/// </summary>
public class PersonToEmployeeMapper : IObjectMapper<SourcePerson, TargetEmployee>
{
public TargetEmployee Map(SourcePerson source)
{
return new TargetEmployee
{
Id = Guid.NewGuid().ToString(),
FullName = $"{source.FirstName} {source.LastName}",
Age = CalculateAge(source.BirthDate),
ContactEmail = source.Email
};
}
private int CalculateAge(DateTime birthDate)
{
var today = DateTime.Today;
var age = today.Year - birthDate.Year;
if (birthDate.Date > today.AddYears(-age)) age--;
return age;
}
}
}
// ============================================================================
// OPTION C: Hybrid Approach
// ============================================================================
namespace XmlMapping.Hybrid
{
/// <summary>
/// Defines the mapping strategy to use
/// </summary>
public enum MappingStrategy
{
XPath,
ObjectBased,
Custom
}
/// <summary>
/// Flexible mapping rule that supports multiple strategies
/// </summary>
public class HybridMappingRule
{
public string Name { get; set; }
public MappingStrategy Strategy { get; set; }
public string SourceXPath { get; set; }
public string TargetXPath { get; set; }
public Func<XElement, object> CustomExtractor { get; set; }
public Func<object, XElement> CustomBuilder { get; set; }
public Type SourceType { get; set; }
public Type TargetType { get; set; }
public Delegate ObjectMapper { get; set; }
}
/// <summary>
/// Profile that can contain mixed mapping strategies
/// </summary>
public class HybridMappingProfile
{
private readonly List<HybridMappingRule> _rules = new List<HybridMappingRule>();
/// <summary>
/// Adds an XPath-based mapping rule
/// </summary>
public void AddXPathMapping(string name, string sourceXPath, string targetXPath)
{
_rules.Add(new HybridMappingRule
{
Name = name,
Strategy = MappingStrategy.XPath,
SourceXPath = sourceXPath,
TargetXPath = targetXPath
});
}
/// <summary>
/// Adds an object-based mapping rule
/// </summary>
public void AddObjectMapping<TSource, TTarget>(string name,
string sourceXPath, Func<TSource, TTarget> mapper)
{
_rules.Add(new HybridMappingRule
{
Name = name,
Strategy = MappingStrategy.ObjectBased,
SourceXPath = sourceXPath,
SourceType = typeof(TSource),
TargetType = typeof(TTarget),
ObjectMapper = mapper
});
}
/// <summary>
/// Adds a custom mapping rule with extraction and building logic
/// </summary>
public void AddCustomMapping(string name,
Func<XElement, object> extractor,
Func<object, XElement> builder)
{
_rules.Add(new HybridMappingRule
{
Name = name,
Strategy = MappingStrategy.Custom,
CustomExtractor = extractor,
CustomBuilder = builder
});
}
public IEnumerable<HybridMappingRule> GetRules() => _rules;
}
/// <summary>
/// Engine that can execute multiple transformation strategies
/// </summary>
public class HybridTransformationEngine
{
private readonly HybridMappingProfile _profile;
private readonly Dictionary<Type, XmlSerializer> _serializers;
public HybridTransformationEngine(HybridMappingProfile profile)
{
_profile = profile;
_serializers = new Dictionary<Type, XmlSerializer>();
}
/// <summary>
/// Transforms XML using appropriate strategy for each rule
/// </summary>
public XDocument Transform(XDocument source)
{
var target = new XDocument(new XElement("root"));
var rules = _profile.GetRules();
foreach (var rule in rules)
{
switch (rule.Strategy)
{
case MappingStrategy.XPath:
ProcessXPathMapping(source, target, rule);
break;
case MappingStrategy.ObjectBased:
ProcessObjectMapping(source, target, rule);
break;
case MappingStrategy.Custom:
ProcessCustomMapping(source, target, rule);
break;
}
}
return target;
}
private void ProcessXPathMapping(XDocument source, XDocument target,
HybridMappingRule rule)
{
var sourceElements = source.XPathSelectElements(rule.SourceXPath);
foreach (var elem in sourceElements)
{
var targetPath = rule.TargetXPath;
AddElementToTarget(target.Root, targetPath, elem.Value);
}
}
private void ProcessObjectMapping(XDocument source, XDocument target,
HybridMappingRule rule)
{
var sourceElements = source.XPathSelectElements(rule.SourceXPath);
var serializer = GetSerializer(rule.SourceType);
foreach (var elem in sourceElements)
{
using (var reader = elem.CreateReader())
{
var sourceObj = serializer.Deserialize(reader);
var targetObj = rule.ObjectMapper.DynamicInvoke(sourceObj);
var targetSerializer = GetSerializer(rule.TargetType);
var targetElement = new XElement("temp");
using (var writer = targetElement.CreateWriter())
{
targetSerializer.Serialize(writer, targetObj);
}
target.Root.Add(targetElement.Elements());
}
}
}
private void ProcessCustomMapping(XDocument source, XDocument target,
HybridMappingRule rule)
{
var extracted = rule.CustomExtractor(source.Root);
var built = rule.CustomBuilder(extracted);
target.Root.Add(built);
}
private XmlSerializer GetSerializer(Type type)
{
if (!_serializers.ContainsKey(type))
{
_serializers[type] = new XmlSerializer(type);
}
return _serializers[type];
}
private void AddElementToTarget(XElement parent, string path, string value)
{
var parts = path.Split('/').Where(p => !string.IsNullOrEmpty(p)).ToArray();
var current = parent;
for (int i = 0; i < parts.Length - 1; i++)
{
var child = current.Element(parts[i]);
if (child == null)
{
child = new XElement(parts[i]);
current.Add(child);
}
current = child;
}
current.Add(new XElement(parts[parts.Length - 1], value));
}
}
}
// ============================================================================
// USAGE EXAMPLES
// ============================================================================
namespace XmlMapping.Examples
{
using XmlMapping.ConfigurationBased;
using XmlMapping.StronglyTyped;
using XmlMapping.Hybrid;
public class UsageExamples
{
/// <summary>
/// Example: XPath-based transformation
/// </summary>
public static void Example_OptionA_XPathBased()
{
Console.WriteLine("=== Option A: XPath-Based Transformation ===");
// Define mapping profile
var profile = new XmlMappingProfile();
profile.CreateMap("//Person/Name", "/root/Employee/FullName");
profile.CreateMap("//Person/Age", "/root/Employee/YearsOld",
age => age); // Can add transformation
profile.CreateMap("//Person/Id", "/root/Employee/@employeeId",
isAttribute: true);
// Create engine with namespaces if needed
var namespaces = new Dictionary<string, string>
{
{ "ns", "http://example.com/schema" }
};
var engine = new XPathTransformationEngine(profile, namespaces);
// Transform
var sourceXml = XDocument.Parse(@"
<People>
<Person>
<n>John Doe</n>
<Age>30</Age>
<Id>123</Id>
</Person>
</People>");
var result = engine.Transform(sourceXml);
Console.WriteLine(result);
Console.WriteLine();
}
/// <summary>
/// Example: Strongly-typed object mapping
/// </summary>
public static void Example_OptionB_StronglyTyped()
{
Console.WriteLine("=== Option B: Strongly-Typed Transformation ===");
// Create mapper
var mapper = new PersonToEmployeeMapper();
var engine = new ObjectMappingEngine<SourcePerson, TargetEmployee>(mapper);
// Transform
var sourceXml = @"
<SourcePerson>
<FirstName>John</FirstName>
<LastName>Doe</LastName>
<BirthDate>1990-05-15</BirthDate>
<Email>john.doe@example.com</Email>
</SourcePerson>";
var result = engine.TransformToString(sourceXml);
Console.WriteLine(result);
Console.WriteLine();
}
/// <summary>
/// Example: Hybrid approach with multiple strategies
/// </summary>
public static void Example_OptionC_Hybrid()
{
Console.WriteLine("=== Option C: Hybrid Transformation ===");
var profile = new HybridMappingProfile();
// Mix XPath mappings
profile.AddXPathMapping("basic", "//Person/Name", "/root/Name");
// Mix object-based mappings
profile.AddObjectMapping<SourcePerson, TargetEmployee>(
"person-to-employee",
"//SourcePerson",
person => new PersonToEmployeeMapper().Map(person));
// Mix custom logic
profile.AddCustomMapping(
"calculated-field",
source => source.Descendants("Items").Count(),
count => new XElement("ItemCount", count));
var engine = new HybridTransformationEngine(profile);
var sourceXml = XDocument.Parse(@"
<root>
<Person><n>Test</n></Person>
<Items><Item>1</Item><Item>2</Item></Items>
</root>");
var result = engine.Transform(sourceXml);
Console.WriteLine(result);
Console.WriteLine();
}
/// <summary>
/// Main entry point to run all examples
/// </summary>
public static void Main(string[] args)
{
try
{
Example_OptionA_XPathBased();
Example_OptionB_StronglyTyped();
Example_OptionC_Hybrid();
}
catch (Exception ex)
{
Console.WriteLine($"Error: {ex.Message}");
Console.WriteLine(ex.StackTrace);
}
}
}
}
How to Use This Code¶
-
Create a new .NET project (Console App or Class Library):
dotnet new console -n XmlTransformationFramework cd XmlTransformationFramework -
Copy the code into your project with the appropriate namespace structure
-
Add required using statements at the top of your files:
using System; using System.Collections.Generic; using System.Linq; using System.Xml; using System.Xml.Linq; using System.Xml.Serialization; using System.Xml.XPath; -
Run the examples:
dotnet run
NuGet Packages Required¶
No external NuGet packages are required! All the code uses built-in .NET libraries:
- System.Xml
- System.Xml.Linq
- System.Xml.Serialization
- System.Xml.XPath
These are included in the standard .NET Framework and .NET Core/5+/6+ runtime.
Testing the Implementation¶
Here's a complete test example you can use:
using System;
using System.Xml.Linq;
using XmlMapping.ConfigurationBased;
class Program
{
static void Main()
{
// Create a simple mapping
var profile = new XmlMappingProfile();
profile.CreateMap("//SourcePerson/FirstName", "/root/Person/First");
profile.CreateMap("//SourcePerson/LastName", "/root/Person/Last");
// Create the engine
var engine = new XPathTransformationEngine(profile);
// Source XML
var sourceXml = XDocument.Parse(@"
<SourcePerson>
<FirstName>John</FirstName>
<LastName>Doe</LastName>
</SourcePerson>");
// Transform
var result = engine.Transform(sourceXml);
// Output
Console.WriteLine("Source XML:");
Console.WriteLine(sourceXml);
Console.WriteLine("\nTransformed XML:");
Console.WriteLine(result);
}
}
Expected output:
Source XML:
<SourcePerson>
<FirstName>John</FirstName>
<LastName>Doe</LastName>
</SourcePerson>
Transformed XML:
<root>
<Person>
<First>John</First>
<Last>Doe</Last>
</Person>
</root>
Advanced Example: Complex Transformation¶
using System;
using System.Xml.Linq;
using XmlMapping.ConfigurationBased;
class AdvancedExample
{
static void Main()
{
var profile = new XmlMappingProfile();
// Map with transformations
profile.CreateMap("//Order/CustomerId", "/root/Order/@customerId",
isAttribute: true);
profile.CreateMap("//Order/Total", "/root/Order/Amount",
total => $"${decimal.Parse(total):F2}"); // Format as currency
profile.CreateMap("//Order/Date", "/root/Order/OrderDate",
date => DateTime.Parse(date).ToString("yyyy-MM-dd"));
profile.CreateMap("//Order/Status", "/root/Order/OrderStatus",
status => status.ToUpper());
var engine = new XPathTransformationEngine(profile);
var sourceXml = XDocument.Parse(@"
<Order>
<CustomerId>C123</CustomerId>
<Total>1234.5</Total>
<Date>1/15/2024</Date>
<Status>pending</Status>
</Order>");
var result = engine.Transform(sourceXml);
Console.WriteLine(result);
}
}
This will produce:
<root>
<Order customerId="C123">
<Amount>$1234.50</Amount>
<OrderDate>2024-01-15</OrderDate>
<OrderStatus>PENDING</OrderStatus>
</Order>
</root>
Integration with Existing Projects¶
To integrate this framework into an existing project:
- Add the framework files to your solution
- Reference the namespace:
using XmlMapping.ConfigurationBased;(or other options) - Create configuration files for your mappings (optional)
- Add dependency injection (optional):
// In your Startup.cs or Program.cs
services.AddSingleton<XmlMappingProfile>(sp =>
{
var profile = new XmlMappingProfile();
// Configure your mappings here or load from config
return profile;
});
services.AddSingleton<XPathTransformationEngine>();
Performance Considerations¶
For large documents (>10MB), consider:
using System.Xml;
public class StreamingTransformationEngine
{
public void TransformLargeDocument(string sourcePath, string targetPath)
{
using var reader = XmlReader.Create(sourcePath);
using var writer = XmlWriter.Create(targetPath, new XmlWriterSettings
{
Indent = true
});
// Process nodes in streaming fashion
while (reader.Read())
{
if (reader.NodeType == XmlNodeType.Element)
{
// Transform and write node by node
}
}
}
}
Implementation code last updated: January 2026