1
votes

I would like to convert xml file format to another format; using XSL version 1.0 or 2.0.

Input XML file:

<ROOT_XML>
  <Struct id="_6" name="Result" context="_1" members="_9 _10 _11 _13 _14 "/>
  <FundamentalType id="_7" name="int" size="32" align="32"/>
  <FundamentalType id="_8" name="float" size="32" align="32"/>
  <Field id="_9" name="angle" type="_8" offset="0" context="_6"/>
  <Field id="_10" name="row" type="_7" offset="32" context="_6"/>
  <Field id="_11" name="cloth" type="_18" offset="96" context="_6"/>
  <Destructor id="_13" name="EmptyClass" artificial="1" throw="" context="_6">
  </Destructor>
  <Constructor id="_14" name="Result" context="_6">
    <Argument type="_20" location="f0:2" file="f0" line="2"/>
  </Constructor>
  <Constructor id="_15" name="Result" context="_6"/>
  <FundamentalType id="_17" name="unsigned int" size="32" align="32"/>
  <ArrayType id="_18" min="0" max="29u" type="_21" size="240" align="8"/>
  <ReferenceType id="_19" type="_6" size="32" align="32"/>
  <ReferenceType id="_20" type="_6c" size="32" align="32"/>
  <FundamentalType id="_21" name="char" size="8" align="8"/>
</ROOT_XML>

Output XML file:

<Struct Result>
  <Fields>
    <Field name="angle"  type="float" size="32"/>
    <Field name="row"    type="int"   size="32"/>
    <Field name="cloth"  type="char"  size="240"/>
  </Fields> 
</Struct>

The is an example on how to parse the 'members' attribute list

http://www.w3.org/1999/XSL/Transform" version = "1.0">

<xsl:template match="/ROOT_XML/Struct"> 

  <Struct><xsl:value-of select="name"/>
      <xsl:choose> 
         <xsl:when test="boolean(./@members)"> 
            <xsl:call-template name="tokenizeString"> 
                <xsl:with-param name="list" select="./@members"/> 
                <xsl:with-param name="delimiter" select="' '"/> 
            </xsl:call-template> 
        </xsl:when> 

        <xsl:otherwise/> 
     </xsl:choose>
   </Struct>

</xsl:template>


<!--############################################################--> 
<!--## Template to tokenize strings                           ##--> 
<!--############################################################--> 

<xsl:template name="tokenizeString"> 
    <!--passed template parameter --> 
    <xsl:param name="list"/> 
    <xsl:param name="delimiter"/> 

    <xsl:choose> 
      <xsl:when test="contains($list, $delimiter)">                 
            <member> 
                <!-- get everything in front of the first delimiter --> 
                <xsl:value-of select="substring-before($list,$delimiter)"/>
            </member> 

            <xsl:call-template name="tokenizeString"> 
                <!-- store anything left in another variable --> 
                <xsl:with-param name="list" select="substring-after($list,$delimiter)"/> 
                <xsl:with-param name="delimiter" select="$delimiter"/> 
            </xsl:call-template> 
        </xsl:when> 

        <xsl:otherwise> 
            <xsl:choose> 
                <xsl:when test="$list = ''"> 
                    <xsl:text/> 
                </xsl:when> 
               <xsl:otherwise> 
                    <member> 
                        <xsl:value-of select="$list"/> 
                    </member> 
                </xsl:otherwise> 
            </xsl:choose> 
        </xsl:otherwise> 

    </xsl:choose> 
</xsl:template>    

This code is a starting point to extract the relevant id's from the 'members' attribute of the 'Struct' node, and later be used to emit only the 'Field' nodes.

In addition, the output XML file might contains more than one 'Struct' node, For Instance:


<Struct Result>
  <Fields>
    <Field name="angle"  type="float" size="32"/>
    <Field name="row"    type="int"   size="32"/>
    <Field name="cloth"  type="char"  size="240"/>
  </Fields> 
</Struct>
<Struct Answer>
  <Fields>
    <Field name="direction" type="float" size="32"/>
    <Field name="col" type="int" size="32"/>
    <Field name="paper" type="char"  size="232"/>
  </Fields> 
</Struct>

Thanks for the reply. still, I would like to emphasize the logic on how to get the xml output.

