Question

I have a scenario where I need to convert:

<Hand id="left">
    <FingerOne>Thumb</FingerOne>
    <FingerTwo>Pointer</FingerTwo>
    <FingerThree>Middle</FingerThree>
</Hand>

To:

<Hand id="left" F1="Thumb" F2="Pointer" F3="Middle" />

I've been using this piece of XSLT to convert the nested entity tags into attributes, and that works great. I'm not sure how to change the names "FingerOne" to 1, "FingerTwo" to 2, etc. though while moving the elements into attributes.

<xsl:for-each select="*">
    <xsl:attribute name="{name()}">
        <xsl:value-of select="text()"/>
    </xsl:attribute>
</xsl:for-each>

I found this answer https://stackoverflow.com/a/8274527/857994 which shows how to use what is basically a map to do the conversion. I can't seem to make it work in my document though.

Note, I'm trying to make this work in Java's build in XSLT functionality using JAXP. It doesn't seem to support many XSLT2.0 features, so if you can stick with XSLT1.0 I'd be greatly appreciative.

Was it helpful?

Solution

Here's an option that is self-contained and doesn't require any outside XML files.

When this XSLT:

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

  <my:attributeNames>
    <name original="FingerOne" new="F1"/>
    <name original="FingerTwo" new="F2"/>
    <name original="FingerThree" new="F3"/>
  </my:attributeNames>

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

  <xsl:template match="Hand/*">
    <xsl:attribute
      name="{document('')/*/my:attributeNames/*
                            [@original = name(current())]/@new}">
      <xsl:value-of select="."/>
    </xsl:attribute>
  </xsl:template>
</xsl:stylesheet>

...is applied against the provided XML:

<Hand id="left">
  <FingerOne>Thumb</FingerOne>
  <FingerTwo>Pointer</FingerTwo>
  <FingerThree>Middle</FingerThree>
</Hand>

...the wanted result is produced:

<Hand id="left" F1="Thumb" F2="Pointer" F3="Middle" />

Explanation:

  • Note the <my:attributeNames> "array" of elements; furthermore, note how the "old" and "new" values are contained on these elements as attributes.

  • These values are accessed via the use of the document('') instruction, which instructs the parser to gather elements from the current document.


Alternatively, here's a different XSLT 1.0 solution. It might be more useful if:

  • document('') is, for some reason, not available in your environment.

OR

  • Using a pseudo-dictionary in your XSLT seems a bit heavy-handed (and you're willing to make a small assumption).

When this XSLT:

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

  <xsl:variable name="vStartingChar" select="'F'" />

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

  <xsl:template match="Hand/*">
    <xsl:attribute
      name="{concat($vStartingChar, count(preceding-sibling::*) + 1)}">
      <xsl:value-of select="."/>
    </xsl:attribute>
  </xsl:template>
</xsl:stylesheet>

...is applied against the same input XML, the wanted result is, again, produced:

<Hand id="left" F1="Thumb" F2="Pointer" F3="Middle" />

Explanation:

Note the variable defined at the top of the XSLT:

<xsl:variable name="vStartingChar" select="'F'" />

This provides a convenient mechanism to change to the starting character of the new attributes. As mentioned before, if this same pattern continues (i.e., additional attributes would follow the same scheme - F4, F5, etc.), this solution is a shade nicer, as it doesn't require you to update an "array" of elements.

OTHER TIPS

If you want to go with the dictionary approach, you should first create a dictionary in a file named dict.xml:

<dict>
    <entry name="FingerOne">F1</entry>
    <entry name="FingerTwo">F2</entry>
    <entry name="FingerThree">F3</entry>
</dict>

Then you can write the transformation like this:

<xsl:variable name="dict" select="document('dict.xml')/dict"/>

<xsl:template match="Hand">
    <xsl:copy>
        <xsl:copy-of select="@*"/>
        <xsl:apply-templates select="*" mode="map-to-attr"/>
    </xsl:copy>
</xsl:template>

<xsl:template match="*" mode="map-to-attr">
    <xsl:attribute name="{$dict/entry[@name=name(current())]}">
        <xsl:value-of select="."/>
    </xsl:attribute>
</xsl:template>
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top