Travis P
Travis P

Reputation: 178

Expand XML depth with XSLT transformation

I'm trying to convert an xml that has a root element and one level deep of child elements. These elements can have the same attribute name. What I am looking for is a way to transform the xml in such a way that the related attributes are nested correctly.

The xml is generated by an HTML form submission (I have control over the form field names).

The resulting XML is generated:

Input.xml

<root>
<project_id>1</project_id>
<project_name>Project 1</project_name>
<project_id>2</project_id>
<project_name>Project 2</project_name>
<project_id>3</project_id>
<project_name>Project 3</project_name>
</root>

Desired output

  <root>
    <project>
      <id>1</id>
      <name>Project 1</name>
    </project>

    <project>
      <id>2</id>
      <name>Project 2</name>
    </project>

    <project>
      <id>3</id>
      <name>Project 3</name>
    </project>
    <root>

My Attempt

Note: I prepended 'r_' to the repeated attributes. <r_project_id> 2</r_project_id>

<xsl:template match="/">
    <root>
        <xsl:apply-templates/>
    </root>

</xsl:template>

<xsl:template match="node()|@*">
    <project>
        <xsl:apply-templates select="*[matches(name(), '^project_')]"/>
    </project>
    <project>
        <xsl:apply-templates select="*[matches(name(), '^r_project')]"/>
    </project>
</xsl:template>

<xsl:template match="*[matches(name(), '^r_project_')]">
    <xsl:apply-templates select="*[matches(name(), '^r_project_')]"/>
    <xsl:copy-of select="*"/>

</xsl:template>


<xsl:template match="*[matches(name(), '^project_')]">
    <xsl:element name="{replace(name(), '^project_', '')}">
        <xsl:copy-of select="*"/>
    </xsl:element>
</xsl:template>

<xsl:template match="*[matches(name(), '^r_project_')]">
    <xsl:element name="{replace(name(), '^r_project_', '')}">
        <xsl:copy-of select="*"/>
    </xsl:element>
</xsl:template>

Output.xml

  <root>
      <project>
        <id></id>
        <name></name>
      </project>
      <project>
        <id></id>
        <name></name>
        <id></id>
        <name></name>
      </project>
    </root>

Is there a simpler method to creating unique XML elements without having to create a extremely verbose xslt transformation that captures all possible repeated elements?

Upvotes: 1

Views: 127

Answers (2)

Michael Kay
Michael Kay

Reputation: 163549

The solution from michael.hor257k looks fine, but a slightly more idiomatic and flexible solution in XSLT 2.0 might be

<xsl:template match="/">
<root>
    <xsl:for-each-group select="root/*" group-starting-with="project_id">
        <project>
            <xsl:apply-templates select="current-group()" mode="rename"/>
        </project>
    </xsl:for-each>
</root>
</xsl:template>

<xsl:template match="*" mode="rename">
  <xsl:element name="{substring-after(name(), 'project_')}">
    <xsl:value-of select="."/>
  </xsl:element>
</xsl:template>

Upvotes: 2

michael.hor257k
michael.hor257k

Reputation: 117102

I can't follow the logic of your XSLT. Is there a reason why this couldn't be simply:

<xsl:template match="/">
<root>
    <xsl:for-each select="root/project_id">
        <project>
            <id><xsl:value-of select="."/></id>
            <name><xsl:value-of select="following-sibling::project_name"/></name>
        </project>
    </xsl:for-each>
</root>
</xsl:template>
</xsl:stylesheet>

Upvotes: 1

Related Questions