tags:

views:

75

answers:

4

Hi Everbody,

My problem is that in some xml files an element exists and in another it does not. When the element exists, it's value should be changed. If it does not exists, it should be added.

Here is an example for better understanding:

<root>
    <group>
        <element1>SomeValue1</element1>
        <element2>SomeValue2</element2>
    </group>
</root>

Let's say I always want element1, element2 and element3 with the values Changed1, Changed2, Changed3.

It should end up like this:

<root>
    <group>
        <element1>Changed1</element1>
        <element2>Changed2</element2>
        <element3>Changed3</element3>
    </group>
</root>

What can I do to make it happen?
Thanking you in anticipation

Dennis

A: 

Your example might be oversimplified. It looks like you can just generate the 3 "changed" values for every element you encounter... so I'm guessing it's a bit more complicated than that in real life.

In any case, if the changed value is not a replacement but actually a modification of the original value, you can probably use a for-each on groups and then generate the 3 elements, where the value of each element is based on an xsl-if element that checks whether the original value exists and uses it if so.

Assaf Lavie
ok, it's oversimplified a bit :-)
Dennis
Sorry, pressed enter to early. The group element can contain various other elements, which should be untouched.
Dennis
+3  A: 

I'm not sure if it's the most elegant solution in the world, but I think this would probably do what you're after:

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

<!--  If the element exists, do what you want to do -->
<xsl:template match="element1">
    <xsl:copy>Changed1</xsl:copy>
</xsl:template>

<xsl:template match="element2">
    <xsl:copy>Changed2</xsl:copy>
</xsl:template>

<xsl:template match="element3">
    <xsl:copy>Changed3</xsl:copy>
</xsl:template>

<!--  If the element doesn't exist, add it -->
<xsl:template match="group">
    <xsl:copy>
        <xsl:apply-templates/>
        <xsl:if test="not(element1)">
            <element1>Changed1</element1>
        </xsl:if>
        <xsl:if test="not(element2)">
            <element2>Changed2</element2>
        </xsl:if>
        <xsl:if test="not(element3)">
            <element3>Changed3</element3>
        </xsl:if>
    </xsl:copy>
</xsl:template>

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

</xsl:stylesheet>

Anything which isn't explicitly matched should just copy across untouched.

Of course, if the values are constant (that is, element1, element2 and element3 will always have the same values regardless of whether they're new or updated) then you can have something simpler:

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

<!--  If the element exists, remove it -->
<xsl:template match="element1 | element2 | element3"/>

<!--  Now put in your preferred elements -->
<xsl:template match="group">
    <xsl:copy>
        <xsl:apply-templates/>
            <element1>Changed1</element1>
            <element2>Changed2</element2>
            <element3>Changed3</element3>
    </xsl:copy>
</xsl:template>

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

</xsl:stylesheet>

Which essentially removes the original "element" nodes and puts yours in in their place.

Erica
works fine for me... Thank you very much :-)
Dennis
@Erica: +1 The second is a good answer. Also, I've edited your identity rule: there is no need to split the expression in two instructions and the union terms could be exchanged because the applying would follow document order anyway.
Alejandro
@Alejandro: You might be interested to see a generic solution :)
Dimitre Novatchev
+1  A: 

Here is a more generic solution. The names of the elements can be specified separately from the code:

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

    <my:newValues>
      <element1>Changed1</element1>
      <element2>Changed2</element2>
      <element3>Changed3</element3>
    </my:newValues>

    <xsl:variable name="vElements" select="document('')/*/my:newValues/*"/>

 <xsl:template match="*" name="identity" mode="copy">
     <xsl:element name="{name()}">
       <xsl:copy-of select="namespace::*[not(name()='my' or name()='xsl')]"/>
       <xsl:apply-templates select="node()|@*"/>
     </xsl:element>
 </xsl:template>

 <xsl:template match="*">
  <xsl:if test="not($vElements[name()=name(current())])">
   <xsl:call-template name="identity"/>
  </xsl:if>
 </xsl:template>

 <xsl:template match="group">
  <xsl:copy>
   <xsl:apply-templates select="node()"/>
    <xsl:apply-templates select="$vElements" mode="copy"/>
  </xsl:copy>
 </xsl:template>
</xsl:stylesheet>

when this transformation is applied on the provided XML document:

<root>
    <group>
        <element1>SomeValue1</element1>
        <element2>SomeValue2</element2>
    </group>
</root>

the wanted, correct result is produced:

<root>
    <group>
        <element1>Changed1</element1>
        <element2>Changed2</element2>
        <element3>Changed3</element3>
    </group>
</root>

Note: The seemingly complicated processing that discards the "xsl" anf "my" namespaces is unnecessary when the to-be-modified elements are in their separate document. This code intentionally puts the to-be-modified elements in the same document as the xslt stylesheet for demonstration purposes. In practice, just an <xsl:copy-of select="$vModifiedDoc/*"> will be used.

Dimitre Novatchev
+1 Yes, this is the general solution I was thinking. It's a pain that few XSLT processor handle XML NAMES 1.1, because in that way you could reset the namespaces in the inline data in order to have a more compact code.
Alejandro
A: 

Just for fun, an XSLT 2.0 solution:

<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"&gt;
    <xsl:variable name="vAdd" as="element()*">
        <element1>Changed1</element1>
        <element2>Changed2</element2>
        <element3>Changed3</element3>
    </xsl:variable>
    <xsl:template match="node()|@*">
        <xsl:copy>
            <xsl:apply-templates select="node()|@*"/>
        </xsl:copy>
    </xsl:template>
    <xsl:template match="group/*[name()=$vAdd/name()]"/>
    <xsl:template match="group/*[last()]" priority="1">
        <xsl:next-match/>
        <xsl:apply-templates select="$vAdd"/>
    </xsl:template>
</xsl:stylesheet>

Output:

<root>
    <group>
        <element1>Changed1</element1>
        <element2>Changed2</element2>
        <element3>Changed3</element3>
    </group>
</root>
Alejandro