CargoWise eAdapter Message Transformation & XSLT: Complete Guide 2025

Nov 15, 2025
cargowiseeadapterxsltxml-transformation
0

CargoWise eAdapter relies on XML message exchanges, requiring robust transformation capabilities to map data between external systems and CargoWise's complex schemas. Whether you're integrating with ERP systems, TMS platforms, or custom applications, understanding message transformation is crucial for building reliable eAdapter integrations.

This comprehensive guide covers XSLT transformation patterns, XML schema validation, data mapping strategies, and production-ready approaches for CargoWise eAdapter message transformation. You'll learn how to transform complex data structures, handle schema validation, implement custom mapping logic, and optimize transformation performance.

Understanding eAdapter Message Transformation

Why Transformation is Needed

CargoWise eAdapter uses standardized XML schemas (XSD) for message exchange. However, external systems often have different data structures, formats, and naming conventions. Transformation bridges this gap by:

  • Mapping field names between different systems
  • Converting data formats (dates, numbers, codes)
  • Restructuring data to match CargoWise schemas
  • Enriching data with default values or calculated fields
  • Validating data before sending to CargoWise

Transformation Approaches

1. XSLT Transformation

  • Declarative, XML-based transformation language
  • Well-suited for complex structural transformations
  • Standardized and widely supported
  • Can be version-controlled and tested independently

