diff options
Diffstat (limited to 'third_party/python/gyp/pylib/gyp/easy_xml.py')
-rw-r--r-- | third_party/python/gyp/pylib/gyp/easy_xml.py | 170 |
1 files changed, 170 insertions, 0 deletions
diff --git a/third_party/python/gyp/pylib/gyp/easy_xml.py b/third_party/python/gyp/pylib/gyp/easy_xml.py new file mode 100644 index 0000000000..2de51e25fb --- /dev/null +++ b/third_party/python/gyp/pylib/gyp/easy_xml.py @@ -0,0 +1,170 @@ +# Copyright (c) 2011 Google Inc. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import re +import os +import locale +import sys + +try: + # reduce moved to functools in python3. + reduce +except NameError: + from functools import reduce + +def XmlToString(content, encoding='utf-8', pretty=False): + """ Writes the XML content to disk, touching the file only if it has changed. + + Visual Studio files have a lot of pre-defined structures. This function makes + it easy to represent these structures as Python data structures, instead of + having to create a lot of function calls. + + Each XML element of the content is represented as a list composed of: + 1. The name of the element, a string, + 2. The attributes of the element, a dictionary (optional), and + 3+. The content of the element, if any. Strings are simple text nodes and + lists are child elements. + + Example 1: + <test/> + becomes + ['test'] + + Example 2: + <myelement a='value1' b='value2'> + <childtype>This is</childtype> + <childtype>it!</childtype> + </myelement> + + becomes + ['myelement', {'a':'value1', 'b':'value2'}, + ['childtype', 'This is'], + ['childtype', 'it!'], + ] + + Args: + content: The structured content to be converted. + encoding: The encoding to report on the first XML line. + pretty: True if we want pretty printing with indents and new lines. + + Returns: + The XML content as a string. + """ + # We create a huge list of all the elements of the file. + xml_parts = ['<?xml version="1.0" encoding="%s"?>' % encoding] + if pretty: + xml_parts.append('\n') + _ConstructContentList(xml_parts, content, pretty) + + # Convert it to a string + return ''.join(xml_parts) + + +def _ConstructContentList(xml_parts, specification, pretty, level=0): + """ Appends the XML parts corresponding to the specification. + + Args: + xml_parts: A list of XML parts to be appended to. + specification: The specification of the element. See EasyXml docs. + pretty: True if we want pretty printing with indents and new lines. + level: Indentation level. + """ + # The first item in a specification is the name of the element. + if pretty: + indentation = ' ' * level + new_line = '\n' + else: + indentation = '' + new_line = '' + name = specification[0] + if not isinstance(name, str): + raise Exception('The first item of an EasyXml specification should be ' + 'a string. Specification was ' + str(specification)) + xml_parts.append(indentation + '<' + name) + + # Optionally in second position is a dictionary of the attributes. + rest = specification[1:] + if rest and isinstance(rest[0], dict): + for at, val in sorted(rest[0].items()): + xml_parts.append(' %s="%s"' % (at, _XmlEscape(val, attr=True))) + rest = rest[1:] + if rest: + xml_parts.append('>') + all_strings = reduce(lambda x, y: x and isinstance(y, str), rest, True) + multi_line = not all_strings + if multi_line and new_line: + xml_parts.append(new_line) + for child_spec in rest: + # If it's a string, append a text node. + # Otherwise recurse over that child definition + if isinstance(child_spec, str): + xml_parts.append(_XmlEscape(child_spec)) + else: + _ConstructContentList(xml_parts, child_spec, pretty, level + 1) + if multi_line and indentation: + xml_parts.append(indentation) + xml_parts.append('</%s>%s' % (name, new_line)) + else: + xml_parts.append('/>%s' % new_line) + + +def WriteXmlIfChanged(content, path, encoding='utf-8', pretty=False, + win32=False): + """ Writes the XML content to disk, touching the file only if it has changed. + + Args: + content: The structured content to be written. + path: Location of the file. + encoding: The encoding to report on the first line of the XML file. + pretty: True if we want pretty printing with indents and new lines. + """ + xml_string = XmlToString(content, encoding, pretty) + if win32 and os.linesep != '\r\n': + xml_string = xml_string.replace('\n', '\r\n') + default_encoding = locale.getdefaultlocale()[1] + if default_encoding and default_encoding.upper() != encoding.upper(): + try: + xml_string = xml_string.decode(default_encoding).encode(encoding) + except AttributeError: + pass + + # Get the old content + try: + f = open(path, 'r') + existing = f.read() + f.close() + except: + existing = None + + # It has changed, write it + if existing != xml_string: + f = open(path, 'w') + f.write(xml_string) + f.close() + + +_xml_escape_map = { + '"': '"', + "'": ''', + '<': '<', + '>': '>', + '&': '&', + '\n': '
', + '\r': '
', +} + + +_xml_escape_re = re.compile( + "(%s)" % "|".join(map(re.escape, _xml_escape_map.keys()))) + + +def _XmlEscape(value, attr=False): + """ Escape a string for inclusion in XML.""" + def replace(match): + m = match.string[match.start() : match.end()] + # don't replace single quotes in attrs + if attr and m == "'": + return m + return _xml_escape_map[m] + return _xml_escape_re.sub(replace, value) |