7.4. Convert XML to RDF/XML

Manually coding triples as RDF/XML as we did in Example 7.1 or in Turtle as in Example 7.2 can become cumbersome, but it must be remembered that RDF was designed to be read and generated by computer. Quite often RDF models are built directly from data. Example 7.4 shows an XSLT stylesheet to transform XML content to RDF. This type of generic transformation stylesheet should be used with care: it produces an RDF file with sometimes unnatural divisions and it cannot produce RDF files that are not tree structured like the original XML file.

We are not proposing this conversion scheme as an optimal one. It is only an example of a kind of automatic conversion from XML to RDF. Usually this type of conversion is an ad-hoc process tailored to fit the original XML file and the target RDF model.

Figure 7.3. Conversion scheme from XML to RDF

A scheme for converting an XML file to an RDF file. On the left, at the top we show a tree of two element and a text nodes and the corresponding XML file at the bottom. On the right, we show the corresponding RDF graph and the XML serialization produced by the stylesheet of Example 7.4.

Conversion scheme from XML to RDF


The transformation process shown in Example 7.4 takes each element and uses it as a subject node for which it generates an rdf:Description with an arbitrary node id. For each XML attribute, an RDF attribute is produced. For a child element with only text content, it generates a predicate named after the name of the attribute or of the node with the element content filled by string value. For child elements with more complex content, we generate a predicate named after the name of the child node and add as rdf:nodeID the node id of the child node. The same process is then applied to each child node with complex content. This process is illustrated on a simple example in Figure 7.3. Breitling [13] present a similar approach for converting XML to RDF.

Example 7.5 and Example 7.6 show the RDF/XML and the corresponding turtle output applied on the cellar book description given in Example 2.2.

Example 7.4. [XML2RDF.xsl] Generic stylesheet to convert an XML file to RDF

This stylesheet takes any XML file and produces RDF triples that encode the information from the XML. The names of the RDF predicates are the ones of the XML element and attribute nodes. This type of generic conversion should be used with care as most often a more precise or selective translation process is preferable.

  1 <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE rdf:RDF [
      <!ENTITY rdf "http://www.w3.org/1999/02/22-rdf-syntax-ns#">
      <!ENTITY ns "http://www.iro.umontreal.ca/lapalme/ns#">             (1)
  5 ]>
    <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0"
        xmlns:rdf="&rdf;">
        
        <xsl:strip-space elements="*"/>                                  (2)
 10     <xsl:output indent="yes"/>
            
        <xsl:template match="/">                                         (3)
            <rdf:RDF xmlns:rdf='&rdf;'
                     xmlns:ns="&ns;">
 15             <xsl:call-template name="element"/>
            </rdf:RDF>
        </xsl:template>
            
        <xsl:template match="*" name="element">                          (4)
 20         <xsl:variable name="separate-descriptions"                   (5)
                          select="*[count(@*|*)>0 and count(text())=0]"/>
            <rdf:Description rdf:nodeID="{generate-id()}" xmlns="&ns;">  (6)
                <xsl:for-each select="@*">                               (7)
                    <xsl:attribute name="{local-name()}" namespace="&ns;">
 25                     <xsl:value-of select="."/>
                    </xsl:attribute> 
                </xsl:for-each>
                
                <xsl:for-each select="$separate-descriptions">           (8)
 30                 <xsl:element name="{local-name()}">
                        <xsl:attribute name="rdf:nodeID" select="generate-id()"/>
                    </xsl:element>
                </xsl:for-each>
                
 35             <xsl:for-each select="* except $separate-descriptions">  (9)
                    <xsl:element name="{local-name()}">
                        <xsl:choose>
                            <xsl:when test="count(*)>0">
                                <xsl:attribute name="rdf:parseType"
 40                                 >Literal</xsl:attribute>
                                <xsl:copy-of select="*|text()" 
                                    copy-namespaces="no"/>
                            </xsl:when>
                            <xsl:otherwise>
 45                             <xsl:value-of select="."/>
                            </xsl:otherwise>
                        </xsl:choose>
                    </xsl:element>
                </xsl:for-each>
 50         </rdf:Description>
            <xsl:apply-templates select="$separate-descriptions"/>       (10)
        </xsl:template>
    </xsl:stylesheet>
    

