I'm working with 2 XML documents, and trying to get a value from one document into the other if a variable is matched. The first XML document is a converted spreadsheet formatted like:
<Doc1>
<row>
<cell>VA15</cell>
<cell>wr23</cell>
</row>
<row>
<cell>VA45</cell>
<cell>wr27</cell>
</row> <row>
<cell>VA78</cell>
<cell>wr24</cell>
</row>
</Doc1>
The second XML document is a longer one inside of which there's an id
element matching one part of the spreadsheet:
<Doc2>
<p> text text text
<id>wr23</id>
</p>
</Doc2>
I'm trying with my xslt transformation to test to see if the id
element matches the value of a cell
in doc1 it pulls the value of the preceding cell
. In this case I'd like the xslt transformation to output "VA15". I've tried various permutations of the following code without success, does anyone have any ideas?
<xsl:for-each select="document('Doc1.xml')//row">
<xsl:if test="/cell=//id'">
<xsl:value-of select="/preceding-sibling::cell"/>
</xsl:if>
</xsl:for-each>
<xsl:for-each select="document('Doc1.xml')//row">
<xsl:if test="/cell=//id'">
<xsl:value-of select="/preceding-sibling::cell"/>
</xsl:if>
</xsl:for-each>
A number of problems:
- None of the two documents has a top element named
cell
-- instead of absolute expression you must use a relative one:
.....
cell = //id
and
preceding-sibling::cell
because a document node doesn't have siblings.
.2. The document that has element named cell
doesn't have elements named id
. When an XPath expression references more than one document, all documents but one must be explicitly referenced. With all corrections, your code becomes something like this:
<xsl:variable name="vthisDoc" select="/"/>
<xsl:for-each select="document('Doc1.xml')//row">
<xsl:if test="cell=$vthisDoc//id'">
<xsl:value-of select="preceding-sibling::cell"/>
</xsl:if>
</xsl:for-each>
Finally, all this can be written shortly as:
<xsl:copy-of select=
"document('Doc1.xml')//row[cell=$vthisDoc//id]/preceding-sibling::cell[1]/text()"/>
Building on Dimitre's answer, you can abstract this "look up an IDREF" pattern out into a separate template using key()
(for fast lookups) and call-template
:
id-lookup.xsl
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:param name="id-lookup-doc">Doc1.xml</xsl:param>
<xsl:variable name="id-to-value-doc" select="document($id-lookup-doc)"/>
<xsl:key name="value-for-id"
match="/Doc1/row/cell[1]"
use="../cell[2]" />
<xsl:template name="value-for-id">
<xsl:param name="id"/>
<!-- for-each is just to change the context
to limit results from key() to the lookup document -->
<xsl:for-each select="$id-to-value-doc">
<xsl:value-of select="key('value-for-id', $id)"/>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
replace-id-with-value.xsl
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:import href="id-lookup.xsl"/>
<xsl:template match="node()|@*">
<xsl:copy>
<xsl:apply-templates select="node()|@*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="id">
<val>
<xsl:apply-templates select="@*"/>
<xsl:call-template name="value-for-id">
<xsl:with-param name="id" select="text()"/>
</xsl:call-template>
</val>
</xsl:template>
</xsl:stylesheet>
When you run this through an xslt processor, you get the document with all <id>
elements replaced with <val>
and the corresponding value.
Using xsltproc
, you would run this like so:
xsltproc id-with-value.xsl Doc2.xml myOtherDoc.xml
You can even change the lookup document using an xslt param:
xsltproc --stringparam id-lookup-doc MyOtherSetOfIdsAndValues.xml id-with-value.xsl Doc2.xml
Whatever XSLT processor you are using will have some way of specifying a template param.