2. Code-Based Transformation (C#)

  • Imperative transformation logic
  • Better for complex business rules
  • Easier to debug and test
  • More flexible for dynamic transformations

3. Hybrid Approach

  • XSLT for structural transformations
  • C# for business logic and validation
  • Best of both worlds

XSLT Fundamentals for eAdapter

Basic XSLT Structure

XSLT (eXtensible Stylesheet Language Transformations) uses templates to transform XML documents:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:cw="urn:cargowise:eadapter:universalshipment">
    
    <xsl:output method="xml" indent="yes" encoding="UTF-8"/>
    
    <!-- Root template -->
    <xsl:template match="/">
        <cw:UniversalShipment>
            <xsl:apply-templates/>
        </cw:UniversalShipment>
    </xsl:template>
    
    <!-- Shipment transformation -->
    <xsl:template match="Shipment">
        <cw:Shipment>
            <cw:ShipmentId>
                <xsl:value-of select="ID"/>
            </cw:ShipmentId>
            <cw:Mode>
                <xsl:value-of select="TransportMode"/>
            </cw:Mode>
        </cw:Shipment>
    </xsl:template>
</xsl:stylesheet>

XSLT Template Matching

Templates use XPath expressions to match XML elements:

<xsl:template match="Shipment">
    <!-- Matches <Shipment> elements -->
</xsl:template>

<xsl:template match="Shipment[@Status='Active']">
    <!-- Matches Shipment elements with Status='Active' -->
</xsl:template>

<xsl:template match="Shipment//Origin">
    <!-- Matches Origin elements anywhere within Shipment -->
</xsl:template>

Data Extraction and Transformation

Extract and transform data using XPath expressions:

<xsl:template match="Shipment">
    <cw:Shipment>
        <!-- Direct value extraction -->
        <cw:ShipmentId>
            <xsl:value-of select="ShipmentNumber"/>
        </cw:ShipmentId>
        
        <!-- Conditional transformation -->
        <cw:Status>
            <xsl:choose>
                <xsl:when test="Status = 'Active'">ACTIVE</xsl:when>
                <xsl:when test="Status = 'Inactive'">INACTIVE</xsl:when>
                <xsl:otherwise>UNKNOWN</xsl:otherwise>
            </xsl:choose>
        </cw:Status>
        
        <!-- Date transformation -->
        <cw:CreatedDate>
            <xsl:value-of select="format-date(CreatedDate, '[Y0001]-[M01]-[D01]T[H01]:[m01]:[s01]Z')"/>
        </cw:CreatedDate>
        
        <!-- Number formatting -->
        <cw:Weight>
            <xsl:value-of select="format-number(Weight, '0.00')"/>
        </cw:Weight>
    </cw:Shipment>
</xsl:template>

Advanced XSLT Patterns

Variable Usage

Use variables to store intermediate values:

<xsl:template match="Shipment">
    <xsl:variable name="totalWeight" select="sum(Items/Weight)"/>
    <xsl:variable name="weightUnit" select="Items[1]/WeightUnit"/>
    
    <cw:Shipment>
        <cw:TotalWeight>
            <xsl:value-of select="$totalWeight"/>
        </cw:TotalWeight>
        <cw:WeightUnit>
            <xsl:value-of select="$weightUnit"/>
        </cw:WeightUnit>
    </cw:Shipment>
</xsl:template>

Function Definitions

Define custom functions for reusable logic:

<xsl:function name="cw:formatCountryCode" as="xs:string">
    <xsl:param name="country" as="xs:string"/>
    <xsl:choose>
        <xsl:when test="$country = 'United States'">US</xsl:when>
        <xsl:when test="$country = 'United Kingdom'">GB</xsl:when>
        <xsl:otherwise>
            <xsl:value-of select="upper-case(substring($country, 1, 2))"/>
        </xsl:otherwise>
    </xsl:choose>
</xsl:function>

<xsl:template match="Origin">
    <cw:Origin>
        <cw:CountryCode>
            <xsl:value-of select="cw:formatCountryCode(Country)"/>
        </cw:CountryCode>
    </cw:Origin>
</xsl:template>

Looping and Iteration

Transform collections using for-each:

<xsl:template match="Shipment">
    <cw:Shipment>
        <cw:Items>
            <xsl:for-each select="Items/Item">
                <cw:Item>
                    <cw:ItemId>
                        <xsl:value-of select="ID"/>
                    </cw:ItemId>
                    <cw:Description>
                        <xsl:value-of select="Description"/>
                    </cw:Description>
                    <cw:Quantity>
                        <xsl:value-of select="Quantity"/>
                    </cw:Quantity>
                </cw:Item>
            </xsl:for-each>
        </cw:Items>
    </cw:Shipment>
</xsl:template>

Conditional Processing

Apply conditional logic based on data:

<xsl:template match="Shipment">
    <cw:Shipment>
        <!-- Include optional fields conditionally -->
        <xsl:if test="CustomsValue">
            <cw:CustomsValue>
                <xsl:value-of select="CustomsValue"/>
            </cw:CustomsValue>
        </xsl:if>
        
        <!-- Multiple conditions -->
        <xsl:choose>
            <xsl:when test="Mode = 'Air'">
                <cw:AirportCode>
                    <xsl:value-of select="Airport"/>
                </cw:AirportCode>
            </xsl:when>
            <xsl:when test="Mode = 'Ocean'">
                <cw:PortCode>
                    <xsl:value-of select="Port"/>
                </cw:PortCode>
            </xsl:when>
        </xsl:choose>
    </cw:Shipment>
</xsl:template>

XML Schema Validation

Loading and Validating Schemas

Validate XML against CargoWise XSD schemas before transformation:

public class XmlSchemaValidator
{
    private readonly XmlSchemaSet _schemaSet;
    private readonly ILogger<XmlSchemaValidator> _logger;

    public XmlSchemaValidator(ILogger<XmlSchemaValidator> logger)
    {
        _schemaSet = new XmlSchemaSet();
        _logger = logger;
        LoadSchemas();
    }

    private void LoadSchemas()
    {
        // Load CargoWise eAdapter schemas
        var schemaPaths = new[]
        {
            "Schemas/UniversalShipment.xsd",
            "Schemas/Consol.xsd",
            "Schemas/Forward.xsd"
        };

        foreach (var schemaPath in schemaPaths)
        {
            using var reader = XmlReader.Create(schemaPath);
            var schema = XmlSchema.Read(reader, null);
            _schemaSet.Add(schema);
        }

        _schemaSet.Compile();
    }

    public ValidationResult Validate(string xmlContent)
    {
        var errors = new List<ValidationError>();
        
        var settings = new XmlReaderSettings
        {
            Schemas = _schemaSet,
            ValidationType = ValidationType.Schema,
            ValidationFlags = XmlSchemaValidationFlags.ReportValidationWarnings
        };

        settings.ValidationEventHandler += (sender, e) =>
        {
            errors.Add(new ValidationError
            {
                Severity = e.Severity,
                Message = e.Message,
                LineNumber = e.Exception?.LineNumber ?? 0,
                LinePosition = e.Exception?.LinePosition ?? 0
            });
        };

        try
        {
            using var reader = XmlReader.Create(new StringReader(xmlContent), settings);
            while (reader.Read()) { }
            
            return new ValidationResult
            {
                IsValid = errors.Count == 0,
                Errors = errors
            };
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Schema validation failed");
            return new ValidationResult
            {
                IsValid = false,
                Errors = new[] { new ValidationError { Message = ex.Message } }
            };
        }
    }
}

Pre-Transformation Validation

Validate source data before transformation:

public class PreTransformationValidator
{
    public ValidationResult ValidateSourceData(XDocument sourceXml)
    {
        var errors = new List<ValidationError>();

        // Validate required fields
        var shipmentId = sourceXml.Descendants("ShipmentId").FirstOrDefault();
        if (shipmentId == null || string.IsNullOrWhiteSpace(shipmentId.Value))
        {
            errors.Add(new ValidationError
            {
                Message = "ShipmentId is required",
                Field = "ShipmentId"
            });
        }

        // Validate data formats
        var weight = sourceXml.Descendants("Weight").FirstOrDefault();
        if (weight != null && !decimal.TryParse(weight.Value, out _))
        {
            errors.Add(new ValidationError
            {
                Message = "Weight must be a valid decimal",
                Field = "Weight"
            });
        }

        // Validate business rules
        var origin = sourceXml.Descendants("Origin").FirstOrDefault();
        var destination = sourceXml.Descendants("Destination").FirstOrDefault();
        if (origin?.Value == destination?.Value)
        {
            errors.Add(new ValidationError
            {
                Message = "Origin and destination cannot be the same",
                Field = "Origin/Destination"
            });
        }

        return new ValidationResult
        {
            IsValid = errors.Count == 0,
            Errors = errors
        };
    }
}

XSLT Transformation in .NET

XslCompiledTransform Usage

Use .NET's XslCompiledTransform for XSLT execution:

public class XsltTransformer
{
    private readonly XslCompiledTransform _transform;
    private readonly ILogger<XsltTransformer> _logger;

    public XsltTransformer(string xsltPath, ILogger<XsltTransformer> logger)
    {
        _transform = new XslCompiledTransform();
        _logger = logger;
        LoadTransform(xsltPath);
    }

    private void LoadTransform(string xsltPath)
    {
        var settings = new XsltSettings(true, true); // Enable script and document functions
        _transform.Load(xsltPath, settings, new XmlUrlResolver());
    }

    public string Transform(string sourceXml)
    {
        using var sourceReader = XmlReader.Create(new StringReader(sourceXml));
        using var resultWriter = new StringWriter();
        using var resultXmlWriter = XmlWriter.Create(resultWriter, new XmlWriterSettings
        {
            Indent = true,
            IndentChars = "  ",
            OmitXmlDeclaration = false
        });

        try
        {
            _transform.Transform(sourceReader, resultXmlWriter);
            return resultWriter.ToString();
        }
        catch (XsltException ex)
        {
            _logger.LogError(ex, "XSLT transformation failed");
            throw new TransformationException("XSLT transformation error", ex);
        }
    }

    public string Transform(XDocument sourceXml, Dictionary<string, object> parameters = null)
    {
        var args = new XsltArgumentList();
        
        if (parameters != null)
        {
            foreach (var param in parameters)
            {
                args.AddParam(param.Key, "", param.Value);
            }
        }

        using var sourceReader = sourceXml.CreateReader();
        using var resultWriter = new StringWriter();
        using var resultXmlWriter = XmlWriter.Create(resultWriter, new XmlWriterSettings
        {
            Indent = true,
            IndentChars = "  "
        });

        _transform.Transform(sourceReader, args, resultXmlWriter);
        return resultWriter.ToString();
    }
}

XSLT with Extension Objects

Pass .NET objects to XSLT for complex operations:

public class XsltExtensionObject
{
    public string FormatDate(DateTime date, string format)
    {
        return date.ToString(format);
    }

    public string GetCountryCode(string countryName)
    {
        return countryName switch
        {
            "United States" => "US",
            "United Kingdom" => "GB",
            "Canada" => "CA",
            _ => countryName.Substring(0, 2).ToUpper()
        };
    }

    public decimal CalculateTotal(IEnumerable<decimal> values)
    {
        return values.Sum();
    }
}

// Usage in transformation
var extensionObject = new XsltExtensionObject();
var args = new XsltArgumentList();
args.AddExtensionObject("urn:extensions", extensionObject);

Data Mapping Strategies

Field Mapping Configuration

Use configuration-driven mapping for flexibility:

public class FieldMapping
{
    public string SourceField { get; set; }
    public string TargetField { get; set; }
    public string Transformation { get; set; }
    public object DefaultValue { get; set; }
    public bool Required { get; set; }
}

public class MappingConfiguration
{
    public List<FieldMapping> Mappings { get; set; } = new();
    
    public static MappingConfiguration LoadFromJson(string jsonPath)
    {
        var json = File.ReadAllText(jsonPath);
        return JsonSerializer.Deserialize<MappingConfiguration>(json);
    }
}

// Example mapping configuration
{
  "mappings": [
    {
      "sourceField": "ShipmentNumber",
      "targetField": "ShipmentId",
      "transformation": "direct",
      "required": true
    },
    {
      "sourceField": "TransportMode",
      "targetField": "Mode",
      "transformation": "uppercase",
      "required": true
    },
    {
      "sourceField": "CreatedDate",
      "targetField": "CreatedDate",
      "transformation": "date-iso8601",
      "required": false
    }
  ]
}

Code-Based Transformation

Implement complex transformations in C#:

public class CargoWiseMessageTransformer
{
    private readonly ILogger<CargoWiseMessageTransformer> _logger;
    private readonly MappingConfiguration _mappingConfig;

    public CargoWiseMessageTransformer(
        MappingConfiguration mappingConfig,
        ILogger<CargoWiseMessageTransformer> logger)
    {
        _mappingConfig = mappingConfig;
        _logger = logger;
    }

    public XDocument Transform(XDocument sourceXml)
    {
        var targetXml = new XDocument(
            new XElement("UniversalShipment",
                new XElement("Header",
                    new XElement("MessageId", GenerateMessageId()),
                    new XElement("Timestamp", DateTime.UtcNow.ToString("o"))
                ),
                TransformShipment(sourceXml)
            )
        );

        return targetXml;
    }

    private XElement TransformShipment(XDocument sourceXml)
    {
        var sourceShipment = sourceXml.Descendants("Shipment").FirstOrDefault();
        if (sourceShipment == null)
            throw new TransformationException("Source shipment not found");

        var targetShipment = new XElement("Shipment");

        foreach (var mapping in _mappingConfig.Mappings)
        {
            var sourceValue = GetSourceValue(sourceShipment, mapping.SourceField);
            var transformedValue = ApplyTransformation(sourceValue, mapping.Transformation);
            
            if (mapping.Required && string.IsNullOrWhiteSpace(transformedValue))
            {
                if (mapping.DefaultValue != null)
                    transformedValue = mapping.DefaultValue.ToString();
                else
                    throw new TransformationException($"Required field {mapping.SourceField} is missing");
            }

            SetTargetValue(targetShipment, mapping.TargetField, transformedValue);
        }

        return targetShipment;
    }

    private string GetSourceValue(XElement source, string fieldPath)
    {
        var element = source.XPathSelectElement(fieldPath);
        return element?.Value ?? string.Empty;
    }

    private string ApplyTransformation(string value, string transformation)
    {
        return transformation switch
        {
            "direct" => value,
            "uppercase" => value.ToUpper(),
            "lowercase" => value.ToLower(),
            "date-iso8601" => DateTime.Parse(value).ToString("o"),
            "trim" => value.Trim(),
            _ => value
        };
    }

    private void SetTargetValue(XElement target, string fieldPath, string value)
    {
        var parts = fieldPath.Split('/');
        var current = target;

        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[^1], value));
    }
}

