1

I'm trying to use XSLT to update an XML document that I have. It needs to make multiple changes to the document to convert it to some other specifications.

What I want to do is update the same node more than once. Another way to look at it is I'd like to make multiple passes at the document, and each pass could update the same node.

All of my googling hints at using the 'mode' attribute of xsl:template, but all of the examples of the 'mode' attribute involve outputting the node twice - like in the case of using a heading in the table of contents and using it again as the title of a chapter. I don't want to output the node twice, I want to update it twice.

My sample XSLT:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version = '2.0'
     xmlns:xsl='http://www.w3.org/1999/XSL/Transform'>

  <!-- identity transform (copy over the document) -->
  <xsl:template match="@*|node()">
    <xsl:copy>
      <xsl:apply-templates select="@*|node()" />
    </xsl:copy>
  </xsl:template>

  <!-- wrap all BBB nodes in parenthesis -->
  <xsl:template match="BBB" >
    <xsl:copy>
        (<xsl:apply-templates select="@*|node()" />)
    </xsl:copy>
  </xsl:template>

  <!-- any BBB that has a CCC should get an attribute indicating so -->
  <xsl:template match="BBB[CCC]">
    <xsl:copy>
        <xsl:attribute name="hasC">true</xsl:attribute>
        <xsl:apply-templates select="@*|node()" />
    </xsl:copy>
  </xsl:template>


</xsl:stylesheet> 

My sample xml document:

<?xml version="1.0" encoding="UTF-8"?>
<source>

<AAA>
     <BBB>Bee bee bee</BBB>
     <BBB>Bee two bee two bee two</BBB>
</AAA>
<AAA>
     <BBB/>
     <CCC>
          <DDD/>
     </CCC>
     <BBB>
          <CCC/>
     </BBB>
</AAA>

</source> 

The current output:

<?xml version="1.0" encoding="UTF-8"?><source>

<AAA>
     <BBB>
        (Bee bee bee)
    </BBB>
     <BBB>
        (Bee two bee two bee two)
    </BBB>
</AAA>
<AAA>
     <BBB>
        ()
    </BBB>
     <CCC>
          <DDD/>
     </CCC>
     <BBB hasC="true">
          <CCC/>
     </BBB>
</AAA>

</source>

As you can see, the BBB node that has the 'CCC' only got updated once, with the second template - the first template didn't modify it. Ideally, I want the output to be

<BBB hasC="true">
  (<CCC/>)
</BBB>

Is this possible? I know I could accomplish this by combining the two templates together, but I really want to keep them separate for readability and maintinence - the two templates come from different requirements, and keeping them separate is ideal.

I suppose I could have two XSLT files and chain them together by calling the XSLT processor twice, but I'm hoping I don't need to that.

EDIT: Based on a lot of the comments I'm getting I think I am misunderstanding something fundamental about XSLT and transformations.

The problem I have is: I have a bunch of XML documents that conform to a spec. This specification has been updated by another group of people, so I need to create a batch/scriptable process to be able to update a large number of these XML documents so that they match this new spec.

Some of these new updates are things like the following.

1) <string lang="FR">hello</string> needs to change to <string lang="fra">hello</string>

2) <a><b><string>Color</string><b></a> needs to change to <a><b><string>Colour</string></b></a> , but don't touch <a><c><string>Color</string></c></a>

3) all <orgName><string>***anytext***</string></orgName> need to change to <orgName><string>My Company: ***anytext***</string></orgName>

4) all <orgName><string></string></orgName> nodes need to change to <organisationName><string></string></orgisationName>

And so on. There are a lot of specific updates that could apply to the same nodes multiple times and there are enough of these updates that making multiple passes to the files seems like a better idea then creating one catch-all template that merges all of these updates into one large template.

Should I be using XSLT to try to do this? Is this a case where I need to break these changes up into multiple files and chain them in a script? Could I do all of these updates in a single .xslt file?

Jimmy James
  • 80
  • 1
  • 8
  • 1
    If you want all content in `BBB` nodes to be wrapped in parenthesis, why not just wrapping the `` in the `` like in the template maching `BBB`? Then the result is `()`. – matthias_h Jan 09 '15 at 20:38
  • The reason I don't want to combine them is there are quite a few of these templates that might modify the same node. Trying to combine all of them could get quite complicated. Another reason is these templates match specific rules coming from a different group. In this case, you can image "To comply with Standard 1.2.3c you need to wrap BBB elements in parenthesis" and "To comply with Standard 1.5.2a you need to add the hasC attribute to BBB nodes". Keeping the templates separate will make readibility/maintenance of this file much easier. – Jimmy James Jan 09 '15 at 20:48
  • IMHO, chaining two transformations will be *more* complicated. – michael.hor257k Jan 09 '15 at 20:54
  • Am I trying to do something that isn't possible with XSLT? Can XSLT update the same node twice? – Jimmy James Jan 09 '15 at 21:09
  • 2
    XSLT does never update a node, it creates result nodes or temporary nodes if you use several transformation steps. As for applying two templates, XSLT 2.0 has `xsl:next-match` but it is difficult to apply that to your case as both templates do a shallow copy. – Martin Honnen Jan 09 '15 at 21:13
  • 1
    "*Can XSLT update the same node twice?*" See: http://stackoverflow.com/a/27848382/3016153 – michael.hor257k Jan 09 '15 at 21:17
  • 1
    It's not that you're trying to do something impossible in XSLT. It's that your model of how XSLT works is incorrect and is leading you to frame your question in terms of "updating the same node twice" rather than in terms of transforming input XML to output XML. Backup, explain your actual goal rather than a mistaken approach, and avoid this [***XY problem***](http://meta.stackexchange.com/questions/66377/what-is-the-xy-problem). – kjhughes Jan 09 '15 at 21:26
  • Ok, I've added some more information to my question - basically described what I want to do rather than what I've come up with so far. – Jimmy James Jan 09 '15 at 21:45

