1 # Copyright (c) 2011 Google Inc. 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.
9 def XmlToString(content, encoding='utf-8', pretty=False):
10 """ Writes the XML content to disk, touching the file only if it has changed.
12 Visual Studio files have a lot of pre-defined structures. This function makes
13 it easy to represent these structures as Python data structures, instead of
14 having to create a lot of function calls.
16 Each XML element of the content is represented as a list composed of:
17 1. The name of the element, a string,
18 2. The attributes of the element, a dictionary (optional), and
19 3+. The content of the element, if any. Strings are simple text nodes and
20 lists are child elements.
28 <myelement a='value1' b='value2'>
29 <childtype>This is</childtype>
30 <childtype>it!</childtype>
34 ['myelement', {'a':'value1', 'b':'value2'},
35 ['childtype', 'This is'],
40 content: The structured content to be converted.
41 encoding: The encoding to report on the first XML line.
42 pretty: True if we want pretty printing with indents and new lines.
45 The XML content as a string.
47 # We create a huge list of all the elements of the file.
48 xml_parts = ['<?xml version="1.0" encoding="%s"?>' % encoding]
50 xml_parts.append('\n')
51 _ConstructContentList(xml_parts, content, pretty)
53 # Convert it to a string
54 return ''.join(xml_parts)
57 def _ConstructContentList(xml_parts, specification, pretty, level=0):
58 """ Appends the XML parts corresponding to the specification.
61 xml_parts: A list of XML parts to be appended to.
62 specification: The specification of the element. See EasyXml docs.
63 pretty: True if we want pretty printing with indents and new lines.
64 level: Indentation level.
66 # The first item in a specification is the name of the element.
68 indentation = ' ' * level
73 name = specification[0]
74 if not isinstance(name, str):
75 raise Exception('The first item of an EasyXml specification should be '
76 'a string. Specification was ' + str(specification))
77 xml_parts.append(indentation + '<' + name)
79 # Optionally in second position is a dictionary of the attributes.
80 rest = specification[1:]
81 if rest and isinstance(rest[0], dict):
82 for at, val in sorted(rest[0].iteritems()):
83 xml_parts.append(' %s="%s"' % (at, _XmlEscape(val, attr=True)))
87 all_strings = reduce(lambda x, y: x and isinstance(y, str), rest, True)
88 multi_line = not all_strings
89 if multi_line and new_line:
90 xml_parts.append(new_line)
91 for child_spec in rest:
92 # If it's a string, append a text node.
93 # Otherwise recurse over that child definition
94 if isinstance(child_spec, str):
95 xml_parts.append(_XmlEscape(child_spec))
97 _ConstructContentList(xml_parts, child_spec, pretty, level + 1)
98 if multi_line and indentation:
99 xml_parts.append(indentation)
100 xml_parts.append('</%s>%s' % (name, new_line))
102 xml_parts.append('/>%s' % new_line)
105 def WriteXmlIfChanged(content, path, encoding='utf-8', pretty=False,
107 """ Writes the XML content to disk, touching the file only if it has changed.
110 content: The structured content to be written.
111 path: Location of the file.
112 encoding: The encoding to report on the first line of the XML file.
113 pretty: True if we want pretty printing with indents and new lines.
115 xml_string = XmlToString(content, encoding, pretty)
116 if win32 and os.linesep != '\r\n':
117 xml_string = xml_string.replace('\n', '\r\n')
120 xml_string = xml_string.encode(encoding)
122 xml_string = unicode(xml_string, 'latin-1').encode(encoding)
124 # Get the old content
132 # It has changed, write it
133 if existing != xml_string:
150 _xml_escape_re = re.compile(
151 "(%s)" % "|".join(map(re.escape, _xml_escape_map.keys())))
154 def _XmlEscape(value, attr=False):
155 """ Escape a string for inclusion in XML."""
157 m = match.string[match.start() : match.end()]
158 # don't replace single quotes in attrs
159 if attr and m == "'":
161 return _xml_escape_map[m]
162 return _xml_escape_re.sub(replace, value)