Initial commit
[yaffs-website] / node_modules / node-gyp / gyp / pylib / gyp / easy_xml.py
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.
4
5 import re
6 import os
7
8
9 def XmlToString(content, encoding='utf-8', pretty=False):
10   """ Writes the XML content to disk, touching the file only if it has changed.
11
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.
15
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.
21
22   Example 1:
23       <test/>
24   becomes
25       ['test']
26
27   Example 2:
28       <myelement a='value1' b='value2'>
29          <childtype>This is</childtype>
30          <childtype>it!</childtype>
31       </myelement>
32
33   becomes
34       ['myelement', {'a':'value1', 'b':'value2'},
35          ['childtype', 'This is'],
36          ['childtype', 'it!'],
37       ]
38
39   Args:
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.
43
44   Returns:
45     The XML content as a string.
46   """
47   # We create a huge list of all the elements of the file.
48   xml_parts = ['<?xml version="1.0" encoding="%s"?>' % encoding]
49   if pretty:
50     xml_parts.append('\n')
51   _ConstructContentList(xml_parts, content, pretty)
52
53   # Convert it to a string
54   return ''.join(xml_parts)
55
56
57 def _ConstructContentList(xml_parts, specification, pretty, level=0):
58   """ Appends the XML parts corresponding to the specification.
59
60   Args:
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.
65   """
66   # The first item in a specification is the name of the element.
67   if pretty:
68     indentation = '  ' * level
69     new_line = '\n'
70   else:
71     indentation = ''
72     new_line = ''
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)
78
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)))
84     rest = rest[1:]
85   if rest:
86     xml_parts.append('>')
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))
96       else:
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))
101   else:
102     xml_parts.append('/>%s' % new_line)
103
104
105 def WriteXmlIfChanged(content, path, encoding='utf-8', pretty=False,
106                       win32=False):
107   """ Writes the XML content to disk, touching the file only if it has changed.
108
109   Args:
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.
114   """
115   xml_string = XmlToString(content, encoding, pretty)
116   if win32 and os.linesep != '\r\n':
117     xml_string = xml_string.replace('\n', '\r\n')
118     
119   try:
120     xml_string = xml_string.encode(encoding)
121   except Exception:
122     xml_string = unicode(xml_string, 'latin-1').encode(encoding)
123
124   # Get the old content
125   try:
126     f = open(path, 'r')
127     existing = f.read()
128     f.close()
129   except:
130     existing = None
131
132   # It has changed, write it
133   if existing != xml_string:
134     f = open(path, 'w')
135     f.write(xml_string)
136     f.close()
137
138
139 _xml_escape_map = {
140     '"': '&quot;',
141     "'": '&apos;',
142     '<': '&lt;',
143     '>': '&gt;',
144     '&': '&amp;',
145     '\n': '&#xA;',
146     '\r': '&#xD;',
147 }
148
149
150 _xml_escape_re = re.compile(
151     "(%s)" % "|".join(map(re.escape, _xml_escape_map.keys())))
152
153
154 def _XmlEscape(value, attr=False):
155   """ Escape a string for inclusion in XML."""
156   def replace(match):
157     m = match.string[match.start() : match.end()]
158     # don't replace single quotes in attrs
159     if attr and m == "'":
160       return m
161     return _xml_escape_map[m]
162   return _xml_escape_re.sub(replace, value)