Checking values of 2 different xml documents again

2019-06-02 21:34发布

问题:

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>

回答1:

<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:

  1. 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()"/>


回答2:

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.