Hybrid Transformation Approach

Combine XSLT and C# for optimal results:

public class HybridTransformer
{
    private readonly XsltTransformer _xsltTransformer;
    private readonly CargoWiseMessageTransformer _codeTransformer;
    private readonly ILogger<HybridTransformer> _logger;

    public HybridTransformer(
        XsltTransformer xsltTransformer,
        CargoWiseMessageTransformer codeTransformer,
        ILogger<HybridTransformer> logger)
    {
        _xsltTransformer = xsltTransformer;
        _codeTransformer = codeTransformer;
        _logger = logger;
    }

    public XDocument Transform(XDocument sourceXml)
    {
        // Step 1: Use XSLT for structural transformation
        var xsltResult = _xsltTransformer.Transform(sourceXml.ToString());
        var intermediateXml = XDocument.Parse(xsltResult);

        // Step 2: Use C# for business logic and validation
        var finalXml = _codeTransformer.Transform(intermediateXml);

        // Step 3: Validate against CargoWise schema
        ValidateSchema(finalXml);

        return finalXml;
    }

    private void ValidateSchema(XDocument xml)
    {
        var validator = new XmlSchemaValidator(_logger);
        var result = validator.Validate(xml.ToString());
        
        if (!result.IsValid)
        {
            var errors = string.Join(", ", result.Errors.Select(e => e.Message));
            throw new ValidationException($"Schema validation failed: {errors}");
        }
    }
}

