Search code examples
xmlxsltstring-matching

Adding element if attribute value does not exist


I am working with a file which looks like this:

<?xml version="1.0" encoding="UTF-8"?>
<items>
<item sort="gruun">
   <entrycl>
      <entry>gruun</entry>
      <gramt>bn</gramt></entrycl>
<translcl>
   <transl>green</transl>
</translcl>
</item>

<item>
   <entrycl placeatentry="gruun">
   <entryx>it is grün</entryx>
</entrycl>
   <translcl><transl>bla bla bla</transl>
</translcl>
</item>

<item>
   <entrycl placeatentry="blue">
   <entryx>it is blue</entryx></entrycl>
   <translcl>
      <transl>blu blu blu</transl>
</translcl>
</item>
</items>

I want to add an entrycl/entry with the value of @placeatentry, but only if there is no other item with this value. In order to facilitate checking the result I want to put an attribute (new ="yes").

The expected outcome is this:

<items>
<item sort="gruun">
   <entrycl>
      <entry>gruun</entry>
      <gramt>bn</gramt></entrycl>
<translcl>
   <transl>ble ble ble</transl>
<rem>existing item</rem>
</translcl>
</item>

<item>
   <entrycl placeatentry="gruun">
   <entryx>it is grün</entryx>
</entrycl>
   <translcl><transl>bla bla bla</transl>
</translcl>
</item>

<item>
   <entrycl new="yes">
      <entry>blue</entry>
   <entryx>it is blue</entryx></entrycl>
   <translcl>
      <transl>blu blu blu</transl>
</translcl>
</item>
</items>

Only the blue should a new entrycl/entry because there is no existing entrycl/entry with the value 'blue'.

The next step is to connect the items which did not get a new entrycl to the existing item, but I think how to do that.

I tried numerous times, benefitting from answers to other questions. Now I have this xslt file:

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

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

<xsl:template match="item[entrycl/@placeatentry and not(//entry[.=entrycl/@placeatentry])]">
    <xsl:copy>
      <entrycl>
        <entry new="yes">
          <xsl:value-of select="entrycl/@placeatentry"/>
        </entry>
        <xsl:apply-templates select="entrycl/node()"/>
      </entrycl>
      <xsl:apply-templates select="@*|node()[not(self::entrycl)]"/>
    </xsl:copy>
  <!-- </xsl:if> -->
  </xsl:template>
</xsl:stylesheet>

Yet my stylesheet places a new entry to all items:

<?xml version="1.0" encoding="UTF-8"?>
<items>
   <item sort="gruun">
      <entrycl>
         <entry>gruun</entry>
         <gramt>bn</gramt>
      </entrycl>
      <translcl>
         <transl>green</transl>
      </translcl>
   </item>
   <item>
      <entrycl>
         <entry new="yes">gruun</entry>
         <entryx>it is grün</entryx>
      </entrycl>
      <translcl>
         <transl>bla bla bla</transl>
      </translcl>
   </item>
   <item>
      <entrycl>
         <entry new="yes">blue</entry>
         <entryx>it is blue</entryx>
      </entrycl>
      <translcl>
         <transl>blu blu blu</transl>
      </translcl>
   </item>
</items>

What am I doing wrong? Am I overlooking something? Thank you in advance for your help.


Solution

  • See if this simplified example can get you on the right track:

    XML

    <items>
        <item>
            <entrycl>
                <entry>gruun</entry>
            </entrycl>
        </item>
        <item>
            <entrycl placeatentry="gruun">
            </entrycl>
        </item>
        <item>
            <entrycl placeatentry="blue">
            </entrycl>
        </item>
    </items>
    

    XSLT 1.0

    <xsl:stylesheet version="1.0" 
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
    <xsl:strip-space elements="*"/>
    
    <xsl:key name="k1" match="entrycl" use="entry" />
    
    <!-- identity transform -->
    <xsl:template match="@*|node()">
        <xsl:copy>
            <xsl:apply-templates select="@*|node()"/>
        </xsl:copy>
    </xsl:template>
    
    <xsl:template match="entrycl[@placeatentry][not(key('k1', @placeatentry))]">
        <xsl:copy>
            <xsl:attribute name="new">yes</xsl:attribute>
            <xsl:apply-templates select="@*|node()"/>
        </xsl:copy>
    </xsl:template>
    
    </xsl:stylesheet>
    

    Result

    <?xml version="1.0" encoding="UTF-8"?>
    <items>
       <item>
          <entrycl>
             <entry>gruun</entry>
          </entrycl>
       </item>
       <item>
          <entrycl placeatentry="gruun"/>
       </item>
       <item>
          <entrycl new="yes" placeatentry="blue"/>
       </item>
    </items>
    

    Note that the search for a matching value in entry includes the current entrycl.