Search code examples
xmlxsltxsdxslt-1.0xslt-2.0

Using XSLT to copy all nodes in XML, while transforming nodes with a specific sibling value


I have an xml file that I need to mostly copy 1 for 1 except for 1 node with a specific value that I need to transform. My XML looks like this:

<?xml version='1.0' encoding='UTF-8'?>
<xyz:Workers xmlns:xyz="urn:com.website/xyz">
    <xyz:Worker>
        <xyz:Effective_Change xyz:Sequence="0">
            <xyz:Person_Identification xyz:isAdded="1">
                <xyz:Identifier>
                    <xyz:ID>MJ 12 34 56 Z</xyz:ID>
                    <xyz:IdType>A Type</xyz:IdType>
                </xyz:Identifier>
            </xyz:Person_Identification>
        </xyz:Effective_Change>
    </xyz:Worker>
    <xyz:Worker>
        <xyz:Effective_Change xyz:Sequence="0">
            <xyz:Person_Identification xyz:isUpdated="1">
                <xyz:Identifier xyz:isUpdated="1">
                    <xyz:ID>JHQ123</xyz:ID>
                    <xyz:IdType>B Type</xyz:IdType>
                </xyz:Identifier>
                <xyz:Identifier xyz:isUpdated="1">
                    <xyz:ID xyz:priorValue="555-55-5555">123-45-6789</xyz:ID>
                    <xyz:IdType>C Type</xyz:IdType>
                </xyz:Identifier>
            </xyz:Person_Identification>
        </xyz:Effective_Change>
    </xyz:Worker>
</xyz:Workers>

I need to take the value loaded in node xyz:ID

 <xyz:ID>MJ 12 34 56 Z</xyz:ID>

And strip the white space so it looks like this:

 <xyz:ID>MJ123456Z</xyz:ID>

But only when their sibling node is xyz:IdType = A Type

 <xyz:IdType>A Type</xyz:IdType>

Here is the XSLT I tried. I referenced this stack overflow (Using XSLT to copy all nodes in XML, with support for special cases) but after working on it, I'm unsure if this is the right approach.

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0" xmlns:this="urn:this-stylesheet"
   xmlns:xs="http://www.w3.org/2001/XMLSchema"
   xmlns:xyz="urn:com.website/xyz" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    exclude-result-prefixes="xs this">
    <!--Identity template, 
        provides default behavior that copies all content into the output -->
    <xsl:template match="@* | node()">
        <xsl:copy>
            <xsl:apply-templates select="@* | node()"/>
        </xsl:copy>
    </xsl:template>
   
    <xsl:template match="xyz:ID">  
        <xsl:copy>
            <xsl:value-of select="translate(node(), ' ', '')"/>
        </xsl:copy>
    </xsl:template> 
</xsl:stylesheet>

This XSLT gives me the below output:

<?xml version="1.0" encoding="UTF-8"?><xyz:Workers xmlns:xyz="urn:com.website/xyz">
    <xyz:Worker>
        <xyz:Effective_Change xyz:Sequence="0">
            <xyz:Person_Identification xyz:isAdded="1">
                <xyz:Identifier>
                    <xyz:ID>MJ123456Z</xyz:ID>
                    <xyz:IdType>A Type</xyz:IdType>
                </xyz:Identifier>
            </xyz:Person_Identification>
        </xyz:Effective_Change>
    </xyz:Worker>
    <xyz:Worker>
        <xyz:Effective_Change xyz:Sequence="0">
            <xyz:Person_Identification xyz:isUpdated="1">
                <xyz:Identifier xyz:isUpdated="1">
                    <xyz:ID>JHQ123</xyz:ID>
                    <xyz:IdType>B Type</xyz:IdType>
                </xyz:Identifier>
                <xyz:Identifier xyz:isUpdated="1">
                    <xyz:ID>123-45-6789</xyz:ID>
                    <xyz:IdType>C Type</xyz:IdType>
                </xyz:Identifier>
            </xyz:Person_Identification>
        </xyz:Effective_Change>
    </xyz:Worker>
</xyz:Workers>

Which is close to the desired output, because it did strip the white space from the desired ID:

 <xyz:ID>MJ123456Z</xyz:ID>

But, on the C Type Identifier, it is removing the data loaded in the Tag:

Before Transformation:

 
 <xyz:ID xyz:priorValue="555-55-5555">123-45-6789</xyz:ID>
 <xyz:IdType>C Type</xyz:IdType>
                 

After transformation:

 <xyz:ID>123-45-6789</xyz:ID>
 <xyz:IdType>C Type</xyz:IdType>

This is an issue as I want the rest of the file to be identical except for ID's where sibling ID type = A Type.

I believe this has to do something with the template match applying itself to all of the ID nodes. I tried doing something along this logic:

   <xsl:template match="xyz:Identifier[xyz:IdType='A Type']">  

But had no luck. Any leads on where to start or the kind of logic I should be using would be highly appreciated.


Solution

  • But only when their sibling node is xyz:ID_Type = A Type

    You can use a predicate on xyz:Identifier for this. Note though that you say xyz:ID_Type but the example has xyz:IdType.

    I'd also just match text() so you don't have to copy or apply-templates to the attributes of xyz:ID.

    Try changing your last template to (change IdType to ID_Type if needed for your actual data):

    <xsl:template match="xyz:Identifier[xyz:IdType='A Type']/xyz:ID/text()">
      <xsl:value-of select="translate(., ' ', '')"/>
    </xsl:template> 
    

    Fiddle: http://xsltfiddle.liberty-development.net/nbUWwbs