0

I have an XML file with multiple Shape elements each with a child Material element that contains a Code attribute. I want to update the Code attribute for each Material element based on a value that is obtained from a separate XML file. The problem I have is that the update source file also have multiple elements.

Here is the main XML file - main.xml:

<?xml version="1.0"?>
<!--Top-->
<Top>
  <Shapes>
    <!--Shape-->
    <Shape Code="Penisola" Description="Rectangle">
      <!--Material-->
      <Material Code="MAT01000257" ></Material>
    </Shape>
    <!--Shape-->
    <Shape Code="Penisola" Description="Rectangle">
      <!--Material-->
      <Material Code="MAT01000258" ></Material>
    </Shape>
  </Shapes>
  <Texts></Texts>
</Top>

...and my update source file - updatesource.xml:

<?xml version="1.0"?>
<Job>
  <Job_Number>B90512</Job_Number>
  <Job_Address>2nd Floor/ 28-32 Albert Road   VIC 3205</Job_Address>
  <Benchtops>
    <Benchtop>
      <Material>Material 1</Material>
    </Benchtop>
    <Benchtop>
      <Material>Material 2</Material>
    </Benchtop>
  </Benchtops>
</Job>

The desired outcome is - output.xml:

<?xml version="1.0"?>
<!--Top-->
<Top>
  <Shapes>
    <!--Shape-->
    <Shape Code="Penisola" Description="Rectangle">
      <!--Material-->
      <Material Code="Material 1" ></Material>
    </Shape>
    <!--Shape-->
    <Shape Code="Penisola" Description="Rectangle">
      <!--Material-->
      <Material Code="Material 2" ></Material>
    </Shape>
  </Shapes>
  <Texts></Texts>
</Top>

I imagine I need to iterate through both the main XML and the update source XML, but I am unsure how to do this. My attempts so far have only succeeded in updating both Code attributes with the value from the first Material element in the update source file.

Some extra information; both the main.xml file and the updatesource.xml file will have matching number of Shape and Benchtop elements respectively, however, the number of each will change with each job I process, so the solution will need to dynamically work through the number of elements present.

paularmy42
  • 39
  • 7
  • you can try using xpath. see this http://stackoverflow.com/questions/340787/parsing-xml-with-xpath-in-java – Abi Jun 15 '15 at 07:45

2 Answers2

1

An XSL transformation that meets your requirement may be like the following:

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

    <!-- the update source file: -->
    <xsl:param name="usource" select="'updatesource.xml'"/>

    <xsl:template match="Material">
        <!-- Material's position: -->
        <xsl:variable name="pos">
            <xsl:value-of select="count(preceding::Material)+1"/>
        </xsl:variable>

        <xsl:copy>
            <xsl:apply-templates select="@*"/>

            <!-- update Code-attribute according to pos-th Material in $usource --> 
            <xsl:attribute name="Code">
                <xsl:value-of select="(document($usource)//Material)[position()=$pos]"/>
            </xsl:attribute>
            <xsl:apply-templates/>
        </xsl:copy>
    </xsl:template>

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

The main points are to check at which position Material is located in the source. And then pick the corresponding one from updatesoure.xml.

leu
  • 2,051
  • 2
  • 12
  • 25
0

How about:

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:param name="update-path" select="'updatesource.xml'" />

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

<xsl:template match="Shape">
    <xsl:variable name="i" select="position()" />
    <xsl:copy>
        <xsl:copy-of select="@*"/>
        <xsl:copy-of select="document($update-path)/Job/Benchtops/Benchtop[$i]/Material"/>
    </xsl:copy>
</xsl:template>

</xsl:stylesheet>

however this will not update the Code attribute, it will replace the existing Material element with the new one. I need to keep the existing element and all other attributes within.

Then I would suggest you do:

<xsl:template match="Material/@Code">
    <xsl:variable name="i" select="count(../../preceding-sibling::Shape) + 1" />
    <xsl:attribute name="Code">
        <xsl:value-of select="document($update-path)/Job/Benchtops/Benchtop[$i]/Material"/>
    </xsl:attribute>
</xsl:template>

Note: I don't particularly like having to count preceding siblings instead of using the position() function, but the alternative in XSLT 1.0 is rather awkward, so unless you have a huge amount of records, you might as well live with it.

michael.hor257k
  • 113,275
  • 6
  • 33
  • 51
  • Thanks for the answer Michael, however this will not update the `Code` attribute, it will replace the existing `Material` element with the new one. I need to keep the existing element and all other attributes within. – paularmy42 Jun 15 '15 at 09:46
  • @paularmy42 I see. Are you using XSLT 1.0 or 2.0? – michael.hor257k Jun 15 '15 at 09:51