1

Arbitrary namespace that should be changed for each case.

2

Ignores all whitespace nodes.

3

Matches the root node: generate the global rdf:RDF element in which the content for the root element is embedded by calling the element template.

4

Generates an RDF triple for each XML element. rdf:Description element followed by the translation of some other nodes.

5

Determines the child nodes having at least an attribute or a child element and no text element as child. A separate description will be generated for each of them.

6

The id of the node of the description is derived from the node in the XML document.

7

For each attribute, generates the corresponding RDF attribute.

8

For each node with complex context, generates an element with a reference to the separate node.

9

When a node combines text and element node, generates a literal XML node.

10

Produces the nodes for which reference to ids were generated.


Example 7.5. Excerpt of CellarBook.rdf, the RDF content of the cellar defined by Example D.1

Output produced by the application of Example 7.4 to Example 2.2.

  1 <?xml version="1.0" encoding="UTF-8"?>
    <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"     (1)
             xmlns:ns="http://www.iro.umontreal.ca/lapalme/ns#">
             
  5    <rdf:Description nodeID="d1">                                     (2)
          <cellar-book nodeID="d1e4"/>
       </rdf:Description>
                    
       <rdf:Description nodeID="d1e4"
 10              noNamespaceSchemaLocation="CellarBook.xsd">
          <wine-catalog nodeID="d1e5"/>
          <owner nodeID="d1e122"/>
          <location nodeID="d1e136"/>
          <cellar nodeID="d1e145"/>
 15    </rdf:Description>
                    
       <rdf:Description nodeID="d1e5">                                   (3)
          <wine nodeID="d1e6"/>
          <wine nodeID="d1e35"/>
 20       <wine nodeID="d1e54"/>
          <wine nodeID="d1e80"/>
          <wine nodeID="d1e103"/>
       </rdf:Description>
                    
 25    <rdf:Description nodeID="d1e6"                                    (4)
                 name="Domaine de l'Île Margaux"
                 appellation="Bordeaux supérieur"
                 classification="a.c."
                 code="C00043125"
 30              format="750ml">
          <properties nodeID="d1e7"/>
          <origin nodeID="d1e14"/>
          <comment>Ready for drinking now</comment>
          <food-pairing parseType="Literal"> Accompanies <emph>Bordelaise ribsteak</emph>, 
 35                 <bold>pork with prunes</bold> or magret de canard. </food-pairing>
          <price>22.80</price>
          <year>2002</year>
       </rdf:Description>
                    
 40    <rdf:Description nodeID="d1e7">                                   (5)
          <color>red</color>
          <alcoholic-strength>12.5</alcoholic-strength>
          <nature>still</nature>
       </rdf:Description>
 45                     ...
       <rdf:Description nodeID="d1e122">                                 (6)
          <name nodeID="d1e123"/>
          <street>1234 rue des Châteaux</street>
          <city>St-George</city>
 50       <province>ON</province>
          <postal-code>M7W 7S0</postal-code>
       </rdf:Description>         
       ...
    </rdf:RDF>

1

Root element of the RDF file with the declaration of the RDF namespace prefix and the ns prefix namespace. ns should be customized for each application.

2

The RDF element for the root node which merely links to the next rdf:Description element.

3

Content of the wine catalog.

4

The first wine in the wine catalog.

5

Property of the first wine in the wine catalog

6

Description of the owner of the cellar book.


Example 7.6. [CellarBook.ttl] The Turtle version of Example 7.5