The xslt processor needs to parse the 'members' attribute of the Struct node. The 'members' attribute is a list of Field's ids.

In the above example:

Only "members=_9 _10 _11" are Field nodes! and therefore they should be output as done by Michael previously. The rest of the items in the list are omitted (i.e. members="_13 _14")

The combined code: (I need assistance to continue...) http://www.w3.org/1999/XSL/Transform" version = "1.0">

  <Struct><xsl:value-of select="name"/>
      <xsl:choose> 
         <xsl:when test="boolean(./@members)"> 
            <xsl:call-template name="tokenizeString"> 
                <xsl:with-param name="list" select="./@members"/> 
                <xsl:with-param name="delimiter" select="' '"/> 
            </xsl:call-template> 
        </xsl:when> 

        <xsl:otherwise/> 
     </xsl:choose>
   </Struct>

</xsl:template>


<!--############################################################--> 
<!--## Template to tokenize strings                           ##--> 
<!--############################################################--> 

<xsl:template name="tokenizeString"> 
    <!--passed template parameter --> 
    <xsl:param name="list"/> 
    <xsl:param name="delimiter"/> 

    <xsl:choose> 
      <xsl:when test="contains($list, $delimiter)">                 
            <member> 
                <!-- get everything in front of the first delimiter --> 
                <xsl:value-of select="substring-before($list,$delimiter)"/>

                <!-- TODO: select holds the member's id...
                    Q: how should we continue from here?? --> 
            </member> 

            <xsl:call-template name="tokenizeString"> 
                <!-- store anything left in another variable --> 
                <xsl:with-param name="list" select="substring-after($list,$delimiter)"/> 
                <xsl:with-param name="delimiter" select="$delimiter"/> 
            </xsl:call-template> 
        </xsl:when> 

        <xsl:otherwise> 
            <xsl:choose> 
                <xsl:when test="$list = ''"> 
                    <xsl:text/> 
                </xsl:when> 
               <xsl:otherwise> 
                    <member> 
                        <xsl:value-of select="$list"/> 

                        <!-- TODO: select holds the member's id...
                            Q: how should we continue from here?? --> 

                    </member> 
                </xsl:otherwise> 
            </xsl:choose> 
        </xsl:otherwise> 

    </xsl:choose> 
</xsl:template>   

<xsl:template match="Field[key('f-type', @type)]">
    <xsl:variable name="f-type" select="key('f-type', @type)" />
    <Field name="{@name}" type="{$f-type/@name}" size="{$f-type/@size}"/>
</xsl:template>

<xsl:template match="Field[key('a-type', @type)]">
    <xsl:variable name="a-type" select="key('a-type', @type)" />
    <xsl:variable name="f-type" select="key('f-type', $a-type/@type)" />
    <Field name="{@name}" type="{$f-type/@name}" size="{$a-type/@size}"/>
</xsl:template>

1
Welcome to Stack Overflow! Stack Overflow is not a code writing service, you need to show what you got so far and where you get stuck. Please read How do I ask a good question?. As a new user, you probably should read the introductory tour as well.Jongware
... <Struct Result> is bad XML, by the way. It's possible to create this through XSLT, but not with the output set to XML.Jongware
Also you will have a hard time telling the renderer to output multiple spaces between attributes.Mr Lister
Could you explain more about the logic that needs to be applied here? One or two examples do not establish a rule.michael.hor257k
@Dan Yours "answers" have been moved to your question - as others have mentioned - answers should be used for answers - if you want to make clarifications/add additional information than you should do so via editing your question instead. Thanks.Jon Clements♦

1 Answers

0
votes

The following stylesheet:

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:key name="f-type" match="FundamentalType" use="@id" />
<xsl:key name="a-type" match="ArrayType" use="@id" />

<xsl:template match="ROOT_XML">
    <Struct>
        <xsl:apply-templates select="Field"/>
    </Struct>
</xsl:template> 

<xsl:template match="Field[key('f-type', @type)]">
    <xsl:variable name="f-type" select="key('f-type', @type)" />
    <Field name="{@name}" type="{$f-type/@name}" size="{$f-type/@size}"/>   
</xsl:template>

