1 # Copyright 2015 The Chromium Authors. All rights reserved.
2 # Use of this source code is governed by a BSD-style license that can be
3 # found in the LICENSE file.
5 """Model types for describing description xml models."""
7 from xml
.dom
import minidom
12 import pretty_print_xml
15 def GetComments(node
):
16 """Extracts comments in the current node.
19 node: The DOM node to extract comments from.
21 A list of comment DOM nodes.
23 return [n
for n
in node
.childNodes
if n
.nodeType
== minidom
.Node
.COMMENT_NODE
]
26 def PutComments(node
, comments
):
27 """Append comments to the DOM node.
30 node: The DOM node to write comments to.
31 comments: A list of comment DOM nodes.
37 class NodeType(object):
38 """Base type for a type of XML node.
41 dont_indent: True iff this node should not have it's children indented
43 extra_newlines: None or a triple of integers describing the number of
44 newlines that should be printed (after_open, before_close, after_close)
45 single_line: True iff this node may be squashed into a single line.
47 def __init__(self
, tag
,
52 self
.dont_indent
= dont_indent
53 self
.extra_newlines
= extra_newlines
54 self
.single_line
= single_line
56 def Unmarshall(self
, node
):
59 def Marshall(self
, doc
, obj
):
62 def GetAttributes(self
):
65 def GetNodeTypes(self
):
66 return {self
.tag
: self
}
69 class TextNodeType(NodeType
):
70 """A type for simple nodes that just have a tag and some text content.
72 Unmarshalls nodes to strings.
75 tag: The name of XML tag for this type of node.
77 def __init__(self
, tag
, **kwargs
):
78 NodeType
.__init
__(self
, tag
, **kwargs
)
81 return 'TextNodeType("%s")' % self
.tag
83 def Unmarshall(self
, node
):
84 return node
.firstChild
.nodeValue
.strip()
86 def Marshall(self
, doc
, obj
):
87 node
= doc
.createElement(self
.tag
)
88 node
.appendChild(doc
.createTextNode(obj
))
92 class ChildType(object):
93 """Metadata about a nodes children.
96 attr: The field name of the parents model object storing the child's model.
97 node_type: The NodeType of the child.
98 multiple: True if the child can be repeated.
100 def __init__(self
, attr
, node_type
, multiple
):
102 self
.node_type
= node_type
103 self
.multiple
= multiple
106 class ObjectNodeType(NodeType
):
107 """A complex node type that has attributes or other nodes as children.
109 Unmarshalls nodes to objects.
112 tag: The name of XML tag for this type of node.
113 int_attributes: A list of names of integer attributes.
114 float_attributes: A list of names of float attributes.
115 string_attributes: A list of names of string attributes.
116 children: A list of ChildTypes describing the objects children.
118 def __init__(self
, tag
,
121 string_attributes
=[],
124 NodeType
.__init
__(self
, tag
, **kwargs
)
125 self
.int_attributes
= int_attributes
126 self
.float_attributes
= float_attributes
127 self
.string_attributes
= string_attributes
128 self
.children
= children
131 return 'ObjectNodeType("%s")' % self
.tag
133 def Unmarshall(self
, node
):
136 obj
['comments'] = GetComments(node
)
138 for attr
in self
.int_attributes
:
139 obj
[attr
] = int(node
.getAttribute(attr
))
141 for attr
in self
.float_attributes
:
142 obj
[attr
] = float(node
.getAttribute(attr
))
144 for attr
in self
.string_attributes
:
145 obj
[attr
] = node
.getAttribute(attr
)
147 for child
in self
.children
:
148 nodes
= node
.getElementsByTagName(child
.node_type
.tag
)
150 obj
[child
.attr
] = [child
.node_type
.Unmarshall(n
) for n
in nodes
]
153 raise ValueError("Missing required tag '%s'" % child
.node_type
.tag
)
154 obj
[child
.attr
] = child
.node_type
.Unmarshall(nodes
[0])
157 def Marshall(self
, doc
, obj
):
158 node
= doc
.createElement(self
.tag
)
159 attributes
= (self
.int_attributes
+
160 self
.float_attributes
+
161 self
.string_attributes
)
162 for attr
in attributes
:
163 node
.setAttribute(attr
, str(obj
[attr
]))
165 PutComments(node
, obj
['comments'])
167 for child
in self
.children
:
169 for o
in obj
[child
.attr
]:
170 node
.appendChild(child
.node_type
.Marshall(doc
, o
))
172 node
.appendChild(child
.node_type
.Marshall(doc
, obj
[child
.attr
]))
175 def GetAttributes(self
):
176 return self
.int_attributes
+ self
.float_attributes
+ self
.string_attributes
178 def GetNodeTypes(self
):
179 types
= {self
.tag
: self
}
180 for child
in self
.children
:
181 types
.update(child
.node_type
.GetNodeTypes())
185 class DocumentType(object):
186 """Model for the root of an XML description file.
189 root_type: A NodeType describing the root tag of the document.
191 def __init__(self
, root_type
):
192 self
.root_type
= root_type
194 def Parse(self
, input_file
):
195 tree
= minidom
.parseString(input_file
)
196 comments
= GetComments(tree
)
197 return comments
, self
.root_type
.Unmarshall(
198 tree
.getElementsByTagName(self
.root_type
.tag
)[0])
200 def GetPrintStyle(self
):
201 types
= self
.root_type
.GetNodeTypes()
202 return pretty_print_xml
.XmlStyle(
203 {t
: types
[t
].GetAttributes() for t
in types
},
204 {t
: types
[t
].extra_newlines
for t
in types
if types
[t
].extra_newlines
},
205 [t
for t
in types
if types
[t
].dont_indent
],
206 [t
for t
in types
if types
[t
].single_line
])
208 def ToXML(self
, comments
, obj
):
209 doc
= minidom
.Document()
210 for comment
in comments
:
211 doc
.appendChild(comment
)
212 doc
.appendChild(self
.root_type
.Marshall(doc
, obj
))
215 def PrettyPrint(self
, comments
, obj
):
216 return self
.GetPrintStyle().PrettyPrintNode(self
.ToXML(comments
, obj
))