Skip to content

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

  1. Framework Architecture
  2. Option A: Configuration-Based (XPath)
  3. Option B: Strongly-Typed Object Mapping
  4. Option C: Hybrid Approach
  5. Comparison Matrix
  6. Implementation Considerations
  7. 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

  1. Separation of Concerns: Mapping configuration is separate from transformation logic
  2. Extensibility: Easy to add custom transformers and validators
  3. Type Safety: Where possible, leverage compile-time type checking
  4. Performance: Consider streaming for large documents
  5. 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:

  1. Streaming: Use XmlReader and XmlWriter instead of loading entire document
  2. Lazy evaluation: Process nodes on-demand
  3. Caching: Cache compiled XPath expressions
  4. 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

  1. Start Simple: Begin with the simplest approach that meets your needs
  2. Test Thoroughly: Unit test each mapping rule
  3. Document Mappings: Keep clear documentation of transformation logic
  4. Version Control: Track changes to mapping configurations
  5. Performance Test: Benchmark with realistic data volumes
  6. Handle Nulls: Always check for null/missing elements
  7. Use Namespaces: Properly handle XML namespaces
  8. Validate: Add validation before and after transformation
  9. Error Context: Provide meaningful error messages with XPath context
  10. 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

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

  1. Choose the appropriate option for your use case
  2. Implement a proof of concept with sample data
  3. Add comprehensive unit tests
  4. Performance test with realistic data volumes
  5. Add logging and diagnostics
  6. Document your mapping rules
  7. 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

  1. Create a new .NET project (Console App or Class Library):

    dotnet new console -n XmlTransformationFramework
    cd XmlTransformationFramework
    

  2. Copy the code into your project with the appropriate namespace structure

  3. 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;
    

  4. 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:

  1. Add the framework files to your solution
  2. Reference the namespace: using XmlMapping.ConfigurationBased; (or other options)
  3. Create configuration files for your mappings (optional)
  4. 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