tags:

views:

55

answers:

2

I want to output something similar to the following using XSLT

XML:

<myroot>
  <node1>
    <subnode1>somestuff</subnode1>
    <subnode2>otherstuff</subnode2>
  </node1>
  <node2>
    <subnode2></subnode2>
    <subnode3>stuff here</subnode3>
  </node2>
  <node3>
    <subnode>stuff</subnode>
    <subnode>stuff</subnode>
    <subnode>other</subnode>
  </node3>
</myroot>

Where I do not know the node names for a given instance.

I want my output to look like this:

myroot = new jsonObject();
myroot.node1 = new jsonObject();
myroot.node1.subnode1 = "holder";
myroot.node1.subnode2 = "holder";
myroot.node2 = new jsonObject();
myroot.node2.subnode2 = "holder";
myroot.node2.subnode3 = "holder";
myroot.node3 = new jsonObject();
myroot.node3.subnode = new array();
"arraystart"
myroot.node3.subnode[aindex] = new jsonObject();
myroot.node3.subnode[aindex] = "holder";
"endarray"

Important points:

  • = "holder"; can be anything unique as I will change this later
  • "arraystart" and "endarray" can be anything unique as I will change this later
  • I do NOT know the specific node names beyond the root.
  • I do NOT know the depth of the tree (some 6-7 deep exist)
  • I do NOT know the numbers or position or array elements, but the child nodes(elements) are the same name for those groups.
  • Multiple arrays may/do exist, and can be at any tree depth.
  • Elements with text do not have child nodes
A: 

Not sure this is the most efficient way of doing it, but hopefully this should give you some pointers, if indeed it doesn't do the job entirely:

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

  <xsl:template match="*[count(*)!=0]">
    <xsl:param name="prefix" />
    <xsl:value-of select="substring(concat($prefix,'.',name()),2)" />
    <xsl:text> = new jsonObject();&#10;</xsl:text>
    <xsl:apply-templates select="*">
      <xsl:with-param name="prefix" select="concat($prefix,'.',name())" />
    </xsl:apply-templates>
  </xsl:template>

  <xsl:template match="*[count(*)=0]">
    <xsl:param name="prefix" />
    <xsl:choose>
      <xsl:when test="count(../*[name()=name(current())]) != 1">
        <xsl:if test="position()=1">
          <xsl:value-of select="substring(concat($prefix,'.',name()),2)" />
          <xsl:text> = new array();&#10;</xsl:text>
          <xsl:text>"arraystart"&#10;</xsl:text>
          <xsl:value-of select="substring(concat($prefix,'.',name()),2)" />
          <xsl:text>[aindex] = new jsonObject();&#10;</xsl:text>
          <xsl:value-of select="substring(concat($prefix,'.',name()),2)" />
          <xsl:text>[aindex] = "holder";&#10;</xsl:text>
          <xsl:text>"endarray"&#10;</xsl:text>
        </xsl:if>
      </xsl:when>
      <xsl:otherwise>
        <xsl:value-of select="substring(concat($prefix,'.',name()),2)" />
        <xsl:text> = "holder";&#10;</xsl:text>
      </xsl:otherwise>
    </xsl:choose>
  </xsl:template>
</xsl:stylesheet>

I originally had the second template matching *[text()], but unfortunately your <subnode></subnode> is equivalent to <subnode />, which has no text at all rather than text with 0 length. This means it assumes any node that has no childs should be treated as if it is text.

The 'when' test in the second template is a bit convoluted, but it basically checks to see if the current node is one of multiple with that name, and if it's the first such node, it outputs the array stuff, otherwise does nothing. The big drawback here is that such arrays can only be of text nodes; for example, if your sample xml had node1 in place of node2 such that you had two node1 elements under myroot, you wouldn't see any of the "arraystart" stuff. If that can happen, it'll need a bit of a redesign, but hopefully there's enough useful examples in here to help.

Edit: Forgot two small points:

I'm using &#10; for the newline character; substitute this with &#13;&#10; if required.

Also, the substring(something,2) bits are because it appends a period followed by the node name every time it goes down a level, which means it'll have one right at the beginning every time. substring(something,2) just takes everything from the 2nd character onwards, in other words, chopping off that period.

Flynn1179
+1 gives desired results
Mark Schultheiss
+1  A: 

This stylesheet:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"&gt;
    <xsl:output method="text"/>
    <xsl:key name="name" match="*" use="name()"/>
    <xsl:template match="text()"/>
    <xsl:template match="*[*]">
        <xsl:param name="name"/>
        <xsl:value-of select="concat($name,
                                     name(),
                                     ' = new jsonObject();&#xA;')"/>
        <xsl:apply-templates>
            <xsl:with-param name="name" select="concat($name,name(),'.')"/>
        </xsl:apply-templates>
    </xsl:template>
    <xsl:template match="*[not(*)][count(../*|key('name',name()))!=count(key('name',name()))]">
        <xsl:param name="name"/>
        <xsl:value-of select="concat($name,
                                     name(),
                                     ' = &quot;holder&quot;;&#xA;')"/>
    </xsl:template>
    <xsl:template match="*[not(*)][1][count(../*|key('name',name()))=count(key('name',name()))]" priority="1">
        <xsl:param name="name"/>
        <xsl:value-of select="concat($name,
                                     name(),
                                     ' = new array();&#xA;',
                                     '&quot;arraystart&quot;&#xA;')"/>
        <xsl:apply-templates select="following-sibling::*" mode="array">
            <xsl:with-param name="name" select="concat($name,name(),'.')"/>
        </xsl:apply-templates>
        <xsl:text>"endarray"</xsl:text>
    </xsl:template>
    <xsl:template match="*" mode="array">
        <xsl:param name="name"/>
        <xsl:value-of select="concat($name,
                                     '[aindex] = ')"/>
        <xsl:choose>
            <xsl:when test="contains(.,'stuff')">new jsonObject();&#xA;</xsl:when>
            <xsl:otherwise>"holder";&#xA;</xsl:otherwise>
        </xsl:choose>
    </xsl:template>
</xsl:stylesheet>

Output:

myroot = new jsonObject();
myroot.node1 = new jsonObject();
myroot.node1.subnode1 = "holder";
myroot.node1.subnode2 = "holder";
myroot.node2 = new jsonObject();
myroot.node2.subnode2 = "holder";
myroot.node2.subnode3 = "holder";
myroot.node3 = new jsonObject();
myroot.node3.subnode = new array();
"arraystart"
myroot.node3.subnode.[aindex] = new jsonObject();
myroot.node3.subnode.[aindex] = "holder";
"endarray"

But I think you should refine your goal.

Alejandro
+1 gives desired results (took out the ") as it was causing issues for unknown reason and not needed 100%
Mark Schultheiss