root/rdfalchemy/trunk/rdfalchemy/sparql/parsers.py

Revision 158, 7.3 KB (checked in by phil, 8 weeks ago)

commit of rdflib3.2 compability to trunk

Line 
1from rdflib import URIRef , Literal, BNode
2
3from urllib2 import urlopen, Request, HTTPError
4from struct import unpack
5
6from rdfalchemy.exceptions import MalformedQueryError, QueryEvaluationError
7
8try:
9    import simplejson
10except ImportError:
11    import json
12   
13import logging
14
15__all__=["_JSONSPARQLHandler","_XMLSPARQLHandler","_BRTRSPARQLHandler"]
16
17log=logging.getLogger(__name__)
18
19# use a fast ElementTree
20# TODO: test these each for iterparse compatability and relative speed
21try:
22    import cElementTree as ET # effbot's C module
23except ImportError:
24    try:
25        import xml.etree.ElementTree as ET # in python >=2.5
26    except ImportError:
27        try:
28            import lxml.etree as ET # ElementTree API using libxml2
29        except ImportError:
30            import elementtree.ElementTree as ET # effbot's pure Python module
31log.debug('Using ElementTree: %s' % ET)
32
33
34class _SPARQLHandler(object):
35    """Abstract base class for parsing the response stream of a sparql query
36    Real classhes should subclass from here but should **not** do too much during `__init__`
37   
38    `__init__` should stip after opening the stream and not read so that users have the
39    option to call p.stream.read() to get the rawResults
40    """
41    mimetype = ""
42
43    def __init__(self, url):
44        req = Request(url)
45        if self.mimetype:
46            req.add_header('Accept',self.mimetype)
47        self.stream = urlopen(req)
48       
49
50class _JSONSPARQLHandler(_SPARQLHandler):
51    """Parse the results of a sparql query returned as json.
52   
53    Note: this uses simplejson.load which will consume the entire
54    stream before returning any results. The XML handler uses a generator
55    type return so it returns the first tuple as soon as it's available
56    *without* having to comsume the entire stream
57    """
58    mimetype = 'application/sparql-results+json'
59
60    def parse(self):
61        ret=simplejson.load(self.stream)
62        var_names = ret['head']['vars'] 
63        bindings = ret['results']['bindings']
64        for b in bindings:
65            for var,val in b.items():
66                type = val['type']
67                if type=='uri':
68                   b[var]=URIRef(val['value'])
69                elif type == 'bnode':
70                   b[var]=BNode(val['value'])
71                elif type == 'literal':
72                   b[var]=Literal(val['value'],lang=val.get('xml:lang'))
73                elif type == 'typed-literal':
74                   b[var]=Literal(val['value'],datatype=val.get('datatype'))
75                else:
76                   raise AttributeError("Binding type error: %s"%(type))
77            yield tuple([b.get(var) for var in var_names])
78
79
80# some constants for parsing the xml tree
81_S_NS    = "{http://www.w3.org/2005/sparql-results#}"
82_VARIABLE= _S_NS+"variable"
83_BNODE   = _S_NS+"bnode"
84_URI     = _S_NS+"uri"
85_BINDING = _S_NS+"binding"
86_LITERAL = _S_NS+"literal"
87_HEAD    = _S_NS+"head"
88_RESULT  = _S_NS+"result"
89_X_NS = "{http://www.w3.org/XML/1998/namespace}"
90_LANG = _X_NS+"lang"
91
92
93class _XMLSPARQLHandler(_SPARQLHandler):
94    """Parse the results of a sparql query returned as xml.
95   
96    Note: returns a generator so that the first tuple is
97    available as soon as it is sent.  This does **not** need to consume
98    the entire results stream before returning results (that's a good thing :-).
99    """
100    mimetype = 'application/sparql-results+xml'
101
102    def parse(self):
103        var_names=[]
104        bindings=[]             
105        events = iter(ET.iterparse(self.stream,events=('start','end')))
106        # lets gather up the variable names in head
107        for (event, node) in events:
108            if event == 'start' and node.tag == _VARIABLE:
109                var_names.append(node.get('name'))
110            elif event == 'end' and node.tag == _HEAD:
111                break
112        # now let's yield each result as we parse them
113        for (event, node) in events:
114            if event == 'start':
115                if node.tag == _BINDING:
116                    idx = var_names.index(node.get('name'))
117                elif node.tag == _RESULT:
118                    bindings = [None,] *  len(var_names)
119            elif event == 'end':
120                if node.tag == _URI:
121                    bindings[idx] = URIRef(node.text)
122                elif node.tag == _BNODE:
123                    bindings[idx] = BNode(node.text)
124                elif node.tag == _LITERAL:
125                    bindings[idx] = Literal(node.text or '',
126                                        datatype = node.get('datatype'), 
127                                        lang= node.get(_LANG))
128                elif node.tag == _RESULT:
129                    node.clear()
130                    yield tuple(bindings)
131                   
132
133class _BRTRSPARQLHandler(_SPARQLHandler):
134    """Handler for the sesame binary table format BRTR_
135   
136    .. _BRTR: http://www.openrdf.org/doc/sesame/api/org/openrdf/sesame/query/BinaryTableResultConstants.html
137    """
138
139    def readint(self):
140        return  unpack('>i',self.stream.read(4))[0]
141   
142    def readstr(self):
143        l = self.readint()
144        return self.stream.read(l).decode("utf-8")
145
146    def parse(self):
147        if self.stream.read(4) <> 'BRTR': raise ParseError("First 4 bytes in should be BRTR")
148        self.ver = self.readint() # ver of protocol
149        self.ncols = self.readint()
150        self.keys = tuple(self.readstr() for x in range(self.ncols))
151        self.values = [None,]*self.ncols
152        self.ns = {}
153        while True:
154            for i in range(self.ncols):
155                val = self.getval()
156                if val is 1: # REPEAT here is like skip...the val is already in self.values[i]
157                    continue
158                self.values[i] = val             
159            yield tuple(self.values)
160
161    def getval(self):
162        while True:
163            rtype = ord(self.stream.read(1))
164            if rtype == 0: #NULL
165                return None
166            elif rtype == 1: #REPEAT
167                return 1
168            elif rtype == 2: #NAMESPACE     
169                nsid = self.readint()
170                url = self.readstr()
171                self.ns[nsid] = url
172            elif rtype == 3: # QNAME
173                nsid = self.readint()
174                localname = self.readstr()
175                return URIRef(self.ns[nsid] + localname)
176            elif rtype == 4: # URI
177                return URIRef(self.readstr())
178            elif rtype == 5: # BNODE
179                return BNode(self.readstr())
180            elif rtype == 6: # PLAIN LITERAL
181                return Literal(self.readstr())
182            elif rtype == 7: # LANGUAGE LITERAL
183                lit = self.readstr()
184                lang= self.readstr()
185                return Literal(lit,lang=lang)
186            elif rtype == 8: # DATATYPE LITERAL
187                lit = self.readstr()
188                datatype = self.getval()
189                return Literal(lit,datatype=datatype)               
190            elif rtype == 126: # ERROR
191                errType = ord(self.stream.read(1))
192                errStr = self.readstr()
193                if errType == 1:
194                    raise MalformedQueryError(errStr)
195                elif errType == 2:
196                    raise QueryEvaluationError(errStr)
197                else:
198                    raise errStr
199            elif rtype == 127: # EOF
200                raise StopIteration()
201            else:
202                raise ParseError("Undefined record type: %s" % rtype)
Note: See TracBrowser for help on using the browser.