Outline of an easier to to read version of the RDF information described in Example 7.5.

  1 @prefix :        <http://www.iro.umontreal.ca/lapalme/ns#> .         (1)
    @prefix ns:      <http://www.iro.umontreal.ca/lapalme/ns#> .
    @prefix rdf:     <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
    
  5 [] :cellar-book                                                      (2)
           [ :noNamespaceSchemaLocation "CellarBook.xsd" ;
             :wine-catalog                                               (3)
                     [ :wine [ :appellation "Bordeaux supérieur" ;       (4)
                               :classification "a.c." ;
 10                            :code   "C00043125" ;
                               :comment "Ready for drinking now" ;
                               :food-pairing 
                                    "Accompanies ... canard. "^^rdf:XMLLiteral ;
                               :format "750ml" ;
 15                            :name   "Domaine de l'Île Margaux" ;
                               :origin [ :country "France" ;
                                         :producer "SCEA ... (B.P. 5)" ;
                                         :region "Bordeaux"
                                       ] ;
 20                            :price  "22.80" ;
                               :properties                               (5)
                                       [ :alcoholic-strength "12.5" ;
                                         :color  "red" ;
                                         :nature "still"
 25                                    ] ;
                               :year   "2002"
                             ] ;
             :owner  [ :city   "St-George" ;                             (6)
                       :name   [ :family "Raisin" ;
 30                              :first  "Jude"
                               ] ;
                       :postal-code "M7W 7S0" ;
                       :province "ON" ;
                       :street "1234 rue des Châteaux"
 35                  ] ;
                             ...
                     ]
           ] .
    

1

Declaration of the RDF namespace prefix and the ns prefix namespace. ns should be customized for each application.

2

Blank node for the root node which merely links to the next element.

3

Content of the wine catalog.

4

The first wine in the wine catalog.

5

Property of the first wine in the wine catalog

6

Description of the owner of the cellar book.


We can use SPARQL to make queries on these triples by creating graph patterns as shown in

Example 7.7. [CellarBook.rq] a title

SPARQL queries on Example 7.7 corresponding to some XPath expressions shown in Example 4.1. Given the number of blank node generated by the automatic transformation process of Example 7.4, the bracketed notation is used extensively. Similarly to Example 7.3, only one SELECT query can be used at a time on RDF database, the other queries should be commented out by preceding each unneeded line with # before execution.

  1 PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
    PREFIX xsd: <http://www.w3.org/2001/XMLSchema#>
    PREFIX : <http://www.iro.umontreal.ca/lapalme/ns#>
    
  5 # name of owner 
    SELECT * WHERE {[:owner [:name [:first ?first; :family ?family]]]}   (1)
    
    # show names and the code of the wines of the catalog
    SELECT * WHERE {[:wine [:name ?name; :code ?code]]}                  (2)
 10 
    # find the code of the wines with less than 2 bottles in the cellar
    SELECT * WHERE {[:wine [:code ?c; :quantity ?q]]                     (3)
                     FILTER (xsd:integer(?q)<2)}
    
 15 # show the name of a wine in the catalog and the comment found in the cellar-book
    SELECT ?name ?comment WHERE                                          (4)
        {[:wine [:code ?code; :comment ?comment ; :quantity ?q]].
         [:wine [:code ?code; :name ?name]]}
    
 20 
    # show the name of a wine in the catalog and its information 
    # at one or two level of predicates 
    SELECT ?name ?p ?p1 ?o  WHERE {                                      (5)
           {[:wine-catalog [:wine [?p ?o ; :name ?name]]] 
 25                        FILTER (!isBlank(?o))}
     UNION {[:wine-catalog [:wine [?p [?p1 ?o]; :name ?name]]]}
    }
    

1

Find the first and family name of the owner of the cellarbook.

2

Show the name and code of all wines in the catalog. The codes of the cellar-book are not found because the :name predicate only occurs within the graph of the wine in the catalog.

3

Find the code of wines for which there are less than two bottles left. Note the use of the FILTER to remove some triples according to values of variables. As the content of node are strings in our case, we have to cast it to integer using xsd:integer to perform the numeric comparison.

4

Combine information from the catalog and the cellar-book by means of the ?code variable. As both the catalog and the cellar-book have a code and a comment, we specify a dummy quantity to make sure that we only match a triple from the cellar-book.

5

Show information from the catalog at one or two levels of nesting. We combine by UNION triples from two graph patterns. We remove the blank node that might be returned by the first graph pattern and that will be output in the second.