Ignore meaningless white space in RSpec
[sfa.git] / sfa / util / rspec.py
1 ### $Id$
2 ### $URL$
3
4 import sys
5 import pprint
6 import os
7 import httplib
8 from xml.dom import minidom
9 from types import StringTypes, ListType
10
11 class RSpec:
12
13     def __init__(self, xml = None, xsd = None, NSURL = None):
14         '''
15         Class to manipulate RSpecs.  Reads and parses rspec xml into python dicts
16         and reads python dicts and writes rspec xml
17
18         self.xsd = # Schema.  Can be local or remote file.
19         self.NSURL = # If schema is remote, Name Space URL to query (full path minus filename)
20         self.rootNode = # root of the DOM
21         self.dict = # dict of the RSpec.
22         self.schemaDict = {} # dict of the Schema
23         '''
24  
25         self.xsd = xsd
26         self.rootNode = None
27         self.dict = {}
28         self.schemaDict = {}
29         self.NSURL = NSURL 
30         if xml:
31             if type(xml) == file:
32                 self.parseFile(xml)
33             if type(xml) in StringTypes:
34                 self.parseString(xml)
35             self.dict = self.toDict() 
36         if xsd:
37             self._parseXSD(self.NSURL + self.xsd)
38
39
40     def _getText(self, nodelist):
41         rc = ""
42         for node in nodelist:
43             if node.nodeType == node.TEXT_NODE:
44                 rc = rc + node.data
45         return rc
46   
47     # The rspec is comprised of 2 parts, and 1 reference:
48     # attributes/elements describe individual resources
49     # complexTypes are used to describe a set of attributes/elements
50     # complexTypes can include a reference to other complexTypes.
51   
52   
53     def _getName(self, node):
54         '''Gets name of node. If tag has no name, then return tag's localName'''
55         name = None
56         if not node.nodeName.startswith("#"):
57             if node.localName:
58                 name = node.localName
59             elif node.attributes.has_key("name"):
60                 name = node.attributes.get("name").value
61         return name     
62  
63  
64     # Attribute.  {name : nameofattribute, {items: values})
65     def _attributeDict(self, attributeDom):
66         '''Traverse single attribute node.  Create a dict {attributename : {name: value,}]}'''
67         node = {} # parsed dict
68         for attr in attributeDom.attributes.keys():
69             node[attr] = attributeDom.attributes.get(attr).value
70         return node
71   
72  
73     def appendToDictOrCreate(self, dict, key, value):
74         if (dict.has_key(key)):
75             dict[key].append(value)
76         else:
77             dict[key]=[value]
78         return dict
79
80     def toGenDict(self, nodeDom=None, parentdict=None, siblingdict={}, parent=None):
81         """
82         convert an XML to a nested dict:
83           * Non-terminal nodes (elements with string children and attributes) are simple dictionaries
84           * Terminal nodes (the rest) are nested dictionaries
85         """
86
87         if (not nodeDom):
88             nodeDom=self.rootNode
89
90         curNodeName = nodeDom.localName
91
92         if (nodeDom.hasChildNodes()):
93             childdict={}
94             for attribute in nodeDom.attributes.keys():
95                 childdict = self.appendToDictOrCreate(childdict, attribute, nodeDom.getAttribute(attribute))
96             for child in nodeDom.childNodes[:-1]:
97                 if (child.nodeValue):
98                     siblingdict = self.appendToDictOrCreate(siblingdict, curNodeName, child.nodeValue)
99                 else:
100                     childdict = self.toGenDict(child, None, childdict, curNodeName)
101
102             child = nodeDom.childNodes[-1]
103             if (child.nodeValue):
104                 siblingdict = self.appendToDictOrCreate(siblingdict, curNodeName, child.nodeValue)
105                 if (childdict):
106                     siblingdict = self.appendToDictOrCreate(siblingdict, curNodeName, childdict)
107             else:
108                 siblingdict = self.toGenDict(child, siblingdict, childdict, curNodeName)
109         else:
110             childdict={}
111             for attribute in nodeDom.attributes.keys():
112                 childdict = self.appendToDictOrCreate(childdict, attribute, nodeDom.getAttribute(attribute))
113
114             self.appendToDictOrCreate(siblingdict, curNodeName, childdict)
115             
116         if (parentdict is not None):
117             parentdict = self.appendToDictOrCreate(parentdict, parent, siblingdict)
118             return parentdict
119         else:
120             return siblingdict
121
122
123
124     def toDict(self, nodeDom = None):
125         """
126         convert this rspec to a dict and return it.
127         """
128         node = {}
129         if not nodeDom:
130              nodeDom = self.rootNode
131   
132         elementName = nodeDom.nodeName
133         if elementName and not elementName.startswith("#"):
134             # attributes have tags and values.  get {tag: value}, else {type: value}
135             node[elementName] = self._attributeDict(nodeDom)
136             # resolve the child nodes.
137             if nodeDom.hasChildNodes():
138                 for child in nodeDom.childNodes:
139                     childName = self._getName(child)
140                     
141                     # skip null children
142                     if not childName: continue
143
144                     # initialize the possible array of children
145                     if not node[elementName].has_key(childName): node[elementName][childName] = []
146
147                     if isinstance(child, minidom.Text):
148                         # add if data is not empty
149                         if child.data.strip():
150                             node[elementName][childName].append(nextchild.data)
151                     elif child.hasChildNodes() and isinstance(child.childNodes[0], minidom.Text):
152                         for nextchild in child.childNodes:  
153                             node[elementName][childName].append(nextchild.data)
154                     else:
155                         childdict = self.toDict(child)
156                         for value in childdict.values():
157                             node[elementName][childName].append(value)
158
159         return node
160
161   
162     def toxml(self):
163         """
164         convert this rspec to an xml string and return it.
165         """
166         return self.rootNode.toxml()
167
168   
169     def toprettyxml(self):
170         """
171         print this rspec in xml in a pretty format.
172         """
173         return self.rootNode.toprettyxml()
174
175   
176     def __removeWhitespaceNodes(self, parent):
177         for child in list(parent.childNodes):
178             if child.nodeType == minidom.Node.TEXT_NODE and child.data.strip() == '':
179                 parent.removeChild(child)
180             else:
181                 self.__removeWhitespaceNodes(child)
182
183     def parseFile(self, filename):
184         """
185         read a local xml file and store it as a dom object.
186         """
187         dom = minidom.parse(filename)
188         self.__removeWhitespaceNodes(dom)
189         self.rootNode = dom.childNodes[0]
190
191
192     def parseString(self, xml):
193         """
194         read an xml string and store it as a dom object.
195         """
196         dom = minidom.parseString(xml)
197         self.__removeWhitespaceNodes(dom)
198         self.rootNode = dom.childNodes[0]
199
200  
201     def _httpGetXSD(self, xsdURI):
202         # split the URI into relevant parts
203         host = xsdURI.split("/")[2]
204         if xsdURI.startswith("https"):
205             conn = httplib.HTTPSConnection(host,
206                 httplib.HTTPSConnection.default_port)
207         elif xsdURI.startswith("http"):
208             conn = httplib.HTTPConnection(host,
209                 httplib.HTTPConnection.default_port)
210         conn.request("GET", xsdURI)
211         # If we can't download the schema, raise an exception
212         r1 = conn.getresponse()
213         if r1.status != 200: 
214             raise Exception
215         return r1.read().replace('\n', '').replace('\t', '').strip() 
216
217
218     def _parseXSD(self, xsdURI):
219         """
220         Download XSD from URL, or if file, read local xsd file and set
221         schemaDict.
222         
223         Since the schema definiton is a global namespace shared by and
224         agreed upon by others, this should probably be a URL.  Check
225         for URL, download xsd, parse, or if local file, use that.
226         """
227         schemaDom = None
228         if xsdURI.startswith("http"):
229             try: 
230                 schemaDom = minidom.parseString(self._httpGetXSD(xsdURI))
231             except Exception, e:
232                 # logging.debug("%s: web file not found" % xsdURI)
233                 # logging.debug("Using local file %s" % self.xsd")
234                 print e
235                 print "Can't find %s on the web. Continuing." % xsdURI
236         if not schemaDom:
237             if os.path.exists(xsdURI):
238                 # logging.debug("using local copy.")
239                 print "Using local %s" % xsdURI
240                 schemaDom = minidom.parse(xsdURI)
241             else:
242                 raise Exception("Can't find xsd locally")
243         self.schemaDict = self.toDict(schemaDom.childNodes[0])
244
245
246     def dict2dom(self, rdict, include_doc = False):
247         """
248         convert a dict object into a dom object.
249         """
250      
251         def elementNode(tagname, rd):
252             element = minidom.Element(tagname)
253             for key in rd.keys():
254                 if isinstance(rd[key], StringTypes) or isinstance(rd[key], int):
255                     element.setAttribute(key, str(rd[key]))
256                 elif isinstance(rd[key], dict):
257                     child = elementNode(key, rd[key])
258                     element.appendChild(child)
259                 elif isinstance(rd[key], list):
260                     for item in rd[key]:
261                         if isinstance(item, dict):
262                             child = elementNode(key, item)
263                             element.appendChild(child)
264                         elif isinstance(item, StringTypes) or isinstance(item, int):
265                             child = minidom.Element(key)
266                             text = minidom.Text()
267                             text.data = item
268                             child.appendChild(text)
269                             element.appendChild(child) 
270             return element
271         
272         # Minidom does not allow documents to have more then one
273         # child, but elements may have many children. Because of
274         # this, the document's root node will be the first key/value
275         # pair in the dictionary.  
276         node = elementNode(rdict.keys()[0], rdict.values()[0])
277         if include_doc:
278             rootNode = minidom.Document()
279             rootNode.appendChild(node)
280         else:
281             rootNode = node
282         return rootNode
283
284  
285     def parseDict(self, rdict, include_doc = True):
286         """
287         Convert a dictionary into a dom object and store it.
288         """
289         self.rootNode = self.dict2dom(rdict, include_doc).childNodes[0]
290  
291  
292     def getDictsByTagName(self, tagname, dom = None):
293         """
294         Search the dom for all elements with the specified tagname
295         and return them as a list of dicts
296         """
297         if not dom:
298             dom = self.rootNode
299         dicts = []
300         doms = dom.getElementsByTagName(tagname)
301         dictlist = [self.toDict(d) for d in doms]
302         for item in dictlist:
303             for value in item.values():
304                 dicts.append(value)
305         return dicts
306
307     def getDictByTagNameValue(self, tagname, value, dom = None):
308         """
309         Search the dom for the first element with the specified tagname
310         and value and return it as a dict.
311         """
312         tempdict = {}
313         if not dom:
314             dom = self.rootNode
315         dicts = self.getDictsByTagName(tagname, dom)
316         
317         for rdict in dicts:
318             if rdict.has_key('name') and rdict['name'] in [value]:
319                 return rdict
320               
321         return tempdict
322
323
324     def filter(self, tagname, attribute, blacklist = [], whitelist = [], dom = None):
325         """
326         Removes all elements where:
327         1. tagname matches the element tag
328         2. attribute matches the element attribte
329         3. attribute value is in valuelist  
330         """
331
332         tempdict = {}
333         if not dom:
334             dom = self.rootNode
335        
336         if dom.localName in [tagname] and dom.attributes.has_key(attribute):
337             if whitelist and dom.attributes.get(attribute).value not in whitelist:
338                 dom.parentNode.removeChild(dom)
339             if blacklist and dom.attributes.get(attribute).value in blacklist:
340                 dom.parentNode.removeChild(dom)
341            
342         if dom.hasChildNodes():
343             for child in dom.childNodes:
344                 self.filter(tagname, attribute, blacklist, whitelist, child) 
345
346
347     def merge(self, rspecs, tagname, dom=None):
348         """
349         Merge this rspec with the requested rspec based on the specified 
350         starting tag name. The start tag (and all of its children) will be merged  
351         """
352         tempdict = {}
353         if not dom:
354             dom = self.rootNode
355
356         whitelist = []
357         blacklist = []
358             
359         if dom.localName in [tagname] and dom.attributes.has_key(attribute):
360             if whitelist and dom.attributes.get(attribute).value not in whitelist:
361                 dom.parentNode.removeChild(dom)
362             if blacklist and dom.attributes.get(attribute).value in blacklist:
363                 dom.parentNode.removeChild(dom)
364
365         if dom.hasChildNodes():
366             for child in dom.childNodes:
367                 self.filter(tagname, attribute, blacklist, whitelist, child) 
368
369     def validateDicts(self):
370         types = {
371             'EInt' : int,
372             'EString' : str,
373             'EByteArray' : list,
374             'EBoolean' : bool,
375             'EFloat' : float,
376             'EDate' : date}
377
378
379     def pprint(self, r = None, depth = 0):
380         """
381         Pretty print the dict
382         """
383         line = ""
384         if r == None: r = self.dict
385         # Set the dept
386         for tab in range(0,depth): line += "    "
387         # check if it's nested
388         if type(r) == dict:
389             for i in r.keys():
390                 print line + "%s:" % i
391                 self.pprint(r[i], depth + 1)
392         elif type(r) in (tuple, list):
393             for j in r: self.pprint(j, depth + 1)
394         # not nested so just print.
395         else:
396             print line + "%s" %  r
397     
398
399
400 class RecordSpec(RSpec):
401
402     root_tag = 'record'
403     def parseDict(self, rdict, include_doc = False):
404         """
405         Convert a dictionary into a dom object and store it.
406         """
407         self.rootNode = self.dict2dom(rdict, include_doc)
408
409     def dict2dom(self, rdict, include_doc = False):
410         record_dict = rdict
411         if not len(rdict.keys()) == 1:
412             record_dict = {self.root_tag : rdict}
413         return RSpec.dict2dom(self, record_dict, include_doc)
414
415         
416 # vim:ts=4:expandtab
417