0
votes

[edit] post updated with revised XML and desired output as detailed in Vincent Biragnet's answer and comments.

I'm trying to put together some code to convert XML data into a text based meta file. I'm having no luck getting it to spit out desired output and am currently kinda stuck, so any help would be appreciated.

XSLT 1.0 does not make tokenizing easy and that's where I got stuck: I would like to treat @syn as a CSV string and break it apart when needed.

I'm working with the following XML data (please note that all nodes, except for the <Meta> node, in this XML file can have any name.)

<Meta>
    <Subject>
        <People>
            <Jane_Doe syn="janie, jd" />
            <John_Doe/>
        </People>
        <Object>
            <Table>
                <Leg/>
            </Table>
            <Chair syn="seat" />
        </Object>
    </Subject>
    <Test1>
        <Test2 syn="testy"/>
        <Test3>
            <Test4/>
        </Test3>
    </Test1>
</Meta>

This XML needs to be converted so the output looks this:

[Subject]
    [People]
        Jane_Doe
            {janie}
            {jd}
        John_Doe
    [Object]
        [Table]
            Leg
        Chair
            {seat}
[Test1]
    Test2
        {testy}
    [Test3]
        Test4

My current XSL:

<?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
    <xsl:output method="text" indent="yes"/>
    <xsl:template match="/Meta"><xsl:apply-templates/></xsl:template>
    <xsl:template match="child::*"><xsl:call-template name="master"/><xsl:apply-templates/></xsl:template>
    <xsl:template name="master">
        <xsl:choose>
            <xsl:when test="count(child::*) = 0">
                <xsl:value-of select="local-name()"/>
                <xsl:apply-templates select="@syn"/>
            </xsl:when>
            <xsl:otherwise>
                [<xsl:value-of select="local-name()"/>]
                <xsl:apply-templates select="@syn"/>
        </xsl:otherwise>
        </xsl:choose>
    </xsl:template>
    <xsl:template name="tokenize" match="@syn">
        <xsl:param name="text" select="."/>
        <xsl:param name="separator" select="','"/>
        <xsl:value-of select="$text"/>
        <xsl:choose>
            <xsl:when test="not(contains($text, $separator))">
                {<xsl:value-of select="normalize-space($text)"/>}
            </xsl:when>
            <xsl:otherwise>
                <xsl:value-of select="normalize-space(substring-before($text, $separator))"/>
                <xsl:call-template name="tokenize">
                    {<xsl:with-param name="text" select="substring-after($text, $separator)"/>}
                </xsl:call-template>
            </xsl:otherwise>
        </xsl:choose>
    </xsl:template>
</xsl:stylesheet>
3

3 Answers

0
votes

you were pretty much there, just got brackets around wrong thing causing error!

<?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
    <xsl:output method="text" indent="yes"/>
    <xsl:template match="/Meta"><xsl:apply-templates/></xsl:template>
    <xsl:template match="child::*"><xsl:call-template name="master"/><xsl:apply-templates/></xsl:template>
    <xsl:template name="master">
        <xsl:choose>
            <xsl:when test="count(child::*) = 0">
                <xsl:value-of select="local-name()"/>
                <xsl:apply-templates select="@syn"/>
            </xsl:when>
            <xsl:otherwise>
                [<xsl:value-of select="local-name()"/>]
                <xsl:apply-templates select="@syn"/>
        </xsl:otherwise>
        </xsl:choose>
    </xsl:template>
    <xsl:template name="tokenize" match="@syn">
        <xsl:param name="text" select="."/>
        <xsl:param name="separator" select="','"/>        
        <xsl:choose>
            <xsl:when test="not(contains($text, $separator))">
                {<xsl:value-of select="normalize-space($text)"/>}
            </xsl:when>
            <xsl:otherwise>
                {<xsl:value-of select="normalize-space(substring-before($text, $separator))"/>}
                <xsl:call-template name="tokenize">
                    <xsl:with-param name="text" select="substring-after($text, $separator)"/>
                </xsl:call-template>
            </xsl:otherwise>
        </xsl:choose>
    </xsl:template>
</xsl:stylesheet>
0
votes

I would do it like this:

