XSLT:Break up tag structure at specic tag -
i have xml file processed using xslt 2.0 can contain tag adhering dtd fragment
<!element p (#pcdata|foo|bar|table)* >
that input file (just 1 of possible variants)
<p>foobar <table attr1="a" attr2="b">...</table> <foo fooattr="foo">fdaghd</foo><bar>something</bar>sometext <table attr1="b">...</table> </p>
i need convert (where namespace must preserved, no usage of copy or copy-of these creates xmlns=""
attributes)
<p>foobar</p> <table attr1="a" attr2="b">...</table> <p> <foo fooattr="foo">fdaghd</foo> <bar>something</bar> sometext </p> <table attr1="b">...</table>
that "split" <p>
-tag whenever <table>
-tag found , continue <p>
after (if there children left).
please note valid input example
<p><table attr1="a" attr2="b">...</table></p>
which should converted into
<table attr1="a" attr2="b">...</table>
and valid input example
<p>bbbb<foo>aaaa</foo></p>
which should not converted @ all, output should be
<p>bbbb<foo>aaaa</foo></p>
the xslt have far includes this
<xsl:template match="p[table]"> <xsl:call-template name="split-paragraph"> <xsl:with-param name="tables" select="table"/> </xsl:call-template> </xsl:template> <xsl:template name="split-paragraph"> <xsl:param name="tables"/> <xsl:if test="$tables"> <xsl:for-each select="$tables[1]"> <xsl:if test="not(preceding-sibling::node//table)"> <p><xsl:apply-templates select="preceding-sibling::node()[not(table)]"/></p> </xsl:if> <xsl:apply-templates select="."/> <xsl:if test="not(following-sibling::node()//table)"> <p><xsl:apply-templates select="following-sibling::node[. << following-sibling::node()[not(name()='table')][1]]"/></p> </xsl:if> <xsl:call-template name="split-paragraph"> <xsl:with-param name="tables" select="$tables[position() > 1]"/> </xsl:call-template> </xsl:for-each> </xsl:if> </xsl:template> <xsl:template match="table"> <xsl:element name="table"> <xsl:apply-templates select="attribute()"/> <xsl:apply-templates select="node()"/> </xsl:element> </xsl:template> <xsl:template match="element()"><xsl:copy/></xsl:template>
which applied to
<p>foo <table attr1="gazonk"><a>bar</a></table> <bar>xyzzy</bar> <table attr2="2"><b>fie</b></table> shfjkdashndk </p>
generates
<p>foo</p> <table attr1="gazonk"> <a>bar</a> </table> <p> </p> <p>foo <table attr1="gazonk"> <a>bar</a> </table> <bar>xyzzy</bar> </p> <table attr2="2"> <b>fie</b> </table> <p>shfjkdashndk</p>
which not desired output. want this
<p>foo</p> <table attr1="gazonk"> <a>bar</a> </table> <p><bar>xyzzy</bar></p> <table attr2="2"> <b>fie</b> </table> <p>shfjkdashndk</p>
if using xslt 1.0, can treat grouping problem. grouping not-table elements together, grouping being done on number of table elements precede nodes group. using muenchian grouping, define key so
<xsl:key name="group" match="p/node()[not(self::table)]" use="concat(generate-id(..), '-', count(preceding-sibling::table))" />
(the generate-id()
here cope multiple p elements in xml)
you start off having template match p elements, skip on element, them select children, using "mode" indicate special processing (and avoid having 2 templates match same node)
<xsl:template match="p"> <xsl:apply-templates mode="group" /> </xsl:template>
you need template match first non-table element occurs in each group, add p tag around , other items in group, so
<xsl:template match="node()[generate-id() = generate-id(key('group', concat(generate-id(..), '-', count(preceding-sibling::table)))[1])]" mode="group"> <p> <xsl:apply-templates select="key('group', concat(generate-id(..), '-', count(preceding-sibling::table)))"/> </p> </xsl:template>
(note, xslt using identity template output other nodes output)
matching table more straight-forward:
<xsl:template match="table" mode="group"> <xsl:apply-templates select="." /> </xsl:template>
finally, need template match other nodes not picked previous 2 templates, ignore them (as previous "group" template have outputted them. use of "mode" becomes apparent)
<xsl:template match="node()" mode="group" />
try xslt
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/xsl/transform" version="1.0"> <xsl:output indent="yes"/> <xsl:strip-space elements="*"/> <xsl:key name="group" match="p/node()[not(self::table)]" use="concat(generate-id(..), '-', count(preceding-sibling::table))" /> <xsl:template match="p"> <xsl:apply-templates mode="group" /> </xsl:template> <xsl:template match="node()[generate-id() = generate-id(key('group', concat(generate-id(..), '-', count(preceding-sibling::table)))[1])]" mode="group"> <p> <xsl:apply-templates select="key('group', concat(generate-id(..), '-', count(preceding-sibling::table)))"/> </p> </xsl:template> <xsl:template match="table" mode="group"> <xsl:apply-templates select="." /> </xsl:template> <xsl:template match="node()" mode="group" /> <xsl:template match="@*|node()" > <xsl:copy> <xsl:apply-templates select="@*|node()"/> </xsl:copy> </xsl:template> </xsl:stylesheet>
edit: cope namespaces, in xslt 1.0, have replace instances of p
in xpath expressions *[local-name() = 'p']
, , table. going have use xsl:element create new p element same namespace.
try xslt:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/xsl/transform" version="1.0"> <xsl:output indent="yes"/> <xsl:strip-space elements="*"/> <xsl:key name="group" match="*[local-name() = 'p']/node()[not(self::*[local-name() = 'table'])]" use="concat(generate-id(..), '-', count(preceding-sibling::*[local-name() = 'table']))" /> <xsl:template match="*[local-name() = 'p']"> <xsl:apply-templates mode="group" /> </xsl:template> <xsl:template match="node()[generate-id() = generate-id(key('group', concat(generate-id(..), '-', count(preceding-sibling::*[local-name() = 'table'])))[1])]" mode="group"> <xsl:element name="p" namespace="{namespace-uri(..)}"> <xsl:apply-templates select="key('group', concat(generate-id(..), '-', count(preceding-sibling::*[local-name() = 'table'])))"/> </xsl:element> </xsl:template> <xsl:template match="*[local-name() = 'table']" mode="group"> <xsl:apply-templates select="." /> </xsl:template> <xsl:template match="node()" mode="group" /> <xsl:template match="@*|node()" > <xsl:copy> <xsl:apply-templates select="@*|node()"/> </xsl:copy> </xsl:template> </xsl:stylesheet>
edit 2.0
here xslt 2.0 solution, can use xsl:for-each-group group elements. in case, grouping adjacent elements together, depending on whether table or not.
also not use of wildcards in checking namespaces (which cope whether namespaces present or not)
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/xsl/transform" version="2.0"> <xsl:output indent="yes"/> <xsl:strip-space elements="*"/> <xsl:template match="*:p"> <xsl:for-each-group select="node()" group-adjacent="boolean(self::*:table)"> <xsl:choose> <xsl:when test="self::*:table"> <xsl:apply-templates select="current-group()" /> </xsl:when> <xsl:otherwise> <xsl:element name="p" namespace="{namespace-uri(..)}"> <xsl:apply-templates select="current-group()" /> </xsl:element> </xsl:otherwise> </xsl:choose> </xsl:for-each-group> </xsl:template> <xsl:template match="@*|node()" > <xsl:copy> <xsl:apply-templates select="@*|node()"/> </xsl:copy> </xsl:template> </xsl:stylesheet>
Comments
Post a Comment