| Viewing file:  easy_xml.py (5.3 KB)      -rw-r--r-- Select action/file-type:
 
  (+) |  (+) |  (+) | Code (+) | Session (+) |  (+) | SDB (+) |  (+) |  (+) |  (+) |  (+) |  (+) | 
 
# 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 sys
 import re
 import os
 import locale
 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(f' {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(f"</{name}>{new_line}")
 else:
 xml_parts.append("/>%s" % new_line)
 
 
 def WriteXmlIfChanged(content, path, encoding="utf-8", pretty=False,
 win32=(sys.platform == "win32")):
 """ 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")
 
 try:  # getdefaultlocale() was removed in Python 3.11
 default_encoding = locale.getdefaultlocale()[1]
 except AttributeError:
 default_encoding = locale.getencoding()
 
 if default_encoding and default_encoding.upper() != encoding.upper():
 xml_string = xml_string.encode(encoding)
 
 # Get the old content
 try:
 with open(path) as file:
 existing = file.read()
 except OSError:
 existing = None
 
 # It has changed, write it
 if existing != xml_string:
 with open(path, "wb") as file:
 file.write(xml_string)
 
 
 _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)
 
 |