1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 """
23 Common routines to parsing XML.
24
25 Flumotion deals with two basic kinds of XML: config and registry. They
26 correspond to data and schema, more or less. This file defines some base
27 parsing routines shared between both kinds of XML.
28 """
29
30 import sets
31
32 from xml.dom import minidom, Node
33 from xml.parsers import expat
34 from xml.sax.saxutils import escape, quoteattr
35
36 from flumotion.common import log, common
37
38
40 """
41 Object designed to wrap, or "box", any value. Useful mostly in the
42 context of the table-driven XML parser, so that a handler that wants
43 to set a scalar value can do so, getting around the limitations of
44 Python's lexical scoping.
45 """
48
51
54
55
57 """
58 Error during parsing of XML.
59
60 args[0]: str
61 """
62
64 """
65 XML parser base class.
66
67 I add some helper functions for specialized XML parsers, mostly the
68 parseFromTable method.
69
70 I am here so that the config parser and the registry parser can
71 share code.
72 """
73
74 parserError = ParserError
75
77 """
78 Return the root of the XML tree for the the string or filename
79 passed as an argument. Raises fxml.ParserError if the XML could
80 not be parsed.
81
82 @param file: An open file object, or the name of a file. Note
83 that if you pass a file object, this function will leave the
84 file open.
85 @type file: File object; can be a duck file like StringIO.
86 Alternately, the path of a file on disk.
87 """
88 self.debug('Parsing XML from %r', file)
89 try:
90 return minidom.parse(file)
91 except expat.ExpatError, e:
92 raise self.parserError('Error parsing XML from %r: %s' % (
93 file, log.getExceptionMessage(e)))
94
96 """
97 Checks that a given XML node has all of the required attributes,
98 and no unknown attributes. Raises fxml.ParserError if unknown
99 or missing attributes are detected. An empty attribute (e.g.
100 'foo=""') is treated as a missing attribute.
101
102 @param node: An XML DOM node.
103 @type node: L{xml.dom.Node}
104 @param required: Set of required attributes, or None.
105 @type required: Sequence (list, tuple, ...) of strings.
106 @param optional: Set of optional attributes, or None.
107 @type optional: Sequence (list, tuple, ...) of strings.
108 """
109 attrs = sets.Set([k for k in node.attributes.keys()
110 if node.getAttribute(k)])
111 required = sets.Set(required or ())
112 optional = sets.Set(optional or ())
113 for x in attrs - required.union(optional):
114 raise self.parserError("Unknown attribute in <%s>: %s"
115 % (node.nodeName, x))
116 for x in required - attrs:
117 raise self.parserError("Missing attribute in <%s>: %s"
118 % (node.nodeName, x))
119
120 - def parseAttributes(self, node, required=None, optional=None,
121 type=str):
122 """
123 Checks the validity of the attributes on an XML node, via
124 Parser.checkAttributes, then parses them out and returns them
125 all as a tuple.
126
127 @param node: An XML DOM node.
128 @type node: L{xml.dom.Node}
129 @param required: Set of required attributes, or None.
130 @type required: Sequence (list, tuple, ...) of strings.
131 @param optional: Set of optional attributes, or None.
132 @type optional: Sequence (list, tuple, ...) of strings.
133 @param type: Type to which to cast attribute values. The
134 original values will always be unicode objects; in most cases
135 you want `str' objects, so this defaults to `str'.
136 @type type: Function of type object -> object.
137
138 @returns: List of all attributes as a tuple. The first element
139 of the returned tuple will be the value of the first required
140 attribute, the second the value of the second required
141 attribute, and so on. The optional attributes follow, with None
142 as the value if the optional attribute was not present.
143 @rtype: tuple of string or None, as long as the combined length
144 of the required and optional attributes.
145 """
146 self.checkAttributes(node, required, optional)
147 out = []
148 for k in (required or ()) + (optional or ()):
149 if node.hasAttribute(k):
150
151 a = node.getAttribute(k)
152 if a:
153 out.append(type(a))
154 else:
155 out.append(None)
156 else:
157 out.append(None)
158 return out
159
161 """
162 A data-driven verifying XML parser. Raises fxml.ParserError if
163 an unexpected child node is encountered.
164
165 @param parent: An XML node whose child nodes you are interested
166 in parsing.
167 @type parent: L{xml.dom.Node}
168 @param parsers: A parse table defining how to parse the child
169 nodes. The keys are the possible child nodes, and the value is a
170 two-tuple of how to parse them consisting of a parser and a
171 value handler. The parser is a one-argument function that will
172 be called with the child node as an argument, and the handler is
173 a one-argument function that will be called with the result of
174 calling the parser.
175 @type parsers: dict of string -> (function, function)
176 """
177 for child in parent.childNodes:
178 if (child.nodeType == Node.TEXT_NODE or
179 child.nodeType == Node.COMMENT_NODE):
180 continue
181 try:
182 parser, handler = parsers[child.nodeName]
183 except KeyError:
184 raise self.parserError("unexpected node in <%s>: %s"
185 % (parent.nodeName, child))
186 handler(parser(child))
187
188 - def parseTextNode(self, node, type=str):
189 """Parse a text-containing XML node.
190
191 The node is expected to contain only text children. Recognized
192 node types are L{xml.dom.Node.TEXT_NODE} and
193 L{xml.dom.Node.CDATA_SECTION_NODE}.
194
195 @param node: the node to parse
196 @type node: L{xml.dom.Node}
197 @param type: a function to call on the resulting text
198 @type type: function of type unicode -> object
199
200 @returns: The result of calling type on the unicode text. By
201 default, type is L{str}.
202 """
203 ret = []
204 for child in node.childNodes:
205 if (child.nodeType == Node.TEXT_NODE
206 or child.nodeType == Node.CDATA_SECTION_NODE):
207 ret.append(child.data)
208 elif child.nodeType == Node.COMMENT_NODE:
209 continue
210 else:
211 raise self.parserError('unexpected non-text content of '
212 '%r: %r' % (node, child))
213 try:
214 return type(''.join(ret))
215 except Exception, e:
216 raise self.parserError('failed to parse %s as %s: %s', node,
217 type, log.getExceptionMessage(e))
218
219
220
222 if not isinstance(expr, list):
223 return escape(unicode(expr))
224 operator = expr[0]
225 args = [sxml2unicode(arg) for arg in expr[1:]]
226 return unicode(operator(args))
227
229 table = {'klass': 'class'}
230 return '-'.join(table.get(k, k).split('_'))
231
232
233
234
235
238 def tag(**kw):
239 pre = '<%s%s>' % (_trans(attr),
240 ''.join([' %s=%s' % (_trans(k), quoteattr(v))
241 for k, v in kw.items()]))
242 post = '</%s>' % (_trans(attr),)
243 def render(args):
244 return pre + '\n'.join(args) + post
245 render.__name__ = pre
246 return render
247 tag.__name__ = attr
248 return tag
249