Performance Optimization

Caching Compiled Transforms

Cache compiled XSLT transforms for performance:

public class CachedXsltTransformer
{
    private readonly ConcurrentDictionary<string, XslCompiledTransform> _cache;
    private readonly ILogger<CachedXsltTransformer> _logger;

    public CachedXsltTransformer(ILogger<CachedXsltTransformer> logger)
    {
        _cache = new ConcurrentDictionary<string, XslCompiledTransform>();
        _logger = logger;
    }

    public XslCompiledTransform GetTransform(string xsltPath)
    {
        return _cache.GetOrAdd(xsltPath, path =>
        {
            var transform = new XslCompiledTransform();
            var settings = new XsltSettings(true, true);
            transform.Load(path, settings, new XmlUrlResolver());
            return transform;
        });
    }
}

Streaming Transformations

Use streaming for large XML documents:

public class StreamingTransformer
{
    public void TransformStreaming(
        Stream sourceStream,
        Stream targetStream,
        XslCompiledTransform transform)
    {
        using var sourceReader = XmlReader.Create(sourceStream);
        using var targetWriter = XmlWriter.Create(targetStream, new XmlWriterSettings
        {
            Indent = true,
            IndentChars = "  "
        });

        transform.Transform(sourceReader, targetWriter);
    }
}

