2
votes

I have a quite flat XML file for which I want to change some attributes, depending on an input list of some kind. The tricky part which I'm not able to solve is that I also want to change the attribute not only on one node but also on a sibling node that references the found node.

I have the following XML file:

<?xml version="1.0" encoding="utf-8"?>
<Root>
    <SubItem id="id21" cls="classA" name="FirstSub" master="#id30"/>
    <SubItem id="id22" cls="classA" name="SecondSub" master="#id31"/>
    <SubItem id="id23" cls="classA" name="ThirdSub" master="#id32"/>
    <SubItem id="id24" cls="classA" name="FourthSub" master="#id33"/>
    <SubItem id="id25" cls="classA" name="FifthSub" master="#id34"/>
    <Item id="id30" cls="classA" name="First"/>
    <Item id="id31" cls="classA" name="Second"/>
    <Item id="id32" cls="classA" name="Third"/>
    <Item id="id33" cls="classA" name="Fourth"/>
    <Item id="id34" cls="classA" name="Fifth" />
</Root>

And the following additional input file listing the name values of elements where I want to change the cls attribute value:

<input>
    <ToClassB>
        <Name>Second</Name>
        <Name>Third</Name>
    </ToClassB>
</input>

And the following XSLT, which works fine for elements with name values matching those in the additional input list:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    xmlns:plm="http://www.plmxml.org/Schemas/PLMXMLSchema" exclude-result-prefixes="xs"
    version="2.0">
    <xsl:output method="xml" version="1.0" encoding="UTF-8" indent="no"/>

    <xsl:variable name="inputs" select="document('file:/C:/Temp//demoInput.xml')"/>

    <xsl:template match="@* | node()">
        <xsl:copy>
            <xsl:apply-templates select="@* | node()"/>
        </xsl:copy>
    </xsl:template>

    <xsl:template match="/Root/Item/@name[. = $inputs/input/ToClassB/Name/text()]">
        <xsl:variable name="elemName" select="../@id"/>
        <xsl:copy>
            <xsl:copy-of select="../@name"/>
        </xsl:copy>
        <xsl:attribute name="cls">
            <xsl:value-of select="'classB'" />
        </xsl:attribute>

    </xsl:template>
</xsl:stylesheet>

My expected result:

<?xml version="1.0" encoding="UTF-8"?>
<Root>
    <SubItem id="id21" cls="classA" name="FirstSub" master="#id30"/>
    <SubItem id="id22" cls="ClassB" name="SecondSub" master="#id31"/>
    <SubItem id="id23" cls="ClassB" name="ThirdSub" master="#id32"/>
    <SubItem id="id24" cls="classA" name="FourthSub" master="#id33"/>
    <SubItem id="id25" cls="classA" name="FifthSub" master="#id34"/>
    <Item id="id30" cls="classA" name="First"/>
    <Item id="id31" cls="ClassB" name="Second"/>
    <Item id="id32" cls="ClassB" name="Third"/>
    <Item id="id33" cls="classA" name="Fourth"/>
    <Item id="id34" cls="classA" name="Fifth"/>
</Root>

Item tags with name values of "Second" or "Third" are matched, and their cls attribute values are changed to ClassB, as well as the cls attribute values for related SubItem elements (master attribute referencing id attribute on Item elements).

Is this possible at all with XSLT or do I need to use some programming?

1
Can you rely on every <SubItem> being associated with an <Item> in the same document, via SubItem/@master and Item/@id? Also, do you need to preserve the element order?John Bollinger
The point of my line of questioning is that you can conceivably restructure your stylesheet so that the template the template for <Root> does not directly apply templates to the <SubItem> children, but instead relies on the template(s) for <Item> to apply templates to the associated <SubItem>s. You'll then have enough context to correctly direct how their cls attributes should be transformed.John Bollinger
XSLT is programming ;-)Daniel Haley

1 Answers

0
votes

You could create an xsl:key to reference the Item from the SubItem. You could use a single template and use either the id or master attributes to access the key.

Example...

XSLT 2.0

<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output indent="yes"/>
  <xsl:strip-space elements="*"/>

  <xsl:key name="itemsByID" match="Item" use="@id"/>

  <xsl:variable name="inputs" 
    select="document('demoInput.xml')/input/ToClassB/Name"/>

  <xsl:template match="@*|node()">
    <xsl:copy>
      <xsl:apply-templates select="@*|node()"/>
    </xsl:copy>
  </xsl:template>

  <xsl:template match="Item|SubItem">
    <xsl:variable name="refid" 
      select="if (@master) then substring-after(@master,'#') else @id"/>
    <xsl:copy>
      <xsl:apply-templates select="@*"/>
      <xsl:attribute name="cls" 
        select="if (key('itemsByID',$refid)/@name = $inputs) 
        then 'ClassB' else @cls"/>
      <xsl:apply-templates select="node()"/>
    </xsl:copy>
  </xsl:template>

</xsl:stylesheet>

Output

<Root>
   <SubItem id="id21" cls="classA" name="FirstSub" master="#id30"/>
   <SubItem id="id22" cls="ClassB" name="SecondSub" master="#id31"/>
   <SubItem id="id23" cls="ClassB" name="ThirdSub" master="#id32"/>
   <SubItem id="id24" cls="classA" name="FourthSub" master="#id33"/>
   <SubItem id="id25" cls="classA" name="FifthSub" master="#id34"/>
   <Item id="id30" cls="classA" name="First"/>
   <Item id="id31" cls="ClassB" name="Second"/>
   <Item id="id32" cls="ClassB" name="Third"/>
   <Item id="id33" cls="classA" name="Fourth"/>
   <Item id="id34" cls="classA" name="Fifth"/>
</Root>