user2035965
user2035965

Reputation: 59

Getting attribute from linked data XSLT

Say I have the code below in my XML file and I would like to display the following infomation in rows: creationDate - serviceName - problemCode - division.

<foi:serviceInfo rdf:ID="SI1">
 <foi:serviceName>Sewer</foi:serviceName>
 <foi:problemCode>SI1</foi:problemCode>
 <foi:division>Water</foi:division>
</foi:serviceInfo>

<foi:serviceInfo rdf:ID="SI2">
 <foi:serviceName>Recycling</foi:serviceName>
 <foi:problemCode>SI2</foi:problemCode>
 <foi:division>Solid Waste</foi:division>
</foi:serviceInfo>

<foi:serviceRequest rdf:ID="R1">
       <foi:creationDate>29 03 2013</foi:creationDate>
       <foi:servicing rdf:resource="#SI1"/>
</foi:serviceRequest>
<foi:serviceRequest rdf:ID="R2">
       <foi:creationDate>29 06 2013</foi:creationDate>
       <foi:servicing rdf:resource="#SI2"/>
</foi:serviceRequest>

I am able to display the information from serviceRequest in rows, however, I do not know how to link the resource in foi:servicing to the ID of serviceInfo (in order to get at the attributes contained in serviceInfo, and subsequently display it).

Upvotes: 0

Views: 131

Answers (3)

Dimitre Novatchev
Dimitre Novatchev

Reputation: 243529

This shorter transformation uses a key for X-referencing and produces the wanted results in a table:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
 xmlns:foi="some:foi" xmlns:rdf="some:rdf" exclude-result-prefixes="foi rdf">
 <xsl:output omit-xml-declaration="yes" indent="yes"/>
 <xsl:strip-space elements="*"/>

 <xsl:key name="kSIById" match="foi:serviceInfo" use="concat('#',@rdf:ID)"/>

 <xsl:template match="/*">
  <table border="1"><xsl:apply-templates/></table>
 </xsl:template>
 <xsl:template match="/*/*" priority="-1"/>

 <xsl:template match="/*/foi:serviceRequest">
  <tr>
   <td><xsl:apply-templates select="foi:creationDate"/></td>
   <xsl:apply-templates select="key('kSIById', foi:servicing/@rdf:resource)/*"/>
  </tr>
 </xsl:template>

 <xsl:template match="foi:serviceInfo/*">
   <td><xsl:value-of select="."/></td>
 </xsl:template>
</xsl:stylesheet>

When applied on the provided source XML (wrapped in a single top element and with namespaces defined):

<root xmlns:foi="some:foi" xmlns:rdf="some:rdf">
  <foi:serviceInfo rdf:ID="SI1">
    <foi:serviceName>Sewer</foi:serviceName>
    <foi:problemCode>SI1</foi:problemCode>
    <foi:division>Water</foi:division>
  </foi:serviceInfo>

  <foi:serviceInfo rdf:ID="SI2">
    <foi:serviceName>Recycling</foi:serviceName>
    <foi:problemCode>SI2</foi:problemCode>
    <foi:division>Solid Waste</foi:division>
  </foi:serviceInfo>

  <foi:serviceRequest rdf:ID="R1">
    <foi:creationDate>29 03 2013</foi:creationDate>
    <foi:servicing rdf:resource="#SI1"/>
  </foi:serviceRequest>
  <foi:serviceRequest rdf:ID="R2">
    <foi:creationDate>29 06 2013</foi:creationDate>
    <foi:servicing rdf:resource="#SI2"/>
  </foi:serviceRequest>
</root>

produces the wanted, correct result:

<table border="1">
   <tr>
      <td>29 03 2013</td>
      <td>Sewer</td>
      <td>SI1</td>
      <td>Water</td>
   </tr>
   <tr>
      <td>29 06 2013</td>
      <td>Recycling</td>
      <td>SI2</td>
      <td>Solid Waste</td>
   </tr>
</table>

Upvotes: 1

JLRishe
JLRishe

Reputation: 101730

The preferred approach to cross-referencing data in XSLT is to use an <xsl:key>. The Following XSLT should do it:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
                xmlns:foi="foifoifoi" xmlns:rdf="rdfrdf"
                exclude-result-prefixes="foi rdf">
  <xsl:output method="xml" indent="yes" omit-xml-declaration="yes"/>
  <xsl:key name="kService" match="foi:serviceInfo" use="@rdf:ID"/>

  <xsl:template match="/*">
    <div>
      <xsl:apply-templates select="foi:serviceRequest" />
    </div>
  </xsl:template>

  <xsl:template match="foi:serviceRequest">
    <xsl:variable name="referenceId"
                  select="substring(foi:servicing/@rdf:resource, 2)" />
    <xsl:variable name="info" select="key('kService', $referenceId)[1]"/>
    <div>
      <xsl:value-of select="foi:creationDate"/>
      <xsl:apply-templates select="$info/*" />
    </div>
  </xsl:template>

  <xsl:template match="foi:serviceInfo/*">
    <xsl:value-of select="concat(' - ', .)"/>
  </xsl:template>
</xsl:stylesheet>