Testing Transformations

Unit Testing XSLT

Test XSLT transformations with sample data:

[Fact]
public void ShouldTransformShipmentCorrectly()
{
    var sourceXml = @"<?xml version=""1.0""?>
        <Shipment>
            <ShipmentNumber>SH-001</ShipmentNumber>
            <TransportMode>Air</TransportMode>
            <Weight>150.5</Weight>
        </Shipment>";

    var transformer = new XsltTransformer("Transforms/ShipmentTransform.xslt", _logger);
    var result = transformer.Transform(sourceXml);

    var resultDoc = XDocument.Parse(result);
    Assert.Equal("SH-001", resultDoc.Descendants("ShipmentId").First().Value);
    Assert.Equal("AIR", resultDoc.Descendants("Mode").First().Value);
    Assert.Equal("150.50", resultDoc.Descendants("Weight").First().Value);
}

Integration Testing

Test end-to-end transformation pipelines:

[Fact]
public async Task ShouldTransformAndValidateCompleteMessage()
{
    var sourceXml = LoadTestData("TestData/SampleShipment.xml");
    var transformer = new HybridTransformer(/* ... */);
    
    var result = transformer.Transform(sourceXml);
    
    // Validate schema
    var validator = new XmlSchemaValidator(_logger);
    var validationResult = validator.Validate(result.ToString());
    Assert.True(validationResult.IsValid);
    
    // Validate business rules
    Assert.NotNull(result.Descendants("ShipmentId").FirstOrDefault());
    Assert.NotNull(result.Descendants("Mode").FirstOrDefault());
}

Production Best Practices

1. Version Control Transformations

  • Store XSLT files in version control
  • Use semantic versioning for transformation changes
  • Document breaking changes

2. Error Handling

  • Catch and log transformation errors
  • Provide detailed error messages
  • Implement fallback transformations

3. Performance Monitoring

  • Track transformation execution time
  • Monitor memory usage for large documents
  • Alert on performance degradation

4. Testing Strategy

  • Unit test individual transformations
  • Integration test complete pipelines
  • Use sample data from production

5. Documentation

  • Document mapping rules
  • Maintain transformation reference
  • Keep examples up to date

Advanced XSLT Patterns

Complex Data Aggregation

Aggregate data across multiple elements:

<xsl:template match="Shipment">
    <cw:Shipment>
        <xsl:variable name="totalWeight" select="sum(Items/Item/Weight)"/>
        <xsl:variable name="totalValue" select="sum(Items/Item/Value)"/>
        <xsl:variable name="itemCount" select="count(Items/Item)"/>
        
        <cw:TotalWeight>
            <xsl:value-of select="format-number($totalWeight, '0.00')"/>
        </cw:TotalWeight>
        <cw:TotalValue>
            <xsl:value-of select="format-number($totalValue, '0.00')"/>
        </cw:TotalValue>
        <cw:ItemCount>
            <xsl:value-of select="$itemCount"/>
        </cw:ItemCount>
        
        <cw:AverageWeight>
            <xsl:value-of select="format-number($totalWeight div $itemCount, '0.00')"/>
        </cw:AverageWeight>
    </cw:Shipment>
</xsl:template>

Multi-Source Data Merging

Merge data from multiple sources:

<xsl:template match="Shipment">
    <xsl:variable name="shipmentData" select="."/>
    <xsl:variable name="customerData" select="document('customers.xml')/Customers/Customer[@Id=current()/CustomerId]"/>
    <xsl:variable name="productData" select="document('products.xml')/Products"/>
    
    <cw:Shipment>
        <cw:ShipmentId>
            <xsl:value-of select="$shipmentData/ShipmentNumber"/>
        </cw:ShipmentId>
        <cw:Customer>
            <cw:Name>
                <xsl:value-of select="$customerData/Name"/>
            </cw:Name>
            <cw:Address>
                <xsl:value-of select="$customerData/Address"/>
            </cw:Address>
        </cw:Customer>
        <cw:Items>
            <xsl:for-each select="$shipmentData/Items/Item">
                <xsl:variable name="product" select="$productData/Product[@Id=current()/ProductId]"/>
                <cw:Item>
                    <cw:ProductName>
                        <xsl:value-of select="$product/Name"/>
                    </cw:ProductName>
                    <cw:Quantity>
                        <xsl:value-of select="Quantity"/>
                    </cw:Quantity>
                </cw:Item>
            </xsl:for-each>
        </cw:Items>
    </cw:Shipment>
</xsl:template>

Dynamic Template Selection

Select templates dynamically based on data:

