Update to the XSLT: - corrected bug where text nodes were not being copied to the result tree. - it's now possible to change multiple nodes with a single redefinition. - because of the last point the syntax is now: <redef match="xpath">new node</redef> - (minor) xpath validation test before making the evaluation.
Regards, João ________________________________ From: johnny bravo <[email protected]> To: [email protected] Sent: Thursday, March 19, 2009 5:24:46 AM Subject: [rng-users] Changing definitions Hi everyone, Coming from working with XML Schema I can't praise Relax NG enough, but I really miss a feature (not available on XML Schema either) which is the possibility of changing one or more nodes contained inside a definition. Let me explain with an example, starting with the input xml: <e1> <e2>foo</e2> <e3> <!-- e3 is similar to e1 but we want to enforce that e2 must be empty --> <e2/> </e3> </e1> The Relax way of validating this input: ------------ --------- --------- --------- ---- <element name="e1" xmlns="http://relaxng. org/ns/structure /1.0"> <element name="e2><text/ ></element> <element name="e3"> <element name="e2"><empty/ ></element> </element> </element> ------------ --------- --------- --------- ---- What I wanted for this case is to define e2 separately and redefine its content type for e3, locating the element inside the definition through xpath: ------------ --------- --------- --------- --------- ------ <grammar xmlns="http://relaxng. org/ns/structure /1.0"> <start> <element name="e1"> <ref name="d1" /> <element name="e3"> <ref name="d1"> <set node="element[ @name='e2' ]"><empty /></set> <!-- change e2 content type --> </ref> </element> </element> </start> <define name="d1"> <element name="e2"><text /></element> </define> </grammar> ------------ --------- --------- --------- --------- ------ It ends up being more verbose for this small example, but it's trivial to find other cases where this comes in handy. Now, this kind of capability probably goes against the KISS principle that rules the Relax NG specs and I fully understand and agree on why it's not supported. In any case it can be achieved through other means either programatically which is cumbersome or through XSLT, so I wrote a small stylesheet that emulates this behaviour by basically creating a clone definition and changing the values for the nodes (and I mean any kind of node, including attributes) that match the xpath expressions. The stylesheet, input file, schema and transformed schema go in attachment. I used Saxon 9 for the XSLT processing. If you have any comments or suggestions for the stylesheet I'd be glad to hear them. Cheers, João C. Morais
<?xml version="1.0" encoding="ISO-8859-1"?> <xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:saxon="http://saxon.sf.net/" xmlns="http://relaxng.org/ns/structure/1.0" xpath-default-namespace="http://relaxng.org/ns/structure/1.0" exclude-result-prefixes="saxon"> <xsl:template match="*"> <xsl:copy> <xsl:copy-of select="@*" /> <xsl:apply-templates /> </xsl:copy> </xsl:template> <xsl:template match="grammar"> <xsl:copy> <xsl:apply-templates select="*|@*" /> <xsl:for-each select="//ref[count(redef) > 0]"> <xsl:variable name="entries"> <xsl:for-each select="redef"> <!-- test for (possibly) valid xpath --> <xsl:if test="string-length(translate(@match,'/','')) > 0"> <xsl:element name="entry"> <xsl:for-each select="saxon:evaluate(concat('//defi...@name=''',../@name,''']/',@match))"> <xsl:element name="node-id"> <xsl:value-of select="generate-id()" /> </xsl:element> </xsl:for-each> <xsl:element name="value"> <xsl:copy-of select="element()|text()[normalize-space() != '']" /> </xsl:element> </xsl:element> </xsl:if> </xsl:for-each> </xsl:variable> <define name="{saxon:generate-id(current())}"> <xsl:apply-templates select="//defi...@name=current()/@name]/*" mode="copy-or-redef"> <xsl:with-param name="entries" select="$entries" /> </xsl:apply-templates> </define> </xsl:for-each> </xsl:copy> </xsl:template> <xsl:template match="ref[count(redef) > 0]"> <ref name="{saxon:generate-id(.)}" /> </xsl:template> <xsl:template match="*" mode="copy-or-redef"> <xsl:param name="entries" /> <xsl:variable name="entry" select="$entries/entry[node-id=saxon:generate-id(current())]" /> <xsl:choose> <xsl:when test="$entry and count(. | ../@*) = count(../@*)"> <!-- node is an attribute node --> <xsl:attribute name="{name()}" select="$entry/value/text()" /> </xsl:when> <xsl:otherwise> <xsl:copy> <xsl:copy-of select="@*" /> <xsl:choose> <xsl:when test="$entry and self::*"> <!-- node is an element node --> <xsl:copy-of select="$entry/value/node()" /> </xsl:when> <xsl:otherwise> <!-- proceed with the copy --> <xsl:apply-templates mode="copy-or-redef"> <xsl:with-param name="entries" select="$entries" /> </xsl:apply-templates> </xsl:otherwise> </xsl:choose> </xsl:copy> </xsl:otherwise> </xsl:choose> </xsl:template> </xsl:stylesheet>