1 Answers1

3

Just as example for your updated example input: Following XSLT

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
 <xsl:output method="xml" doctype-public="XSLT-compat" 
      encoding="UTF-8" indent="yes" />
 <xsl:strip-space elements="*" />
  <!-- identity transform (copy over the document) -->
  <xsl:template match="@*|node()">
    <xsl:copy>
      <xsl:apply-templates select="@*|node()" />
    </xsl:copy>
  </xsl:template>
  <!-- wrap all BBB nodes in parenthesis -->
  <xsl:template match="BBB">
    <xsl:copy>
       <xsl:if test="CCC">
          <xsl:attribute name="hasC">true</xsl:attribute>
       </xsl:if>
       (<xsl:apply-templates select="@*|node()" />)
    </xsl:copy>
  </xsl:template>
  <xsl:template match="orgName[string]">
    <organisationName>
      <xsl:apply-templates />
    </organisationName>
  </xsl:template>
  <xsl:template match="a/b/string[text()='Color']">
    <xsl:text>Colour</xsl:text>
  </xsl:template>
  <xsl:template match="orgName/string/text()">
    <xsl:text>Company:</xsl:text>
    <xsl:value-of select="." />
  </xsl:template>
  <xsl:template match="string[@lang='FR']">
    <xsl:copy>
      <xsl:attribute name="lang">fra</xsl:attribute>
      <xsl:apply-templates />
    </xsl:copy>
  </xsl:template>
</xsl:stylesheet>

when applied to the example input XML

<?xml version="1.0" encoding="UTF-8"?>
<source>
<AAA>
  <BBB>Bee bee bee</BBB>
  <BBB>Bee two bee two bee two</BBB>
    <test>
      <string lang="FR">hello</string>
    </test>
    <a>
       <b>
         <string>Color</string>
       </b>
    </a>
    <a>
       <c>
         <string>Color</string>
       </c>
    </a>
    <orgName>
       <string>***anytext***</string>
    </orgName>
    <orgName>
       <notstring>***anytext***</notstring>
    </orgName>
  </AAA>
  <AAA>
      <BBB />
      <CCC>
        <DDD />
      </CCC>
    <BBB>
      <CCC />
    </BBB>
  </AAA>
</source>

produces the following output:

<?xml version="1.0" encoding="UTF-8"?>
<source>
  <AAA>
    <BBB>(Bee bee bee)</BBB>
    <BBB>(Bee two bee two bee two)</BBB>
    <test>
      <string lang="fra">hello</string>
    </test>
    <a>
      <b>Colour</b>
    </a>
    <a>
      <c>
        <string>Color</string>
      </c>
    </a>
    <organisationName>
      <string>Company:***anytext***</string>
    </organisationName>
    <orgName>
     <notstring>***anytext***</notstring>
  </orgName>
  </AAA>
  <AAA>
    <BBB>()</BBB>
    <CCC>
      <DDD />
    </CCC>
    <BBB hasC="true">
     (<CCC />)
    </BBB>
  </AAA>
</source>

You already mentioned that the 4 added requirements in your update are only an example for a list of many changes that should be applied to the input XML, so this is only an example of how various changes on the same nodes can be handled - e.g.:

<xsl:template match="orgName[string]">

matches a node orgName that contains a node string. By

<organisationName>
  <xsl:apply-templates />
</organisationName>

this template renames orgName to organisationName.

A second template matches the text() of a string node in an orgName node:

<xsl:template match="orgName/string/text()">

and adds Company: in front of this text:

<xsl:text>Company:</xsl:text>
  <xsl:value-of select="." />

Result of both templates:

<organisationName>
   <string>Company:***anytext***</string>
</organisationName>

The orgName of this from the example input

<orgName><notstring>***anytext***</notstring></orgName>

as well as the text remains unchanged as orgName doesn't contain string.

I've also removed your template matching BBB[CCC] to set the hasC attribute. Instead, I've added

<xsl:if test="CCC">
  <xsl:attribute name="hasC">true</xsl:attribute>
</xsl:if>

to the template matching BBB.

I won't recommend using XSLT for applying all of your required changes as I don't know all specific changes that have to be applied - it really depends on the input and the full specification of the required changes. I just wanted to provide an example for how the changes you mentioned in your question could be handled with a single XSLT.

matthias_h
  • 11,356
  • 9
  • 22
  • 40
  • I would have thought that the four additional rules I added would have run into the same problem as my BBB/CCC example, but you handled each rule with its own template. This makes me think I probably can handle most of the transformations with a single template, and on the rare occasions where I can't (BBB/CCC) I can just comment the hell out of it. Thanks for the examples, they'll definitely help me out! – Jimmy James Jan 09 '15 at 23:11