November 1, 2014
Hot Topics:
RSS RSS feed Download our iPhone app

Modeling One-To-Many Relationships With XML

  • January 28, 2003
  • By Jeff Ryan
  • Send Email »
  • More Articles »

Intra-Document Relationship

When implementing our model through an Intra-Document relationship, we'll be getting a little bit closer to how we may have implemented the domain model in a relational database. Rather than a department containing employees, an employee will have a "works for" relationship to a department. In fact, an employee could have many potential relationships to various departments.

We'll take advantage of ID and IDREF data types in our DTD:

  • The ID is used to uniquely identify a node in a document. You can think of the ID as being a "key" to a node.
  • An IDREF is used to reference another node in a document. You can think of an IDREF as a "foreign key" to associate a node with another node.
<?xml version="1.0" encoding="UTF-8"?><!ELEMENT Company (Departments, Employees)><!ELEMENT Department (Name)><!ATTLIST Department  id ID #REQUIRED><!ELEMENT Departments (Department+)><!ELEMENT Employees (Employee+)><!ELEMENT Employee (Name)><!ATTLIST Employee  DepartmentRef IDREF #REQUIRED><!ELEMENT Name (#PCDATA)>

A Company will contain Departments and Employees elements. Each Departments element may contain many Department elements. Each Department has an id attribute of type ID which uniquely identifies the department.

Similarly, each Employees element may contain many Employee elements. Each Employee element has a DeparmentRef attribute of type IDREF. This is very similar to a foreign key in a relational database.

Whenever modeling a one-to-many relationship in a relational database, you put the key of the "one" as a foreign key on the side of the "many." This is exactly what we did by putting the department id as a reference on the Employee element.

A sample XML stream will help to illustrate:

<?xml version="1.0" encoding="UTF-8"?><?xml-stylesheet href="IntraDocument.xsl"; type="text/xsl"?><!DOCTYPE Company SYSTEM "IntraDocument.dtd"><Company>  <Departments>    <Department id="d1">      <Name>Enterprise Development</Name>    </Department>    <Department id="d2">      <Name>Foundation Services</Name>    </Department>  </Departments>  <Employees>    <Employee DepartmentRef="d1">      <Name>Jeff</Name>    </Employee>    <Employee DepartmentRef="d1">      <Name>Mike</Name>    </Employee>    <Employee DepartmentRef="d2">      <Name>Sam</Name>    </Employee>  </Employees></Company>

Rather than Jeff and Mike being contained in the Enterprise Development Department, they now just reference this department's ID via the DepartmentRef attribute.

The stylesheet from the containment example will only need a simple modification to the Employee template now to display the employee.

  <xsl:template match="Employee">    <tr>      <td>        <xsl:value-of select="Name"/>      </td>      <td>        <xsl:value-of select="id(@DepartmentRef)/Name"/>      </td>    </tr>  </xsl:template>

The id() function in XPath is used to efficiently find ID nodes in an XML document. We find the Department node associated with a given employee by supplying its key as specified in the @DepartmentRef attribute.

Note that if we didn't have a DTD for this XML document, the id() function wouldn't produce the desired results. The following XPath expression could have been used in its place, but it wouldn't be nearly as efficient:
//Department[@id = current()/@DepartmentRef]. This expression is searching through all Department nodes, looking for one with an id attribute equal to the DepartmentRef attribute of the current Employee node.

Inter-Document Relationship

In a relational database, the Department and Employee tables might live in different filesystems or tablespaces. In XML, the Department and Employee nodes may live in different documents. In fact, each employee or department may even live in its own document.

We'll define two DTDs for this example: one for the Departments and one for the Employees.

<?xml version="1.0"; encoding="UTF-8"?><!ELEMENT Departments (Department+)><!ELEMENT Department (Name)><!ATTLIST Department  id ID #REQUIRED><!ELEMENT Name (#PCDATA)>

The department's DTD is very simple. Departments nodes may contain many Department nodes. Each Department has an id attribute and a Name element.

The following document is a sample Departments stream:

<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE Departments SYSTEM "Departments.dtd"><Departments>  <Department id="d1">    <Name>Enterprise Development</Name>  </Department>  <Department id="d2">    <Name>Foundation Services</Name>  </Department></Departments>

Let's pay close attention to the Employee DTD:

<?xml version="1.0" encoding="UTF-8"?><!ELEMENT Employees (Employee+)><!ELEMENT Employee (Department, Name)><!ELEMENT Department EMPTY><!ATTLIST Department  href CDATA #REQUIRED><!ELEMENT Name (#PCDATA)>

The Employees element may contain many Employee elements. Each Employee must contain a Department and a Name element. The Department element must be empty. It has an href attribute which will be used to specify the document and id of that department. The Department element is really a "proxy" element that points to the real department element.

Here is a sample Employees stream:

<?xml-stylesheet href="InterDocument.xsl"; type="text/xsl"?><!DOCTYPE Employees SYSTEM "Employees.dtd"><Employees>  <Employee>    <Department href=";Departments.xml#d1"/>    <Name>Jeff</Name>  </Employee>  <Employee>    <Department href="Departments.xml#d1"/>    <Name>Mike</Name>  </Employee>  <Employee>    <Department href="Departments.xml#d2"/>    <Name>Sam</Name>  </Employee></Employees>

The href attribute should look quite familiar to HTML developers. This is used to link to different documents and different locations within documents. In XML, it can serve the same function.

The href attribute as listed here is an XPointer expression. XPointer is a language for identifying fragments of documents referenced in links or included in other documents. Rather than using an XQuery engine to process this expression, we'll use standard XPath features available in XSLT.

  <xsl:template match="Employee">    <xsl:variable name="document"                  select="document(substring-before(                  Department/@href,'#'))"/>    <xsl:variable name="id"                  select="substring-after(Department/@href,'#')"/>    <tr>      <td><xsl:value-of select="Name"/></td>      <!-for-each changes context node for id function-->      <xsl:for-each select="$document">        <td><xsl:value-of select="id($id)/Name"/></td>      </xsl:for-each>    </tr>  </xsl:template>

In the Employee template, a document and an id variable are created from the Department href attribute. Remember that this Department is the "proxy" to the real department. The document() function is used to reference the Departments document. The substring-before() and substring-after() functions come in handy to parse the href into the document and id portions of the string.

The <xsl:for-each> statement is used in a peculiar way. It's not being used to iterate through a node set, but to change the context node to the root node of the departments document. Then the id() function is used to reference the actual Department node.

Note that because we only have a single department document, a more efficient implementation would be to pass the department document name as a parameter to the stylesheet and have a global document variable. The implementation here allows for departments to exist in multiple documents.

Also, note that the main template changed slightly from the prior examples. Please see the Code Examples link at the bottom of the article for the entire stylesheet.





Page 2 of 3



Comment and Contribute

 


(Maximum characters: 1200). You have characters left.

 

 


Sitemap | Contact Us

Rocket Fuel