265be8bf8fc5b48d01529202f8bdd625838ff1bf
[sfa.git] / geni / util / rspec.py
1 import sys
2 import pprint
3 import os
4 import httplib
5 from xml.dom import minidom
6 from types import StringTypes, ListType
7
8 class Rspec():
9
10     def __init__(self, xml = None, xsd = None, NSURL = None):
11         '''
12         Class to manipulate RSpecs.  Reads and parses rspec xml into python dicts
13         and reads python dicts and writes rspec xml
14
15         self.xsd = # Schema.  Can be local or remote file.
16         self.NSURL = # If schema is remote, Name Space URL to query (full path minus filename)
17         self.rootNode = # root of the DOM
18         self.dict = # dict of the RSpec.
19         self.schemaDict = {} # dict of the Schema
20         '''
21  
22         self.xsd = xsd
23         self.rootNode = None
24         self.dict = {}
25         self.schemaDict = {}
26         self.NSURL = NSURL 
27         if xml: 
28             if type(xml) == file:
29                 self.parseFile(xml)
30             if type(xml) == str:
31                 self.parseString(xml)
32             self.dict = self.toDict() 
33         if xsd:
34             self._parseXSD(self.NSURL + self.xsd)
35
36
37     def _getText(self, nodelist):
38         rc = ""
39         for node in nodelist:
40             if node.nodeType == node.TEXT_NODE:
41                 rc = rc + node.data
42         return rc
43   
44     # The rspec is comprised of 2 parts, and 1 reference:
45     # attributes/elements describe individual resources
46     # complexTypes are used to describe a set of attributes/elements
47     # complexTypes can include a reference to other complexTypes.
48   
49   
50     def _getName(self, node):
51         '''Gets name of node. If tag has no name, then return tag's localName'''
52         name = None
53         if not node.nodeName.startswith("#"):
54             if node.localName:
55                 name = node.localName
56             elif node.attributes.has_key("name"):
57                 name = node.attributes.get("name").value
58         return name     
59  
60  
61     # Attribute.  {name : nameofattribute, {items: values})
62     def _attributeDict(self, attributeDom):
63         '''Traverse single attribute node.  Create a dict {attributename : {name: value,}]}'''
64         node = {} # parsed dict
65         for attr in attributeDom.attributes.keys():
66             node[attr] = attributeDom.attributes.get(attr).value
67         return node
68   
69  
70     def toDict(self, nodeDom = None):
71         """
72         convert this rspec to a dict and return it.
73         """
74         node = {}
75         if not nodeDom:
76              nodeDom = self.rootNode
77   
78         elementName = nodeDom.nodeName
79         if elementName and not elementName.startswith("#"):
80             # attributes have tags and values.  get {tag: value}, else {type: value}
81             node[elementName] = self._attributeDict(nodeDom)
82             # resolve the child nodes.
83             if nodeDom.hasChildNodes():
84                 for child in nodeDom.childNodes:
85                     childName = self._getName(child)
86                     # skip null children 
87                     if not childName:
88                         continue
89                     # initialize the possible array of children        
90                     if not node[elementName].has_key(childName):
91                         node[elementName][childName] = []
92                     # if child node has text child nodes
93                     # append the children to the array as strings
94                     if child.hasChildNodes() and isinstance(child.childNodes[0], minidom.Text):
95                         for nextchild in child.childNodes:
96                             node[elementName][childName].append(nextchild.data)
97                     # convert element child node to dict
98                     else:       
99                         childdict = self.toDict(child)
100                         for value in childdict.values():
101                             node[elementName][childName].append(value)
102                     #node[childName].append(self.toDict(child))
103         return node
104
105   
106     def toxml(self):
107         """
108         convert this rspec to an xml string and return it.
109         """
110         return self.rootNode.toxml()
111
112   
113     def toprettyxml(self):
114         """
115         print this rspec in xml in a pretty format.
116         """
117         return self.rootNode.toprettyxml()
118
119   
120     def parseFile(self, filename):
121         """
122         read a local xml file and store it as a dom object.
123         """
124         dom = minidom.parse(filename)
125         self.rootNode = dom.childNodes[0]
126
127
128     def parseString(self, xml):
129         """
130         read an xml string and store it as a dom object.
131         """
132         xml = xml.replace('\n', '').replace('\t', '').strip()
133         dom = minidom.parseString(xml)
134         self.rootNode = dom.childNodes[0]
135
136  
137     def _httpGetXSD(self, xsdURI):
138         # split the URI into relevant parts
139         host = xsdURI.split("/")[2]
140         if xsdURI.startswith("https"):
141             conn = httplib.HTTPSConnection(host,
142                 httplib.HTTPSConnection.default_port)
143         elif xsdURI.startswith("http"):
144             conn = httplib.HTTPConnection(host,
145                 httplib.HTTPConnection.default_port)
146         conn.request("GET", xsdURI)
147         # If we can't download the schema, raise an exception
148         r1 = conn.getresponse()
149         if r1.status != 200: 
150             raise Exception
151         return r1.read().replace('\n', '').replace('\t', '').strip() 
152
153
154     def _parseXSD(self, xsdURI):
155         """
156         Download XSD from URL, or if file, read local xsd file and set schemaDict
157         """
158         # Since the schema definiton is a global namespace shared by and agreed upon by
159         # others, this should probably be a URL.  Check for URL, download xsd, parse, or 
160         # if local file, use local file.
161         schemaDom = None
162         if xsdURI.startswith("http"):
163             try: 
164                 schemaDom = minidom.parseString(self._httpGetXSD(xsdURI))
165             except Exception, e:
166                 # logging.debug("%s: web file not found" % xsdURI)
167                 # logging.debug("Using local file %s" % self.xsd")
168                 print e
169                 print "Can't find %s on the web. Continuing." % xsdURI
170         if not schemaDom:
171             if os.path.exists(xsdURI):
172                 # logging.debug("using local copy.")
173                 print "Using local %s" % xsdURI
174                 schemaDom = minidom.parse(xsdURI)
175             else:
176                 raise Exception("Can't find xsd locally")
177         self.schemaDict = self.toDict(schemaDom.childNodes[0])
178
179
180     def dict2dom(self, rdict, include_doc = False):
181         """
182         convert a dict object into a dom object.
183         """
184      
185         def elementNode(tagname, rd):
186             element = minidom.Element(tagname)
187             for key in rd.keys():
188                 if isinstance(rd[key], StringTypes) or isinstance(rd[key], int):
189                     element.setAttribute(key, str(rd[key]))
190                 elif isinstance(rd[key], dict):
191                     child = elementNode(key, rd[key])
192                     element.appendChild(child)
193                 elif isinstance(rd[key], list):
194                     for item in rd[key]:
195                         if isinstance(item, dict):
196                             child = elementNode(key, item)
197                             element.appendChild(child)
198                         elif isinstance(item, StringTypes) or isinstance(item, int):
199                             child = minidom.Element(key)
200                             text = minidom.Text()
201                             text.data = item
202                             child.appendChild(text)
203                             element.appendChild(child) 
204             return element
205         
206         # Minidom does not allow documents to have more then one
207         # child, but elements may have many children. Because of
208         # this, the document's root node will be the first key/value
209         # pair in the dictionary.  
210         node = elementNode(rdict.keys()[0], rdict.values()[0])
211         if include_doc:
212             rootNode = minidom.Document()
213             rootNode.appendChild(node)
214         else:
215             rootNode = node
216         return rootNode
217
218  
219     def parseDict(self, rdict, include_doc = True):
220         """
221         Convert a dictionary into a dom object and store it.
222         """
223         self.rootNode = self.dict2dom(rdict, include_doc)
224  
225  
226     def getDictsByTagName(self, tagname, dom = None):
227         """
228         Search the dom for all elements with the specified tagname
229         and return them as a list of dicts
230         """
231         if not dom:
232             dom = self.rootNode
233         dicts = []
234         doms = dom.getElementsByTagName(tagname)
235         dictlist = [self.toDict(d) for d in doms]
236         for item in dictlist:
237             for value in item.values():
238                 dicts.append(value)
239         return dicts
240
241     def getDictByTagNameValue(self, tagname, value, dom = None):
242         """
243         Search the dom for the first element with the specified tagname
244         and value and return it as a dict.
245         """
246         tempdict = {}
247         if not dom:
248             dom = self.rootNode
249         dicts = self.getDictsByTagName(tagname, dom)
250         
251         for rdict in dicts:
252             if rdict.has_key('name') and rdict['name'] in [value]:
253                 return rdict
254               
255         return tempdict
256
257
258     def filter(self, tagname, attribute, blacklist = [], whitelist = [], dom = None):
259         """
260         Removes all elements where:
261         1. tagname matches the element tag
262         2. attribute matches the element attribte
263         3. attribute value is in valuelist  
264         """
265
266         tempdict = {}
267         if not dom:
268             dom = self.rootNode
269        
270         if dom.localName in [tagname] and dom.attributes.has_key(attribute):
271             if whitelist and dom.attributes.get(attribute).value not in whitelist:
272                 dom.parentNode.removeChild(dom)
273             if blacklist and dom.attributes.get(attribute).value in blacklist:
274                 dom.parentNode.removeChild(dom)
275            
276         if dom.hasChildNodes():
277             for child in dom.childNodes:
278                 self.filter(tagname, attribute, blacklist, whitelist, child) 
279
280
281     def validateDicts(self):
282         types = {
283             'EInt' : int,
284             'EString' : str,
285             'EByteArray' : list,
286             'EBoolean' : bool,
287             'EFloat' : float,
288             'EDate' : date}
289
290
291
292 class RecordSpec(Rspec):
293
294     root_tag = 'record'
295     def parseDict(self, rdict, include_doc = False):
296         """
297         Convert a dictionary into a dom object and store it.
298         """
299         self.rootNode = self.dict2dom(rdict, include_doc)
300
301     def dict2dom(self, rdict, include_doc = False):
302         record_dict = {self.root_tag : rdict}
303         return Rspec.dict2dom(self, record_dict, include_doc)
304         
305 # vim:ts=4:expandtab