<xsl:template match="Field[key('a-type', @type)]">
    <xsl:variable name="a-type" select="key('a-type', @type)" />
    <xsl:variable name="f-type" select="key('f-type', $a-type/@type)" />
    <Field name="{@name}" type="{$f-type/@name}" size="{$a-type/@size}"/>   
</xsl:template>

</xsl:stylesheet>

when applied to your example input, will return:

<?xml version="1.0" encoding="UTF-8"?>
<Struct>
  <Field name="angle" type="float" size="32"/>
  <Field name="row" type="int" size="32"/>
  <Field name="cloth" type="char" size="240"/>
</Struct>

How this works:

  • If a Field's type matches an id of a FundamentalType, then the type and the size values are looked up from the matching FundamentalType;
  • If a Field's type matches an id of an ArrayType, then the size value is looked up from the matching ArrayType, while the type value is looked up from the FundamentalType whose id matches the type attribute of the ArrayType.

Edit:

If you want each Struct to include only Fields whose id is listed in its members attribute, you can do it this way:

XSLT 2.0

<xsl:stylesheet version="2.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:key name="field" match="Field" use="@id" />
<xsl:key name="f-type" match="FundamentalType" use="@id" />
<xsl:key name="a-type" match="ArrayType" use="@id" />

<xsl:variable name="xml" select="/" />

<xsl:template match="/ROOT_XML">
    <root>
        <xsl:for-each select="Struct">
            <Struct name="{@name}">
                <xsl:apply-templates select="key('field', tokenize(@members, ' '))"/>
            </Struct>
        </xsl:for-each> 
    </root>
</xsl:template> 

<xsl:template match="Field[key('f-type', @type)]">
    <xsl:variable name="f-type" select="key('f-type', @type)" />
    <Field name="{@name}" type="{$f-type/@name}" size="{$f-type/@size}"/>   
</xsl:template>

<xsl:template match="Field[key('a-type', @type)]">
    <xsl:variable name="a-type" select="key('a-type', @type)" />
    <xsl:variable name="f-type" select="key('f-type', $a-type/@type)" />
    <Field name="{@name}" type="{$f-type/@name}" size="{$a-type/@size}"/>   
</xsl:template>

</xsl:stylesheet>

Edit 2

The same thing in XSLT 1.0:

<xsl:stylesheet version="1.0" 
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:exsl="http://exslt.org/common"
extension-element-prefixes="exsl">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>

<xsl:key name="field" match="Field" use="@id" />
<xsl:key name="f-type" match="FundamentalType" use="@id" />
<xsl:key name="a-type" match="ArrayType" use="@id" />

<xsl:variable name="xml" select="/" />

<xsl:template match="/ROOT_XML">
    <root>
        <xsl:for-each select="Struct">
            <xsl:variable name="members">
                <xsl:call-template name="tokenize">
                    <xsl:with-param name="text" select="@members"/>
                </xsl:call-template>
            </xsl:variable>
            <Struct name="{@name}">
                <xsl:apply-templates select="key('field', exsl:node-set($members)/token)"/>
            </Struct>
        </xsl:for-each> 
    </root>
</xsl:template> 

<xsl:template match="Field[key('f-type', @type)]">
    <xsl:variable name="f-type" select="key('f-type', @type)" />
    <Field name="{@name}" type="{$f-type/@name}" size="{$f-type/@size}"/>   
</xsl:template>

<xsl:template match="Field[key('a-type', @type)]">
    <xsl:variable name="a-type" select="key('a-type', @type)" />
    <xsl:variable name="f-type" select="key('f-type', $a-type/@type)" />
    <Field name="{@name}" type="{$f-type/@name}" size="{$a-type/@size}"/>   
</xsl:template>

<xsl:template name="tokenize">
    <xsl:param name="text"/>
    <xsl:param name="delimiter" select="' '"/>
    <xsl:choose>
        <xsl:when test="contains($text, $delimiter)">
            <token>
                <xsl:value-of select="substring-before($text, $delimiter)"/>
            </token>
            <!-- recursive call -->
            <xsl:call-template name="tokenize">
                <xsl:with-param name="text" select="substring-after($text, $delimiter)"/>
            </xsl:call-template>
        </xsl:when>
        <xsl:otherwise>
            <token>
                <xsl:value-of select="$text"/>
            </token>
        </xsl:otherwise>
    </xsl:choose>
</xsl:template>


</xsl:stylesheet>