refactor XML with XSL

2019-07-28 02:05发布

I need to move the <sectionid/> and the <sponsor/> elements inside the corresponding <amendment/>.

Here is an example:

From :

<root>
      <sectionid>A</sectionid>
      <sponsor>john</sponsor>
      <sponsor>paul</sponsor>
      <amendment>
            <id>1</id>
            <text>some text</text>
      </amendment>
      <sectionid>B</sectionid>
      <sponsor>peter</sponsor>
      <amendment>
            <id>5</id>
            <text>some text</text>
      </amendment>
      <amendment>
            <id>4</id>
            <text>some text</text>
      </amendment>
      <sponsor>max</sponsor>
      <amendment>
            <id>6</id>
            <text>some text</text>
      </amendment>
      <amendment>
            <id>7</id>
            <text>some text</text>
      </amendment>

</root>

to:

<root>
      <amendment>
            <sectionid>A</sectionid>
            <sponsor>john</sponsor>
            <sponsor>paul</paul>
            <id>1</id>
            <text>some text</text>
      </amendment>
      <amendment>
            <sectionid>B</sectionid>
            <sponsor>peter</sponsor>
            <id>5</id>
            <text>some text</text>
      </amendment>
      <amendment>
            <sectionid>B</sectionid>
            <sponsor>peter</sponsor>
            <id>4</id>
            <text>some text</text>
      </amendment>
      <amendment>
            <sectionid>B</sectionid>
            <sponsor>max</sponsor>
            <id>6</id>
            <text>some text</text>
      </amendment>
      <amendment>
            <sectionid>B</sectionid>
            <sponsor>max</sponsor>
            <id>7</id>
            <text>some text</text>
      </amendment>

</root>

Note 1: the <section/> element applies to all the <amendments/> before the next <sectionid/>

Note 2: the <sponsor/> element applies to all the <amendments/> before the next <sponsor/> list.

Note 3: The values of //amendment/id are not sequential.

How can this transformation be done with XSLT 1.0.

标签: xml xslt
2条回答
趁早两清
2楼-- · 2019-07-28 02:46

This transformation:

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

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

 <xsl:template match="amendment">
  <xsl:copy>
     <xsl:apply-templates select="@*"/>

     <xsl:variable name="vSectionId" 
          select="preceding-sibling::sectionid[1]"/>
     <xsl:variable name="vprevAmendment" 
          select="preceding-sibling::amendment[1]"/>

     <xsl:apply-templates select="$vSectionId" mode="copy"/>

     <xsl:variable name="vsectSponsors"
      select="preceding-sibling::sponsor
               [generate-id(preceding-sibling::sectionid[1])
               =
                generate-id($vSectionId)
               ]"/>

     <xsl:variable name="vamdSponsors"
      select="preceding-sibling::sponsor
               [generate-id(preceding-sibling::amendment[1])
               =
                generate-id($vprevAmendment)
               ]"/>


     <xsl:apply-templates mode="copy"
          select="$vamdSponsors|$vsectSponsors[not($vamdSponsors)]"/>
    <xsl:apply-templates select="node()|@*"/>
   </xsl:copy>
 </xsl:template>

 <xsl:template match="*" mode="copy">
  <xsl:call-template name="identity"/>
 </xsl:template>

 <xsl:template match="sectionid|sponsor"/>
</xsl:stylesheet>

when applied on the provided XML document:

<root>
      <sectionid>A</sectionid>
      <sponsor>john</sponsor>
      <sponsor>paul</sponsor>
      <amendment>
            <id>1</id>
            <text>some text</text>
      </amendment>
      <sectionid>B</sectionid>
      <sponsor>peter</sponsor>
      <amendment>
            <id>5</id>
            <text>some text</text>
      </amendment>
      <amendment>
            <id>4</id>
            <text>some text</text>
      </amendment>
      <sponsor>max</sponsor>
      <amendment>
            <id>6</id>
            <text>some text</text>
      </amendment>
</root>

produces the wanted, correct results:

<root>
   <amendment>
      <sectionid>A</sectionid>
      <sponsor>john</sponsor>
      <sponsor>paul</sponsor>
      <id>1</id>
      <text>some text</text>
   </amendment>
   <amendment>
      <sectionid>B</sectionid>
      <sponsor>peter</sponsor>
      <id>5</id>
      <text>some text</text>
   </amendment>
   <amendment>
      <sectionid>B</sectionid>
      <sponsor>peter</sponsor>
      <id>4</id>
      <text>some text</text>
   </amendment>
   <amendment>
      <sectionid>B</sectionid>
      <sponsor>max</sponsor>
      <id>6</id>
      <text>some text</text>
   </amendment>
</root>
查看更多
beautiful°
3楼-- · 2019-07-28 02:53

This stylesheet:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:strip-space elements="*"/>
    <xsl:template match="node()|@*">
        <xsl:param name="pSectionId"/>
        <xsl:param name="pSponsors"/>
        <xsl:copy>
            <xsl:apply-templates select="node()[1]|@*"/>
        </xsl:copy>
        <xsl:apply-templates select="following-sibling::node()[1]">
            <xsl:with-param name="pSectionId" select="$pSectionId"/>
            <xsl:with-param name="pSponsors" select="$pSponsors"/>
        </xsl:apply-templates>
    </xsl:template>
    <xsl:template match="sectionid">
        <xsl:param name="pSectionId"/>
        <xsl:param name="pSponsors"/>
        <xsl:apply-templates select="following-sibling::node()[1]">
            <xsl:with-param name="pSectionId" select="."/>
            <xsl:with-param name="pSponsors" select="$pSponsors"/>
        </xsl:apply-templates>
    </xsl:template>
    <xsl:template match="sponsor">
        <xsl:param name="pSectionId"/>
        <xsl:param name="pSponsors"/>
        <xsl:apply-templates select="following-sibling::node()[1]">
            <xsl:with-param name="pSectionId" select="$pSectionId"/>
            <xsl:with-param name="pSponsors" select="."/>
        </xsl:apply-templates>
    </xsl:template>
    <xsl:template match="sponsor[preceding-sibling::node()[1]/self::sponsor]">
        <xsl:param name="pSectionId"/>
        <xsl:param name="pSponsors"/>
        <xsl:apply-templates select="following-sibling::node()[1]">
            <xsl:with-param name="pSectionId" select="$pSectionId"/>
            <xsl:with-param name="pSponsors" select="$pSponsors|."/>
        </xsl:apply-templates>
    </xsl:template>
    <xsl:template match="amendment">
        <xsl:param name="pSectionId"/>
        <xsl:param name="pSponsors"/>
        <xsl:copy>
            <xsl:apply-templates select="@*"/>
            <xsl:copy-of select="$pSectionId|$pSponsors"/>
            <xsl:apply-templates select="node()[1]"/>
        </xsl:copy>
        <xsl:apply-templates select="following-sibling::node()[1]">
            <xsl:with-param name="pSectionId" select="$pSectionId"/>
            <xsl:with-param name="pSponsors" select="$pSponsors"/>
        </xsl:apply-templates>
    </xsl:template>
</xsl:stylesheet>

Output:

<root>
    <amendment>
        <sectionid>A</sectionid>
        <sponsor>john</sponsor>
        <sponsor>paul</sponsor>
        <id>1</id>
        <text>some text</text>
    </amendment>
    <amendment>
        <sectionid>B</sectionid>
        <sponsor>peter</sponsor>
        <id>5</id>
        <text>some text</text>
    </amendment>
    <amendment>
        <sectionid>B</sectionid>
        <sponsor>peter</sponsor>
        <id>4</id>
        <text>some text</text>
    </amendment>
    <amendment>
        <sectionid>B</sectionid>
        <sponsor>max</sponsor>
        <id>6</id>
        <text>some text</text>
    </amendment>
    <amendment>
        <sectionid>B</sectionid>
        <sponsor>max</sponsor>
        <id>7</id>
        <text>some text</text>
    </amendment>
</root>

Note: Fine grained traversal. Linear complexity.

Edit: New input sample.

查看更多
登录 后发表回答