<xsl:template match="Shipment">
    <xsl:choose>
        <xsl:when test="Mode = 'Air'">
            <xsl:apply-templates select="." mode="air"/>
        </xsl:when>
        <xsl:when test="Mode = 'Ocean'">
            <xsl:apply-templates select="." mode="ocean"/>
        </xsl:when>
        <xsl:when test="Mode = 'Road'">
            <xsl:apply-templates select="." mode="road"/>
        </xsl:when>
        <xsl:otherwise>
            <xsl:apply-templates select="." mode="default"/>
        </xsl:otherwise>
    </xsl:choose>
</xsl:template>

<xsl:template match="Shipment" mode="air">
    <cw:Shipment>
        <cw:Mode>AIR</cw:Mode>
        <cw:AirportCode>
            <xsl:value-of select="Airport"/>
        </cw:AirportCode>
    </cw:Shipment>
</xsl:template>

<xsl:template match="Shipment" mode="ocean">
    <cw:Shipment>
        <cw:Mode>OCEAN</cw:Mode>
        <cw:PortCode>
            <xsl:value-of select="Port"/>
        </cw:PortCode>
    </cw:Shipment>
</xsl:template>

Real-World Transformation Scenarios

Scenario 1: Legacy System Integration

Transform data from legacy ERP system:

public class LegacyErpTransformer
{
    public XDocument TransformLegacyErpData(XDocument legacyXml)
    {
        // Legacy system uses different structure
        var shipments = legacyXml.Descendants("SHIPMENT_RECORD");
        
        var cargoWiseXml = new XDocument(
            new XElement("UniversalShipment",
                new XElement("Header",
                    new XElement("MessageId", GenerateMessageId()),
                    new XElement("Timestamp", DateTime.UtcNow.ToString("o"))
                ),
                shipments.Select(s => TransformLegacyShipment(s))
            )
        );

        return cargoWiseXml;
    }

    private XElement TransformLegacyShipment(XElement legacyShipment)
    {
        // Map legacy fields to CargoWise format
        return new XElement("Shipment",
            new XElement("ShipmentId", legacyShipment.Element("SHIP_NO")?.Value),
            new XElement("Mode", MapLegacyMode(legacyShipment.Element("TRANS_TYPE")?.Value)),
            new XElement("Origin",
                new XElement("City", legacyShipment.Element("ORIG_CITY")?.Value),
                new XElement("Country", legacyShipment.Element("ORIG_CNTRY")?.Value)
            ),
            new XElement("Destination",
                new XElement("City", legacyShipment.Element("DEST_CITY")?.Value),
                new XElement("Country", legacyShipment.Element("DEST_CNTRY")?.Value)
            )
        );
    }

    private string MapLegacyMode(string legacyMode)
    {
        return legacyMode?.ToUpper() switch
        {
            "A" => "AIR",
            "O" => "OCEAN",
            "R" => "ROAD",
            _ => "UNKNOWN"
        };
    }
}

Scenario 2: Multi-Format Support

Handle multiple input formats:

public class MultiFormatTransformer
{
    private readonly Dictionary<string, ITransformer> _transformers;

    public MultiFormatTransformer()
    {
        _transformers = new Dictionary<string, ITransformer>
        {
            ["XML"] = new XmlTransformer(),
            ["JSON"] = new JsonTransformer(),
            ["CSV"] = new CsvTransformer(),
            ["EDI"] = new EdiTransformer()
        };
    }

    public XDocument Transform(string input, string format)
    {
        if (!_transformers.TryGetValue(format.ToUpper(), out var transformer))
        {
            throw new NotSupportedException($"Format {format} not supported");
        }

        return transformer.Transform(input);
    }
}

Scenario 3: Incremental Transformation

Transform only changed data:

public class IncrementalTransformer
{
    private readonly IChangeTracker _changeTracker;

    public XDocument TransformIncremental(XDocument sourceXml, XDocument previousXml)
    {
        var changes = _changeTracker.DetectChanges(sourceXml, previousXml);
        
        var result = previousXml.DeepCopy();
        
        foreach (var change in changes)
        {
            ApplyChange(result, change);
        }

        return result;
    }

    private void ApplyChange(XDocument target, Change change)
    {
        switch (change.Type)
        {
            case ChangeType.Added:
                target.Root.Add(change.NewElement);
                break;
            case ChangeType.Modified:
                var existing = target.Descendants(change.ElementName).First();
                existing.ReplaceWith(change.NewElement);
                break;
            case ChangeType.Removed:
                target.Descendants(change.ElementName).First().Remove();
                break;
        }
    }
}

Performance Optimization Techniques

Parallel Transformation

Transform multiple messages in parallel:

public class ParallelTransformer
{
    private readonly XsltTransformer _transformer;
    private readonly int _maxConcurrency;

