XSLT to generate another XSLT script

2019-04-08 06:02发布

In this thread I refer to my last thread: Convert XML to CSV using XSLT - dynamic columns.

The XSLT script in the refered thread works fine but with a large XML document the performance is not good. Now I want to write an XSLT script that outputs another XSLT script which will output the final CSV file.

Question:

How to write the first XSLT script? The output should look like the following:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>

<xsl:template match="/*">
    <xsl:text>Name;</xsl:text>
    <xsl:text>Brother;</xsl:text>
    <xsl:text>Sister</xsl:text>
    <-- this part is dynamic -->
    <xsl:apply-templates select="Person" />
</xsl:template>

<xsl:template match="Person">
    <xsl:value-of select="Name" />
    <xsl:value-of select="Brother" />
    <xsl:value-of select="Sister" />
    <-- this part is dynamic too -->
    <xsl:text>&#10;</xsl:text>
</xsl:template>
</xsl:stylesheet>

The input XML file is the same like in the refered thread:

<Person>
    <Name>John</Name>
    <FamilyMembers>
        <FamilyMember>
            <Name>Lisa</Name>
            <Type>Sister</Type>
        </FamilyMember>
        <FamilyMember>
            <Name>Tom</Name>
            <Type>Brother</Type>
        </FamilyMember>
    </FamilyMembers>
</Person>
<Person>
    <Name>Daniel</Name>
    <FamilyMembers>
        <FamilyMember>
            <Name>Peter</Name>
            <Type>Father</Type>
        </FamilyMember>
    </FamilyMembers>
</Person>

For every different type element there should be one line like the following in the resulting XSLT script:

<xsl:text>Type;</xsl:text>

标签: xml xslt
3条回答
闹够了就滚
2楼-- · 2019-04-08 06:23

To write one XSLT that outputs another you either need to generate the output elements using <xsl:element>, e.g.

<xsl:element name="xsl:text">

or use <xsl:namespace-alias> if you want to use literal result elements. The XSLT spec has an example:

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

<xsl:namespace-alias stylesheet-prefix="axsl" result-prefix="xsl"/>

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

Any <axsl:...> elements in the stylesheet will become <xsl:...> in the output.

查看更多
萌系小妹纸
3楼-- · 2019-04-08 06:27

See an actual example of an XSLT transformation that produces a stylesheet for another XSLT transformation here:

http://dnovatchev.wordpress.com/2006/10/21/a-stylesheet-to-write-xslt-code/

查看更多
混吃等死
4楼-- · 2019-04-08 06:28

Rather than a two-external-phase solution (meaning a style-sheet that writes a style-sheet that gets executed), I think you would be better served by a version of Tim's solution that performs better at scale. Please measure the performance of this solution with your 'large XML document' as input.

This XSLT 1.0 style-sheet...

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

<xsl:key name="kTypes" match="Type" use="." />  
<xsl:variable name="distinct-types"
  select="/*/Person/FamilyMembers/FamilyMember/Type[
  generate-id()=generate-id(key('kTypes',.)[1])]" />

<xsl:template match="/">
  <xsl:value-of select="'Name;'" />
  <xsl:for-each select="$distinct-types">
    <xsl:value-of select="." />
    <xsl:if test="position() &lt; last()">
      <xsl:value-of select="';'" />
    </xsl:if>  
  </xsl:for-each>
  <xsl:value-of select="'&#x0A;'" />
  <xsl:apply-templates select="*/Person" />
</xsl:template>

<xsl:template match="Person">
  <xsl:value-of select="concat(Name,';')" />
  <xsl:variable name="family" select="FamilyMembers/FamilyMember" />
  <xsl:for-each select="$distinct-types">
    <xsl:variable name="type" select="string(.)" />
    <xsl:value-of select="$family/self::*[Type=$type]/Name" />
    <xsl:if test="position() &lt; last()">
      <xsl:value-of select="';'" />
    </xsl:if>  
  </xsl:for-each>
  <xsl:value-of select="'&#x0A;'" />
</xsl:template>

</xsl:stylesheet>

...will transform this input (or others efficiently at scale) ...

<t>
<Person>
    <Name>John</Name>
    <FamilyMembers>
        <FamilyMember>
            <Name>Lisa</Name>
            <Type>Sister</Type>
        </FamilyMember>
        <FamilyMember>
            <Name>Tom</Name>
            <Type>Brother</Type>
        </FamilyMember>
    </FamilyMembers>
</Person>
<Person>
    <Name>Daniel</Name>
    <FamilyMembers>
        <FamilyMember>
            <Name>Peter</Name>
            <Type>Father</Type>
        </FamilyMember>
    </FamilyMembers>
</Person>
</t>

... and yield text...

Name;Sister;Brother;Father
John;Lisa;Tom;
Daniel;;;Peter
查看更多
登录 后发表回答