<xsl:stylesheet
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  version="1.0">

  <xsl:output method="text"/>

  <xsl:param name="lf" select="'&#10;'"/>
  <xsl:param name="start-indent" select="'    '"/>
  <xsl:param name="br1" select="'['"/>
  <xsl:param name="br2" select="']'"/>
  <xsl:param name="br3" select="'{'"/>
  <xsl:param name="br4" select="'}'"/>
  <xsl:param name="sep" select="','"/>

  <xsl:template match="/Meta" priority="10">
    <xsl:apply-templates select="*">
      <xsl:with-param name="indent" select="''"/>
    </xsl:apply-templates>
  </xsl:template>

  <xsl:template match="*[not(*)]">
    <xsl:param name="indent"/>
    <xsl:value-of select="concat($indent, local-name(), $lf)"/>
    <xsl:apply-templates select="@syn">
      <xsl:with-param name="indent" select="concat($indent, $start-indent)"/>
    </xsl:apply-templates>
  </xsl:template>

  <xsl:template match="*[*]">
    <xsl:param name="indent"/>
    <xsl:value-of select="concat($indent, $br1, local-name(), $br2, $lf)"/>
    <xsl:apply-templates select="*">
      <xsl:with-param name="indent" select="concat($indent, $start-indent)"/>
    </xsl:apply-templates>
  </xsl:template>

  <xsl:template match="@syn">
    <xsl:param name="indent"/>
    <xsl:param name="text" select="."/>
    <xsl:choose>
      <xsl:when test="not(contains($text, $sep))">
        <xsl:if test="normalize-space($text)">
          <xsl:value-of select="concat($indent, $br3, normalize-space($text), $br4, $lf)"/>
        </xsl:if>
      </xsl:when>
      <xsl:otherwise>
        <xsl:value-of select="concat($indent, $br3, normalize-space(substring-before($text, $sep)), $br4, $lf)"/>
        <xsl:apply-templates select=".">
          <xsl:with-param name="indent" select="$indent"/>
          <xsl:with-param name="text" select="substring-after($text, $sep)"/>
        </xsl:apply-templates>
      </xsl:otherwise>
    </xsl:choose>
  </xsl:template>

</xsl:stylesheet>
0
votes

It's a bit tricky to get exactly your output with XSLT 1.0, but you can use the mecanism you tried. Here is an XSLT 1.0 that output with the indentation :

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
    <xsl:output method="text"/>
    <xsl:strip-space elements="*"/>
    <xsl:template match="Meta">
        <xsl:apply-templates/>
    </xsl:template>
    <xsl:template match="*">
        <xsl:param name="depth" select="0"/>
        <xsl:choose>
            <xsl:when test="$depth > 0">
                <xsl:text>&#10;</xsl:text>            
                <xsl:call-template name="addSpace">
                    <xsl:with-param name="nb" select="$depth"></xsl:with-param>
                </xsl:call-template>                
            </xsl:when>
            <xsl:when test="position() > 1">
                <xsl:text>&#10;</xsl:text>                            
            </xsl:when>
        </xsl:choose>        
        <xsl:choose>
            <xsl:when test="count(*) > 0">
                <xsl:text>[</xsl:text>
                <xsl:value-of select="local-name()"/>
                <xsl:text>]</xsl:text>                
            </xsl:when>
            <xsl:otherwise>
                <xsl:value-of select="local-name()"/>
            </xsl:otherwise>
        </xsl:choose>
        <xsl:apply-templates select="@syn|*">
            <xsl:with-param name="depth" select="$depth + 1"/>
        </xsl:apply-templates>       
    </xsl:template>
    <xsl:template name="tokenize" match="@syn">
        <xsl:param name="text" select="."/>
        <xsl:param name="depth" select="1"/>
        <xsl:param name="separator" select="','"/>
        <xsl:text>&#10;</xsl:text>
        <xsl:call-template name="addSpace">
            <xsl:with-param name="nb" select="$depth"></xsl:with-param>
        </xsl:call-template>
        <xsl:choose>
            <xsl:when test="not(contains($text, $separator))">
                <xsl:text>{</xsl:text>
                <xsl:value-of select="normalize-space($text)"/>
                <xsl:text>}</xsl:text>
            </xsl:when>
            <xsl:otherwise>
                <xsl:text>{</xsl:text>
                <xsl:value-of select="normalize-space(substring-before($text, $separator))"/>
                <xsl:text>}</xsl:text>
                <xsl:call-template name="tokenize">
                    <xsl:with-param name="text" select="substring-after($text, $separator)"/>
                    <xsl:with-param name="separator" select="$separator"/>
                    <xsl:with-param name="depth" select="$depth"/>
                </xsl:call-template>
            </xsl:otherwise>
        </xsl:choose>
    </xsl:template>
    <xsl:template name="addSpace">
        <xsl:param name="nb"/>
        <xsl:text> </xsl:text>
        <xsl:if test="$nb >1 ">
            <xsl:call-template name="addSpace">
                <xsl:with-param name="nb" select="$nb - 1"></xsl:with-param>
            </xsl:call-template>            
        </xsl:if>
    </xsl:template>
</xsl:stylesheet>

The result with your input is :

[Subject]
 [People]
  Jane_Doe
   {janie}
   {jd}
  John_Doe
 [Object]
  [Table]
   Leg
  Chair
   {seat}

Note the difference with your output : the table element is between bracket because it has one or more children.