    public async Task<List<XDocument>> TransformParallelAsync(
        IEnumerable<XDocument> sourceDocuments)
    {
        var semaphore = new SemaphoreSlim(_maxConcurrency);
        var tasks = sourceDocuments.Select(async doc =>
        {
            await semaphore.WaitAsync();
            try
            {
                return await Task.Run(() => _transformer.Transform(doc));
            }
            finally
            {
                semaphore.Release();
            }
        });

        return (await Task.WhenAll(tasks)).ToList();
    }
}

Memory-Efficient Transformation

Transform large documents without loading entirely:

public class StreamingTransformer
{
    public async Task TransformLargeDocumentAsync(
        Stream sourceStream,
        Stream targetStream,
        XslCompiledTransform transform)
    {
        using var sourceReader = XmlReader.Create(
            sourceStream,
            new XmlReaderSettings
            {
                Async = true,
                IgnoreWhitespace = true
            });

        using var targetWriter = XmlWriter.Create(
            targetStream,
            new XmlWriterSettings
            {
                Async = true,
                Indent = true
            });

        await Task.Run(() => transform.Transform(sourceReader, targetWriter));
    }
}

Transformation Caching Strategy

Cache transformation results:

public class CachedTransformer
{
    private readonly IMemoryCache _cache;
    private readonly ITransformer _transformer;

    public async Task<XDocument> TransformWithCacheAsync(
        XDocument source,
        string cacheKey)
    {
        if (_cache.TryGetValue(cacheKey, out XDocument cached))
        {
            return cached;
        }

        var transformed = await _transformer.TransformAsync(source);
        
        _cache.Set(cacheKey, transformed, TimeSpan.FromHours(1));
        
        return transformed;
    }

    private string GenerateCacheKey(XDocument source)
    {
        // Generate key from document hash
        var hash = ComputeHash(source);
        return $"transform:{hash}";
    }
}

Troubleshooting Transformation Issues

Common XSLT Errors

Error 1: Template Not Found

Symptoms:

  • Transformation produces empty output
  • Missing elements in result

Solution:

<!-- Add default template -->
<xsl:template match="*">
    <xsl:apply-templates/>
</xsl:template>

<!-- Add template for missing elements -->
<xsl:template match="MissingElement">
    <xsl:element name="cw:MissingElement">
        <xsl:value-of select="."/>
    </xsl:element>
</xsl:template>

Error 2: Namespace Issues

Symptoms:

  • Elements not matching
  • Transformation not applying

Solution:

<!-- Declare all namespaces -->
<xsl:stylesheet version="2.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:cw="urn:cargowise:eadapter:universalshipment"
    xmlns:src="http://example.com/source">
    
    <!-- Use namespace prefixes -->
    <xsl:template match="src:Shipment">
        <cw:Shipment>
            <xsl:value-of select="src:ShipmentNumber"/>
        </cw:Shipment>
    </xsl:template>
</xsl:stylesheet>

Error 3: Performance Issues

Symptoms:

  • Slow transformations
  • High memory usage

Solution:

// Use compiled transforms
var transform = new XslCompiledTransform();
transform.Load(xsltPath);

// Enable streaming
var settings = new XmlReaderSettings
{
    IgnoreWhitespace = true,
    IgnoreComments = true
};

// Use parallel processing for multiple documents

Extended FAQ

Q: When should I use XSLT vs C# for transformations?

A: Use XSLT when:

  • Transformation is primarily structural
  • Logic is declarative
  • Transformation rules are stable
  • You need version-controlled transformations

Use C# when:

  • Complex business logic required
  • Dynamic transformations needed
  • Integration with external services
  • Performance is critical

Q: How do I handle schema version changes?

A: Implement version-aware transformation:

public class VersionAwareTransformer
{
    public XDocument Transform(XDocument source, string targetVersion)
    {
        var sourceVersion = DetectSourceVersion(source);
        
        if (sourceVersion == targetVersion)
        {
            return source; // No transformation needed
        }

        // Transform through intermediate versions if needed
        var current = source;
        foreach (var version in GetVersionPath(sourceVersion, targetVersion))
        {
            current = TransformToVersion(current, version);
        }

        return current;
    }
}

Q: How do I test XSLT transformations?

A: Use comprehensive test data:

[Theory]
[InlineData("TestData/SimpleShipment.xml")]
[InlineData("TestData/ComplexShipment.xml")]
[InlineData("TestData/MultiItemShipment.xml")]
public void ShouldTransformShipmentCorrectly(string testFile)
{
    var source = XDocument.Load(testFile);
    var transformer = new XsltTransformer("Transforms/Shipment.xslt");
    var result = transformer.Transform(source);

    // Validate result
    Assert.NotNull(result.Descendants("ShipmentId").FirstOrDefault());
    ValidateSchema(result);
}

Q: How do I handle transformation errors gracefully?

A: Implement error handling:

public class ErrorHandlingTransformer
{
    public TransformationResult TransformSafely(XDocument source)
    {
        try
        {
            var result = Transform(source);
            return new TransformationResult
            {
                Success = true,
                Result = result
            };
        }
        catch (XsltException ex)
        {
            _logger.LogError(ex, "XSLT transformation failed");
            return new TransformationResult
            {
                Success = false,
                Error = ex.Message,
                ErrorDetails = ex.InnerException?.Message
            };
        }
    }
}

Case Studies

Case Study 1: High-Volume Transformation

Challenge: A logistics company needed to transform 10,000+ shipment messages per hour from legacy system to CargoWise format.

Solution: Implemented parallel transformation with caching:

public class HighVolumeTransformer
{
    private readonly ParallelTransformer _parallelTransformer;
    private readonly CachedTransformer _cachedTransformer;

    public async Task<List<XDocument>> TransformBatchAsync(
        List<XDocument> sourceDocuments)
    {
        // Group by transformation type for caching
        var grouped = sourceDocuments.GroupBy(d => GetTransformationType(d));
        
        var results = new List<XDocument>();
        
        foreach (var group in grouped)
        {
            var transformed = await _parallelTransformer.TransformParallelAsync(
                group.Select(doc => _cachedTransformer.TransformWithCacheAsync(doc)));
            
            results.AddRange(transformed);
        }

        return results;
    }
}

Results:

  • 95% reduction in transformation time
  • 80% reduction in CPU usage
  • Zero transformation errors

Case Study 2: Complex Business Rules

Challenge: An e-commerce platform needed to transform orders with complex pricing and tax calculations.

Solution: Used hybrid approach with C# for business logic:

public class OrderTransformer
{
    public XDocument TransformOrder(Order order)
    {
        // Use XSLT for structural transformation
        var structural = _xsltTransformer.Transform(order.ToXml());
        
        // Use C# for business logic
        var withPricing = CalculatePricing(structural);
        var withTax = CalculateTax(withPricing);
        
        return withTax;
    }

    private XDocument CalculatePricing(XDocument order)
    {
        var items = order.Descendants("Item");
        foreach (var item in items)
        {
            var basePrice = decimal.Parse(item.Element("BasePrice").Value);
            var discount = CalculateDiscount(item);
            var finalPrice = basePrice - discount;
            
            item.Element("FinalPrice").Value = finalPrice.ToString("F2");
        }

        return order;
    }
}

Migration Guide

Migrating from Manual Transformation

Step 1: Document current transformation logic:

// Document existing transformation
public class TransformationDocumentation
{
    public void DocumentTransformation(string sourceFormat, string targetFormat)
    {
        var mapping = new Dictionary<string, string>
        {
            ["SourceField1"] = "TargetField1",
            ["SourceField2"] = "TargetField2"
        };

        // Save to configuration
        SaveMappingConfiguration(mapping);
    }
}

Step 2: Create XSLT templates:

<!-- Create XSLT from documented mappings -->
<xsl:template match="SourceElement1">
    <TargetElement1>
        <xsl:value-of select="."/>
    </TargetElement1>
</xsl:template>

Step 3: Test and validate:

// Test transformation
var testData = LoadTestData();
var result = Transform(testData);
ValidateResult(result);

Best Practices Summary

  1. Use XSLT for Structure: Structural transformations in XSLT
  2. Use C# for Logic: Business logic in C#
  3. Validate Always: Schema validation before and after
  4. Cache Transforms: Cache compiled XSLT transforms
  5. Test Comprehensively: Unit and integration tests
  6. Monitor Performance: Track transformation metrics
  7. Handle Errors: Graceful error handling
  8. Document Mappings: Maintain mapping documentation
  9. Version Control: Version XSLT files
  10. Optimize: Profile and optimize slow transformations

Conclusion

Mastering CargoWise eAdapter message transformation is essential for building reliable integrations. By combining XSLT for structural transformations with C# for business logic, implementing proper schema validation, and following best practices, you can build robust transformation pipelines that handle complex data mapping scenarios.

Key Takeaways:

  1. Use XSLT for structural transformations - Declarative and maintainable
  2. Use C# for business logic - More flexible for complex rules
  3. Validate schemas - Ensure data quality before processing
  4. Cache compiled transforms - Optimize performance
  5. Test thoroughly - Unit and integration tests
  6. Monitor performance - Track transformation metrics
  7. Handle errors gracefully - Comprehensive error handling
  8. Optimize for scale - Parallel and streaming transformations
  9. Document mappings - Maintain transformation documentation
  10. Version transformations - Handle schema evolution

Next Steps:

  1. Review your transformation requirements
  2. Choose XSLT, C#, or hybrid approach
  3. Implement schema validation
  4. Set up testing framework
  5. Monitor transformation performance
  6. Optimize based on production data

For more CargoWise integration guidance, explore our CargoWise eAdapter Integration Patterns guide or Error Handling & Retry Patterns.

Related posts