Move specific nodes to a node A and only the ones between the two occurrences of a t node
I want to fix a XML file (generated from a text file) with XSLT or XQuery, so I can do something useful with it. This is the input XML file:
<root>
<main><id>100&l开发者_JS百科t;/id></main>
<child>1</child>
<child>2</child>
<main><id>200</id></main>
<child>1</child>
<child>2</child>
<child>3</child>
<main><id>300</id></main>
<child>1</child>
</root>
This is the output I like to have
<root>
<main>
<id>100</id>
<children>
<child>1</child>
<child>2</child>
</children>
</main>
<main>
<id>200</id>
<children>
<child>1</child>
<child>2</child>
<child>3</child>
</children>
</main>
<main>
<id>300</id>
<children>
<child>1</child>
</children>
</main>
</root>
What you want is a simple rearrangement of the input XML based on intersection of two node-sets respect to the main
context:
A all preceding nodes of the next
main
, or all following nodes of the lastmain
node"following-sibling::main[1] /preceding-sibling::node() | following-sibling::*[count(current()/following::main[1])=0]"
B all following nodes of the current
main
node"following-sibling::*"
In [XSLT 1.0] intersection is represented as:
"$B[count(.|$A)=count($A)]"
In [XSLT 2.0] you use the intersect
operator:
"$A intersect $B"
The following transform is an example of XSLT 1.0 solution
Note This transform makes use the well known Identity transformation by including it from external file identity.xsl
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output indent="yes"/>
<xsl:include href="identity.xsl"/>
<xsl:template match="child"/>
<xsl:template match="main">
<xsl:variable name="A"
select="
following-sibling::main[1]
/preceding-sibling::node()
|
following-sibling::*
[count(current()/following::main[1])=0]"/>
<xsl:variable name="B" select="following-sibling::*"/>
<xsl:copy>
<xsl:apply-templates select="@*|id"/>
<children>
<xsl:apply-templates select="$B[count(.|$A) = count($A)]"
mode="copy"/>
</children>
</xsl:copy>
</xsl:template>
<xsl:template match="child" mode="copy">
<xsl:copy-of select="."/>
</xsl:template>
</xsl:stylesheet>
Here is the solution with XSLT 1.0. I've commented as much as possible. I used a tree walking solution. Tree walking is not the easiest thing (for beginners) in XSLT. For a general explanation of this approach look here: Dave Pawson XSLT FAQ
Inside the <root>
node the XSLT goes/walks step by step through the direct children of <root>
and generates a new block of <main>...</main>
(on every <main>
node) or <children>...</children>
(<child>
which has a direct <main>
parent) on some nodes.
XSLT:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="xml" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="root">
<xsl:copy>
<!-- copy any attributes in root -->
<xsl:apply-templates select="@*"/>
<!-- walk through and copy nodes which are preceding main (if there are any?) -->
<xsl:apply-templates select="*[1][not(self::main)]" mode="walker"/>
<!-- start walking mode with first main -->
<xsl:apply-templates select="*[self::main][1]" mode="walker"/>
</xsl:copy>
</xsl:template>
<!-- do not copy main, it will be copied later in walker mode -->
<xsl:template match="root/main"/>
<xsl:template match="main" mode="walker">
<main>
<!-- copy any attributes of main -->
<xsl:apply-templates select="@*"/>
<!-- copy all children of main -->
<xsl:apply-templates/>
<!-- begin walking and copying the next following-sibling node IF it is not main -->
<xsl:apply-templates select="following-sibling::*[1][not(self::main)]" mode="walker"/>
</main>
<!-- search the next main start and generate a new main block -->
<xsl:apply-templates select="following-sibling::*[self::main][1]" mode="walker"/>
</xsl:template>
<!-- every child which has main as it first preceding-sibling generates a new children block -->
<xsl:template match="child[preceding-sibling::*[1][self::main]]" mode="walker">
<children>
<xsl:apply-templates select="."/>
<!-- continue walking (through children) if the following-sibling node is NOT main -->
<xsl:apply-templates select="following-sibling::*[1][not(self::main)]" mode="walker"/>
</children>
</xsl:template>
<!-- copy all other nodes in walking mode -->
<xsl:template match="*" mode="walker">
<!-- copy this node and children -->
<xsl:apply-templates select="."/>
<!-- walk to the next following-sibling IF it is not main -->
<xsl:apply-templates select="following-sibling::*[1][not(self::main)]" mode="walker"/>
</xsl:template>
<!-- Default: Copy everything -->
<xsl:template match="@*|node()">
<xsl:copy>
<xsl:apply-templates select="@*|node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
produces the desired output:
<?xml version="1.0" encoding="UTF-8"?>
<root>
<main>
<id>100</id>
<children>
<child>1</child>
<child>2</child>
</children>
</main>
<main>
<id>200</id>
<children>
<child>1</child>
<child>2</child>
<child>3</child>
</children>
</main>
<main>
<id>300</id>
<children>
<child>1</child>
</children>
</main>
</root>
精彩评论