home *** CD-ROM | disk | FTP | other *** search
/ Mac Easy 2010 May / Mac Life Ubuntu.iso / casper / filesystem.squashfs / usr / share / python-support / python-rdflib / rdflib / syntax / parsers / RDFaParser.py < prev    next >
Encoding:
Python Source  |  2007-04-04  |  9.5 KB  |  292 lines

  1. """
  2. RDFa parser.
  3.  
  4. RDFa is a set of attributes used to embed RDF in XHTML. An important goal of
  5. RDFa is to achieve this RDF embedding without repeating existing XHTML content
  6. when that content is the metadata.
  7.  
  8. REFERENCES:
  9.  
  10.     http://www.w3.org/2001/sw/BestPractices/HTML/2005-rdfa-syntax
  11.  
  12. LICENSE:
  13.  
  14.   BSD
  15.  
  16. CHANGE HISTORY:
  17.  
  18.   2006/06/03 - Initial Version
  19.   2006/06/08 - Added support for role (as per primer not syntax spec)
  20.                Added support for plaintext and flattening of XMLLiterals
  21.                ... (Sections 5.1.1.2 and 5.1.2.1)
  22.                Fixed plaintext bug where it was being resolved as CURIE
  23.                Added support to skip reserved @rel keywords from:
  24.                  http://www.w3.org/TR/REC-html40/types.html#h-6.12
  25.   2006/08/12 - Changed reserved @rel resolution to include a '#'
  26.                Fixed subject resolution for LINK/META when inside HEAD
  27.                Fixed blank node extraction [_:address] -> [_:_:address]
  28.                Added support for passing prefix mappings to the Graph
  29.                via RDFaSink
  30.                Added @id support as part of subject resolution
  31.  
  32. Copyright (c) 2006, Elias Torres <elias@torrez.us>
  33.  
  34. """
  35.  
  36. import sys, re, urllib, urlparse, cStringIO, string
  37. from xml.dom import pulldom
  38. from rdflib.syntax.parsers import Parser
  39. from rdflib.Graph import ConjunctiveGraph
  40. from rdflib import URIRef
  41. from rdflib import BNode
  42. from rdflib import Literal
  43. from rdflib import Namespace
  44.  
  45. __version__ = "$Id: RDFaParser.py 1072 2007-03-30 18:12:54Z eliast $"
  46.  
  47. rdfa_attribs = ["about","property","rel","rev","href","content","role","id"]
  48.  
  49. reserved_links = ['alternate', 'stylesheet', 'start', 'next', 'prev',
  50.                  'contents', 'index', 'glossary', 'copyright', 'chapter',
  51.                  'section', 'subsection', 'appendix', 'help', 'bookmark']
  52.  
  53. xhtml = Namespace("http://www.w3.org/1999/xhtml")
  54. xml = Namespace("http://www.w3.org/XML/1998/namespace")
  55. rdf = Namespace("http://www.w3.org/1999/02/22-rdf-syntax-ns#")
  56.  
  57. class RDFaSink(object):
  58.   def __init__(self, graph):
  59.     self.graph = graph
  60.   def __str__(self):
  61.     return self.graph.serialize(format="pretty-xml")
  62.   def triple(self, s, p, o):
  63.     self.graph.add((s, p, o))
  64.   def prefix(self, prefix, ns):
  65.     self.graph.bind(prefix, ns, override=False)
  66.  
  67. _urifixer = re.compile('^([A-Za-z][A-Za-z0-9+-.]*://)(/*)(.*?)')
  68.  
  69. def _urljoin(base, uri):
  70.   uri = _urifixer.sub(r'\1\3', uri)
  71.   return urlparse.urljoin(base, uri)
  72.  
  73. class RDFaParser(Parser):
  74.   def __init__(self):
  75.     self.lang = None
  76.     self.abouts = []
  77.     self.xmlbases = []
  78.     self.langs = []
  79.     self.elementStack = [None]
  80.     self.bcounter = {}
  81.     self.bnodes = {}
  82.     self.sink = None
  83.  
  84.   def parse(self, source, sink, baseURI=None):
  85.     self.sink = RDFaSink(sink)
  86.     self.triple = self.sink.triple
  87.     self.prefix = self.sink.prefix
  88.     self.baseuri = baseURI or source.getPublicId()
  89.     f = source.getByteStream()
  90.     events = pulldom.parse(f)
  91.     self.handler = events.pulldom
  92.     for (event, node) in events:
  93.  
  94.       if event == pulldom.START_DOCUMENT:
  95.         self.abouts += [(URIRef(""), node)]
  96.  
  97.       if event == pulldom.END_DOCUMENT:
  98.         assert len(self.elementStack) == 0
  99.  
  100.       if event == pulldom.START_ELEMENT:
  101.  
  102.         # keep track of parent node
  103.         self.elementStack += [node]
  104.  
  105.         #if __debug__: print [e.tagName for e in self.elementStack if e]
  106.  
  107.         found = filter(lambda x:x in node.attributes.keys(),rdfa_attribs)
  108.  
  109.         # keep track of xml:lang xml:base
  110.         baseuri = node.getAttributeNS(xml,"base") or node.getAttribute("xml:base") or self.baseuri
  111.         self.baseuri = _urljoin(self.baseuri, baseuri)
  112.         self.xmlbases.append(self.baseuri)
  113.  
  114.         if node.hasAttributeNS(xml,"lang") or node.hasAttribute("xml:lang"):
  115.           lang = node.getAttributeNS(xml, 'lang') or node.getAttribute('xml:lang')
  116.  
  117.           if lang == '':
  118.             # xml:lang could be explicitly set to '', we need to capture that
  119.             lang = None
  120.         else:
  121.           # if no xml:lang is specified, use parent lang
  122.           lang = self.lang
  123.  
  124.         self.lang = lang
  125.         self.langs.append(lang)
  126.  
  127.         # node is not an RDFa element.
  128.         if len(found) == 0: continue
  129.  
  130.         parentNode = self.elementStack[-2]
  131.  
  132.         if "about" in found:
  133.           self.abouts += [(self.extractCURIEorURI(node.getAttribute("about")),node)]
  134.         elif "id" in found:
  135.           self.abouts += [(self.extractCURIEorURI("#" + node.getAttribute("id")),node)]
  136.  
  137.         subject = self.abouts[-1][0]
  138.  
  139.         # meta/link subject processing
  140.         if(node.tagName == "meta" or node.tagName == "link"):
  141.           if not("about" in found) and parentNode:
  142.             if parentNode and parentNode.tagName == "head":
  143.               subject = URIRef("")
  144.             elif(parentNode.hasAttribute("about")):
  145.               subject = self.extractCURIEorURI(parentNode.getAttribute("about"))
  146.             elif parentNode.hasAttributeNS(xml,"id") or parentNode.hasAttribute("id"):
  147.               # TODO: is this the right way to process xml:id by adding a '#'
  148.               id = parentNode.getAttributeNS(xml,"id") or parentNode.getAttribute("id")
  149.               subject = self.extractCURIEorURI("#" + id)
  150.             else:
  151.               subject = self.generateBlankNode(parentNode)
  152.  
  153.         if 'property' in found:
  154.           predicate = self.extractCURIEorURI(node.getAttribute('property'))
  155.           literal = None
  156.           datatype = None
  157.           plaintext = False
  158.  
  159.           if node.hasAttribute('datatype'):
  160.             sdt = node.getAttribute('datatype')
  161.             if sdt <> 'plaintext':
  162.               datatype = self.extractCURIEorURI(sdt)
  163.             else:
  164.               plaintext = True
  165.  
  166.           if node.hasAttribute("content"):
  167.             literal = Literal(node.getAttribute("content"), lang=lang, datatype=datatype)
  168.           else:
  169.             events.expandNode(node)
  170.  
  171.             # because I expanded, I won't get an END_ELEMENT
  172.             self._popStacks(event, node)
  173.  
  174.             content = ""
  175.             for child in node.childNodes:
  176.               if datatype or plaintext:
  177.                   content += self._getNodeText(child)
  178.               else:
  179.                 content += child.toxml()
  180.             content = content.strip()
  181.             literal = Literal(content,datatype=datatype or rdf.XMLLiteral)
  182.  
  183.           if literal:
  184.             self.triple(subject, predicate, literal)
  185.  
  186.         if "rel" in found:
  187.           rel = node.getAttribute("rel").strip()
  188.           if string.lower(rel) in reserved_links:
  189.             rel = xhtml["#" + string.lower(rel)]
  190.  
  191.           predicate = self.extractCURIEorURI(rel)
  192.           if node.hasAttribute("href"):
  193.             object = self.extractCURIEorURI(node.getAttribute("href"))
  194.             self.triple(subject, predicate, object)
  195.  
  196.         if "rev" in found:
  197.           predicate = self.extractCURIEorURI(node.getAttribute("rev"))
  198.           if node.hasAttribute("href"):
  199.             object = self.extractCURIEorURI(node.getAttribute("href"))
  200.             self.triple(object, predicate, subject)
  201.  
  202.         # role is in the primer, but not in the syntax.
  203.         # could be deprecated.
  204.         # Assumptions:
  205.         # - Subject resolution as always (including meta/link)
  206.         # - Attribute Value is a CURIE or URI
  207.         # - It adds another triple, besides prop, rel, rev.
  208.         if "role" in found:
  209.           type = self.extractCURIEorURI(node.getAttribute('role'))
  210.           self.triple(subject, rdf.type, type)
  211.  
  212.       if event == pulldom.END_ELEMENT:
  213.         self._popStacks(event, node)
  214.      
  215.     # share with sink any prefix mappings
  216.     for nsc in self.handler._ns_contexts:
  217.       for ns, prefix in nsc.items():
  218.         self.prefix(prefix, ns)
  219.  
  220.     f.close()
  221.  
  222.   def _getNodeText(self, node):
  223.     if node.nodeType in (3,4): return node.nodeValue
  224.     text = ''
  225.     for child in node.childNodes:
  226.       if child.nodeType in (3,4):
  227.         text = text + child.nodeValue
  228.     return text
  229.  
  230.   def generateBlankNode(self, parentNode):
  231.     name = parentNode.tagName
  232.     if self.bnodes.has_key(parentNode):
  233.       return self.bnodes[parentNode]
  234.  
  235.     if self.bcounter.has_key(name):
  236.       self.bcounter[name] = self.bcounter[name] + 1
  237.     else:
  238.       self.bcounter[name] = 0
  239.  
  240.     self.bnodes[parentNode] = BNode("%s%d" % (name, self.bcounter[name]))
  241.  
  242.     return self.bnodes[parentNode]
  243.  
  244.   def extractCURIEorURI(self, resource):
  245.     if(len(resource) > 0 and resource[0] == "[" and resource[-1] == "]"):
  246.       resource = resource[1:-1]
  247.  
  248.     # resolve prefixes
  249.     # TODO: check whether I need to reverse the ns_contexts
  250.     if(resource.find(":") > -1):
  251.       rpre,rsuf = resource.split(":", 1)
  252.       for nsc in self.handler._ns_contexts:
  253.         for ns, prefix in nsc.items():
  254.           if prefix == rpre:
  255.             resource = ns + rsuf
  256.  
  257.     # TODO: is this enough to check for bnodes?
  258.     if(len(resource) > 0 and resource[0:2] == "_:"):
  259.       return BNode(resource[2:])
  260.  
  261.     return URIRef(self.resolveURI(resource))
  262.  
  263.   def resolveURI(self, uri):
  264.     return _urljoin(self.baseuri or '', uri)
  265.  
  266.   def _popStacks(self, event, node):
  267.     # check abouts
  268.     if len(self.abouts) <> 0:
  269.       about, aboutnode = self.abouts[-1]
  270.       if aboutnode == node:
  271.         self.abouts.pop()
  272.  
  273.     # keep track of nodes going out of scope
  274.     self.elementStack.pop()
  275.  
  276.     # track xml:base and xml:lang going out of scope
  277.     if self.xmlbases:
  278.       self.xmlbases.pop()
  279.       if self.xmlbases and self.xmlbases[-1]:
  280.         self.baseuri = self.xmlbases[-1]
  281.  
  282.     if self.langs:
  283.       self.langs.pop()
  284.       if self.langs and self.langs[-1]:
  285.         self.lang = self.langs[-1]
  286.  
  287. if __name__ == "__main__":
  288.     store = ConjunctiveGraph()
  289.     store.load(sys.argv[1], format="rdfa") 
  290.     print store.serialize(format="pretty-xml")
  291.  
  292.