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>

Reply via email to