[XSL-LIST Mailing List Archive Home] [By Thread] [By Date] [Recent Entries] [Reply To This Message] Re: pruning nodes not in xpath list
Hi Cliff, > I would like to be able to generate a sytlesheet that will produce > output documents that exclude all nodes that are not matched by any > of the xpath expressions. The first thing is to work out a stylesheet that does what you want with the expressions hard-coded into it... > From an algorithm point of view, one solution is to create a > node-set collection of all nodes that should be copied to the output > document. For each xpath, find the terminal nodes and add them and > all of their ancestor elements to the node-set collection. After the > node-set collection is created, visit each node in source tree in > document order -- if the node exists in the node-set, use > <xsl:copy/>. This would ensure that each node only gets copied once, > and in the document-order so the tree is maintained. I cannot figure > out a way to implement this algorithm using XSLT. This could be done > with a DOM (Document), but I don't want to have to implement all of > the code to handle "xpath" expressions. OK, so first work out which nodes have been selected by the expressions: <xsl:variable name="selected-nodes" select="//product/@sku | //product/cost"/> Then to work out which nodes should therefore be copied - that's all those nodes that are selected, plus their ancestors: <xsl:variable name="copied-nodes" select="$selected-nodes | $selected-nodes/ancestor::*"/> Now, starting from the top (the document element is always the first in the list of copied nodes), we want to work through the document. If the element is in the list of selected nodes, then we just want to copy it completely. Otherwise, we want to copy the element, copy any attributes that are in the list of selected nodes, and apply templates to any of its children that are in the list of nodes to be copied. Now, working out whether a node is in a set of nodes involves a bit of set manipulation - if you count how many nodes there are in the set that's the union of the node and the node set, and it's the same as the number of nodes in the node set, then the node has to be in the node set. So to work out whether the context node is in the set of $selected-nodes, I can use: count(.|$selected-nodes) = count($selected-nodes) Since $selected-nodes and $copied-nodes are known up front, I'll create variables that indicate how many nodes are in each of them: <xsl:variable name="nselected-nodes" select="count($selected-nodes)" /> <xsl:variable name="ncopied-nodes" select="count($copied-nodes)"/> So the template looks like: <xsl:template match="*"> <xsl:choose> <!-- this node is one of the selected nodes --> <xsl:when test="count(.|$selected-nodes) = $nselected-nodes"> <xsl:copy-of select="."/> </xsl:when> <xsl:otherwise> <xsl:copy> <!-- copy attributes that are selected nodes --> <xsl:copy-of select="@*[count(.|$selected-nodes) = $nselected-nodes]"/> <!-- apply templates to nodes that are to be copied --> <xsl:apply-templates select="node()[count(.|$copied-nodes) = $ncopied-nodes]"/> </xsl:copy> </xsl:otherwise> </xsl:choose> </xsl:template> And there you have it. That stylesheet will take the input you specified and give the output you specified. Now all you have to do is create a stylesheet that creates it. In fact most of it is static, so you could bundle it out to a separate stylesheet and include it rather than create it dynamically. The only thing that isn't static is the creation of the $selected-nodes variable: <xsl:variable name="selected-nodes" select="//product/@sku | //product/cost"/> Assuming that you're using oxsl as the namespace prefix for your XSLT namespace alias, then you need to create an oxsl:variable with a name attribute equal to selected-nodes: <oxsl:variable name="selected-nodes"> ... </oxsl:variable> The select attribute is created dynamically by iterating over the various expressions that you have (I'll assume their held in an $exprs variable): <oxsl:variable name="selected-nodes"> <xsl:attribute name="select"> <xsl:for-each select="$exprs"> <xsl:value-of select="." /> <xsl:if test="position() != last()"> | </xsl:if> </xsl:for-each> </xsl:attribute> </oxsl:variable> You should be able to create the rest of the stylesheet very straight-forwardly. I hope that helps, Jeni --- Jeni Tennison http://www.jenitennison.com/ XSL-List info and archive: http://www.mulberrytech.com/xsl/xsl-list
|
PURCHASE STYLUS STUDIO ONLINE TODAY!Purchasing Stylus Studio from our online shop is Easy, Secure and Value Priced! Download The World's Best XML IDE!Accelerate XML development with our award-winning XML IDE - Download a free trial today! Subscribe in XML format
|