Replaced InvalidRSpec with InvalidXML
[sfa.git] / sfa / util / xml.py
1 #!/usr/bin/python 
2 from lxml import etree
3 from StringIO import StringIO
4 from datetime import datetime, timedelta
5 from sfa.util.xrn import *
6 from sfa.util.plxrn import hostname_to_urn
7 from sfa.util.faults import SfaNotImplemented, InvalidXML
8
9 class XpathFilter:
10     @staticmethod
11     def xpath(filter={}):
12         xpath = ""
13         if filter:
14             filter_list = []
15             for (key, value) in filter.items():
16                 if key == 'text':
17                     key = 'text()'
18                 else:
19                     key = '@'+key
20                 if isinstance(value, str):
21                     filter_list.append('%s="%s"' % (key, value))
22                 elif isinstance(value, list):
23                     filter_list.append('contains("%s", %s)' % (' '.join(map(str, value)), key))
24             if filter_list:
25                 xpath = ' and '.join(filter_list)
26                 xpath = '[' + xpath + ']'
27         return xpath
28
29 class XML:
30  
31     def __init__(self, xml=None):
32         self.root = None
33         self.namespaces = None
34         self.default_namespace = None
35         self.schema = None
36         if isinstance(xml, basestring):
37             self.parse_xml(xml)
38         elif isinstance(xml, etree._ElementTree):
39             self.root = xml.getroot()
40         elif isinstance(xml, etree._Element):
41             self.root = xml 
42
43     def parse_xml(self, xml):
44         """
45         parse rspec into etree
46         """
47         parser = etree.XMLParser(remove_blank_text=True)
48         try:
49             tree = etree.parse(xml, parser)
50         except IOError:
51             # 'rspec' file doesnt exist. 'rspec' is proably an xml string
52             try:
53                 tree = etree.parse(StringIO(xml), parser)
54             except Exception, e:
55                 raise InvalidXML(str(e))
56         self.root = tree.getroot()
57         # set namespaces map
58         self.namespaces = dict(self.root.nsmap)
59         # If the 'None' exist, then it's pointing to the default namespace. This makes 
60         # it hard for us to write xpath queries for the default naemspace because lxml 
61         # wont understand a None prefix. We will just associate the default namespeace 
62         # with a key named 'default'.     
63         if None in self.namespaces:
64             default_namespace = self.namespaces.pop(None)
65             self.namespaces['default'] = default_namespace
66
67         # set schema 
68         for key in self.root.attrib.keys():
69             if key.endswith('schemaLocation'):
70                 # schema location should be at the end of the list
71                 schema_parts  = self.root.attrib[key].split(' ')
72                 self.schema = schema_parts[1]    
73                 namespace, schema  = schema_parts[0], schema_parts[1]
74                 break
75
76     def validate(self, schema):
77         """
78         Validate against rng schema
79         """
80         relaxng_doc = etree.parse(schema)
81         relaxng = etree.RelaxNG(relaxng_doc)
82         if not relaxng(self.root):
83             error = relaxng.error_log.last_error
84             message = "%s (line %s)" % (error.message, error.line)
85             raise InvalidXML(message)
86         return True
87
88     def xpath(self, xpath, namespaces=None):
89         if not namespaces:
90             namespaces = self.namespaces
91         return self.root.xpath(xpath, namespaces=namespaces)
92
93     def set(self, key, value):
94         return self.root.set(key, value)
95
96     def add_attribute(self, elem, name, value):
97         """
98         Add attribute to specified etree element    
99         """
100         opt = etree.SubElement(elem, name)
101         opt.text = value
102
103     def add_element(self, name, attrs={}, parent=None, text=""):
104         """
105         Generic wrapper around etree.SubElement(). Adds an element to 
106         specified parent node. Adds element to root node is parent is 
107         not specified. 
108         """
109         if parent == None:
110             parent = self.root
111         element = etree.SubElement(parent, name)
112         if text:
113             element.text = text
114         if isinstance(attrs, dict):
115             for attr in attrs:
116                 element.set(attr, attrs[attr])  
117         return element
118
119     def remove_attribute(self, elem, name, value):
120         """
121         Removes an attribute from an element
122         """
123         if elem is not None:
124             opts = elem.iterfind(name)
125             if opts is not None:
126                 for opt in opts:
127                     if opt.text == value:
128                         elem.remove(opt)
129
130     def remove_element(self, element_name, root_node = None):
131         """
132         Removes all occurences of an element from the tree. Start at 
133         specified root_node if specified, otherwise start at tree's root.   
134         """
135         if not root_node:
136             root_node = self.root
137
138         if not element_name.startswith('//'):
139             element_name = '//' + element_name
140
141         elements = root_node.xpath('%s ' % element_name, namespaces=self.namespaces)
142         for element in elements:
143             parent = element.getparent()
144             parent.remove(element)
145
146     def attributes_list(self, elem):
147         # convert a list of attribute tags into list of tuples
148         # (tagnme, text_value)
149         opts = []
150         if elem is not None:
151             for e in elem:
152                 opts.append((e.tag, str(e.text).strip()))
153         return opts
154
155     def get_element_attributes(self, elem=None, depth=0):
156         if elem == None:
157             elem = self.root_node
158         if not hasattr(elem, 'attrib'):
159             # this is probably not an element node with attribute. could be just and an
160             # attribute, return it
161             return elem
162         attrs = dict(elem.attrib)
163         attrs['text'] = str(elem.text).strip()
164         attrs['parent'] = elem.getparent()
165         if isinstance(depth, int) and depth > 0:
166             for child_elem in list(elem):
167                 key = str(child_elem.tag)
168                 if key not in attrs:
169                     attrs[key] = [self.get_element_attributes(child_elem, depth-1)]
170                 else:
171                     attrs[key].append(self.get_element_attributes(child_elem, depth-1))
172         else:
173             attrs['child_nodes'] = list(elem)
174         return attrs
175
176     def merge(self, in_xml):
177         pass
178
179     def __str__(self):
180         return self.toxml()
181
182     def toxml(self):
183         return etree.tostring(self.root, pretty_print=True)  
184         
185     def save(self, filename):
186         f = open(filename, 'w')
187         f.write(self.toxml())
188         f.close()
189  
190 if __name__ == '__main__':
191     rspec = RSpec('/tmp/resources.rspec')
192     print rspec
193