(I had to make up namespaces for foi and rdf since you didn't indicate them. Please substitute in the right URIs). When run on this XML (with a root node added):

<root xmlns:foi="foifoifoi" xmlns:rdf="rdfrdf">
  <foi:serviceInfo rdf:ID="SI1">
    <foi:serviceName>Sewer</foi:serviceName>
    <foi:problemCode>SI1</foi:problemCode>
    <foi:division>Water</foi:division>
  </foi:serviceInfo>

  <foi:serviceInfo rdf:ID="SI2">
    <foi:serviceName>Recycling</foi:serviceName>
    <foi:problemCode>SI2</foi:problemCode>
    <foi:division>Solid Waste</foi:division>
  </foi:serviceInfo>

  <foi:serviceRequest rdf:ID="R1">
    <foi:creationDate>29 03 2013</foi:creationDate>
    <foi:servicing rdf:resource="#SI1"/>
  </foi:serviceRequest>
  <foi:serviceRequest rdf:ID="R2">
    <foi:creationDate>29 06 2013</foi:creationDate>
    <foi:servicing rdf:resource="#SI2"/>
  </foi:serviceRequest>
</root>

This produces:

<div>
  <div>29 03 2013 - Sewer - SI1 - Water</div>
  <div>29 06 2013 - Recycling - SI2 - Solid Waste</div>
</div>

Main points:

  • Use of an xsl:key to allow locating foi:serviceInfo by a certain ID.
  • Use of the key() function to find a related foi:serviceInfo by ID. The [1] at the end limits this to the first match. I wasn't sure if there's a possibility there could be more than one match, but is there?
  • A template to render any child of foi:serviceInfo as a hyphen plus the element value.

Upvotes: 0

TddOrBust
TddOrBust

Reputation: 402

This is a bit of an inelegant solution and would require extra work for extending to data sets that differ in their structure (although it will work for any number of serviceInfo and serviceRequest elements arranged in your sample data set). Its virtue is that it involves use of apply-templates rather than for-each. Using apply-templates is the best practice. Also, I didn't know the right namespaces to use so I just made some up.

This XSLT

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:foi="http://www.foi.com"
xmlns:rdf="http://www.rdf.com"
exclude-result-prefixes="foi rdf"
version="2.0">

<!-- Identity template -->
<xsl:template match="@* | node()">
    <xsl:copy>
        <xsl:apply-templates select="@* | node()"/>
    </xsl:copy>
</xsl:template>
<xsl:variable name="newLine"><xsl:text>

</xsl:text></xsl:variable>
<xsl:template match="/">
    <xsl:value-of select="$newLine"/><table><xsl:value-of select="$newLine"/>
        <xsl:apply-templates select="//foi:creationDate"/>
    </table><xsl:value-of select="$newLine"/>
</xsl:template>
<xsl:template match="foi:creationDate">
    <xsl:variable name="resourceId" select="replace(../foi:servicing/@rdf:resource,'#','')"/>
    <xsl:message>BAH <xsl:value-of select="$resourceId"/></xsl:message>
    <tr><xsl:value-of select="$newLine"/>
        <td><xsl:apply-templates/></td><xsl:value-of select="$newLine"/>
        <td><xsl:apply-templates select="//foi:serviceName[parent::*[@rdf:ID=$resourceId]]"/></td><xsl:value-of select="$newLine"/>
        <td><xsl:apply-templates select="//foi:problemCode[parent::*[@rdf:ID=$resourceId]]"/></td><xsl:value-of select="$newLine"/>
        <td><xsl:apply-templates select="//foi:division[parent::*[@rdf:ID=$resourceId]]"/></td><xsl:value-of select="$newLine"/>
    </tr><xsl:value-of select="$newLine"/>
</xsl:template>
<xsl:template match="foi:serviceName">
    <xsl:apply-templates select="@* | node()" />
</xsl:template>
<xsl:template match="foi:problemCode">
    <xsl:apply-templates select="@* | node()" />
</xsl:template>
<xsl:template match="foi:division">
    <xsl:apply-templates select="@* | node()" />
</xsl:template>
</xsl:stylesheet>

When applied on this XML document: (note: I added a root element)

<root>
<foi:serviceInfo rdf:ID="SI1">
    <foi:serviceName>Sewer</foi:serviceName>
    <foi:problemCode>SI1</foi:problemCode>
    <foi:division>Water</foi:division>
</foi:serviceInfo>
<foi:serviceInfo rdf:ID="SI2">
    <foi:serviceName>Recycling</foi:serviceName>
    <foi:problemCode>SI2</foi:problemCode>
    <foi:division>Solid Waste</foi:division>
</foi:serviceInfo>
<foi:serviceRequest rdf:ID="R1">
    <foi:creationDate>29 03 2013</foi:creationDate>
    <foi:servicing rdf:resource="#SI1"/>
</foi:serviceRequest>
<foi:serviceRequest rdf:ID="R2">
    <foi:creationDate>29 06 2013</foi:creationDate>
    <foi:servicing rdf:resource="#SI2"/>
</foi:serviceRequest>
</root>

Produces the desired result:

<tr>

<td>29 03 2013</td>

<td>Sewer</td>

<td>SI1</td>

<td>Water</td>

</tr>

<tr>

<td>29 06 2013</td>

<td>Recycling</td>

<td>SI2</td>

<td>Solid Waste</td>

</tr>

</table>

A brief explanation:

  • Suppress text nodes (the default template for text nodes is to output them; we'll output them through specific templates at the end of the XSLT file)
  • Process foi:creationDate elements
  • When processing each foi:createDate element find its parent rdf:resource and store that value in a variable
  • Then process each appropriate element, found by looking through all of each in a document (that's what "//" means) whose parent's rdf:ID matches the rdf:resource (note: this will output multiple values if there are multiple elements that match the criteria... your data set had no such multiple elements; the * could be [perhaps should be] foi:serviceName, but I wanted to show what * meant -- select any element and the stuff in the [] discriminates what elements can qualify)
  • This also allows you to have children nodes (other than just text nodes) in your serviceName, etc. elements and have those processed (although right now the processing is copying to the result tree)

Upvotes: 1

Related Questions