Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Best practice for inserting and deleting elements via XSLT

Tags:

xml

xslt

I am new to XSLT and now i have a rather complicated problem... My input file looks like

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<doc>
  <outerElement>
    <first>
      <textElement>Some Text</textElement>
    </first>
    <second>
      <textElement>Some Text</textElement>
    </second>
    <third>
      <textElement>Some Text</textElement>
    </third>
  </outerElement>
</doc>

The problem occurs with the "second" Element. In the input file it can have one of 3 forms:

MISSING

<second>
  <textElement>Some Text</textElement>
</second>

<second missingCause="" />

In the output file it should be inserted like the second form. If it was missing before the textElement should indicate it was inserted by the Template and whats important here is it has to be at the second position because i want to validate it against a xsd schema...

Here is my current XSL:

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

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

  <!-- remove Elements with attribute deleteme -->
  <xsl:template match="outerElement/second[@missingCause='*']" />

  <!-- look if second is there. If not insert -->
  <xsl:template match="outerElement">
    <xsl:copy>
      <xsl:apply-templates select="node()|@*"/>
      <xsl:if test="not(second)">
        <second>
        </second>
      </xsl:if>
    </xsl:copy>    
  </xsl:template>

  <!-- Insert Element second -->
  <xsl:template match="outerElement/second">
    <xsl:apply-templates select="node()|@*"/>
    <xsl:copy>
      <xsl:if test="not(textElement)">
        <textElement>Inserted by Template</textElement>
      </xsl:if>
    </xsl:copy>    
  </xsl:template>

</xsl:stylesheet> 

If "second" is missing my output file just gets an Element "" but its empty and the last transformation does not get applied. Everything else looks fine while when i have in the Document i get a warning...

Can someone please help me moving the Element to where it has to be so it is validates to a Schema and make it work in all three cases?

I think my way seems not very good and will end up chaotic because i got several similar Elements to insert / delete like this.

Thanks for reading ;)

like image 650
Dennis Ich Avatar asked Dec 05 '25 20:12

Dennis Ich


2 Answers

Here is a generic solution that works for any number of differently named children of outerElement and any prefered order between them:

<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
 xmlns:ext="http://exslt.org/common" exclude-result-prefixes="ext">
 <xsl:output omit-xml-declaration="yes" indent="yes"/>
 <xsl:strip-space elements="*"/>

 <xsl:param name="pUncertainElName" select="'second'"/>

 <xsl:param name="pOrderedNames" select="'|first|second|third|'"/>

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

 <xsl:template match="outerElement">
  <xsl:variable name="vrtfFirstPass">
      <xsl:copy>
       <xsl:apply-templates select="node()|@*"/>
       <xsl:apply-templates select=
         "self::*[not(*[name() = $pUncertainElName])
                or
                 *[name()=$pUncertainElName and @missing-cause]]"
         mode="missing"/>
      </xsl:copy>
  </xsl:variable>

  <xsl:apply-templates select="ext:node-set($vrtfFirstPass)/*" mode="pass2"/>
 </xsl:template>

 <xsl:template match="*[@missing-cause]"/>

 <xsl:template match="*" mode="missing">
    <xsl:element name="{$pUncertainElName}">
        <textElement>Some Text</textElement>
    </xsl:element>
 </xsl:template>

 <xsl:template match="outerElement" mode="pass2">
  <xsl:copy>
   <xsl:apply-templates>
     <xsl:sort data-type="number" select=
     "string-length(substring-before($pOrderedNames,
                                     concat('|', name(), '|')
                                     )
                   )"/>
   </xsl:apply-templates>
  </xsl:copy>
 </xsl:template>
</xsl:stylesheet>

When this transformation is applied on the following XML document:

<doc>
    <outerElement>
        <first>
            <textElement>Some Text</textElement>
        </first>
        <second missing-cause="">
            <textElement>Some Text</textElement>
        </second>
        <third>
            <textElement>Some Text</textElement>
        </third>
    </outerElement>
</doc>

the wanted, correct result is produced:

<doc>
   <outerElement>
      <first>
         <textElement>Some Text</textElement>
      </first>
      <second>
         <textElement>Some Text</textElement>
      </second>
      <third>
         <textElement>Some Text</textElement>
      </third>
   </outerElement>
</doc>

When it is applied on this XML document:

<doc>
    <outerElement>
        <first>
            <textElement>Some Text</textElement>
        </first>
        <third>
            <textElement>Some Text</textElement>
        </third>
    </outerElement>
</doc>

again the wanted, correct result is produced:

<doc>
   <outerElement>
      <first>
         <textElement>Some Text</textElement>
      </first>
      <second>
         <textElement>Some Text</textElement>
      </second>
      <third>
         <textElement>Some Text</textElement>
      </third>
   </outerElement>
</doc>

Finally, when the same transformation is applied on this XML document:

<doc>
    <outerElement>
        <first>
            <textElement>Some Text</textElement>
        </first>
        <second>
            <textElement>Some Text</textElement>
        </second>
        <third>
            <textElement>Some Text</textElement>
        </third>
    </outerElement>
</doc>

again the same wanted, correct result is produced:

<doc>
   <outerElement>
      <first>
         <textElement>Some Text</textElement>
      </first>
      <second>
         <textElement>Some Text</textElement>
      </second>
      <third>
         <textElement>Some Text</textElement>
      </third>
   </outerElement>
</doc>

Do Note:

There can be any, unlimited number of differently named children of outerElement (not just three), and their order may not be known in advance.

For example:

Given this XML document:

<doc>
    <outerElement>
        <first>
            <textElement>Some Text</textElement>
        </first>
        <second missing-cause="">
            <textElement>Some Text</textElement>
        </second>
        <third>
            <textElement>Some Text</textElement>
        </third>
        <fourth>
            <textElement>Some Text</textElement>
        </fourth>
    </outerElement>
</doc>

and this order: forth, second, third, first

We only have to replace:

 <xsl:param name="pOrderedNames" select="'|first|second|third|'"/>

with:

 <xsl:param name="pOrderedNames" select="'|fourth|second|third|first|'"/>

And now the new wanted result is produced:

<doc>
   <outerElement>
      <fourth>
         <textElement>Some Text</textElement>
      </fourth>
      <second>
         <textElement>Some Text</textElement>
      </second>
      <third>
         <textElement>Some Text</textElement>
      </third>
      <first>
         <textElement>Some Text</textElement>
      </first>
   </outerElement>
</doc>

Explanation:

  1. This is a two-pass transformation.

  2. The name of the element that may or maynot be present is specified in the external/global parameter $pUncertainElName. For convenience in this explanation we refer to this element as second.

  3. On the first pass, all children of outerElement with the exception of second that has the missing-cause attribute, are copied "as-is". If the second element was absent or had the missing-cause attribute, we output a new child of outerElement -- the exactly wanted second element.

  4. In the second pass we sort the children of the outerElement produced in the first pass, according to their priority, as specified in another external/global parameter, named $pOrderedNames (a name that is left of another name in this string, has higher priority)

like image 137
Dimitre Novatchev Avatar answered Dec 08 '25 13:12

Dimitre Novatchev


I think you are misunderstanding how templates work. For what you need to accomplish, it seems that just one template is required (plus the identity template). Try and feedback:

<xsl:template match="outerElement[not(second) or second[@missingCause='']]">
    <xsl:apply-templates select="@*|first"/>
    <second>
        <textElement>Inserted by Template</textElement>
    </second>
    <xsl:apply-templates select="third"/>
</xsl:template>
like image 21
Emiliano Poggi Avatar answered Dec 08 '25 12:12

Emiliano Poggi