• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#/usr/bin/env python3
2#
3# Copyright (C) 2018 The Android Open Source Project
4#
5# Licensed under the Apache License, Version 2.0 (the "License"); you may not
6# use this file except in compliance with the License. You may obtain a copy of
7# the License at
8#
9# http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14# License for the specific language governing permissions and limitations under
15# the License.
16
17#
18# SimpleXMLWriter
19# $Id: SimpleXMLWriter.py 3265 2007-09-06 20:42:00Z fredrik $
20#
21# a simple XML writer
22#
23# history:
24# 2001-12-28 fl   created
25# 2002-11-25 fl   fixed attribute encoding
26# 2002-12-02 fl   minor fixes for 1.5.2
27# 2004-06-17 fl   added pythondoc markup
28# 2004-07-23 fl   added flush method (from Jay Graves)
29# 2004-10-03 fl   added declaration method
30#
31# Copyright (c) 2001-2004 by Fredrik Lundh
32#
33# fredrik@pythonware.com
34# http://www.pythonware.com
35#
36# --------------------------------------------------------------------
37# The SimpleXMLWriter module is
38#
39# Copyright (c) 2001-2004 by Fredrik Lundh
40#
41# By obtaining, using, and/or copying this software and/or its
42# associated documentation, you agree that you have read, understood,
43# and will comply with the following terms and conditions:
44#
45# Permission to use, copy, modify, and distribute this software and
46# its associated documentation for any purpose and without fee is
47# hereby granted, provided that the above copyright notice appears in
48# all copies, and that both that copyright notice and this permission
49# notice appear in supporting documentation, and that the name of
50# Secret Labs AB or the author not be used in advertising or publicity
51# pertaining to distribution of the software without specific, written
52# prior permission.
53#
54# SECRET LABS AB AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD
55# TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANT-
56# ABILITY AND FITNESS.  IN NO EVENT SHALL SECRET LABS AB OR THE AUTHOR
57# BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY
58# DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
59# WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
60# ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
61# OF THIS SOFTWARE.
62# --------------------------------------------------------------------
63
64##
65# Tools to write XML files, without having to deal with encoding
66# issues, well-formedness, etc.
67# <p>
68# The current version does not provide built-in support for
69# namespaces. To create files using namespaces, you have to provide
70# "xmlns" attributes and explicitly add prefixes to tags and
71# attributes.
72#
73# <h3>Patterns</h3>
74#
75# The following example generates a small XHTML document.
76# <pre>
77#
78# from elementtree.SimpleXMLWriter import XMLWriter
79# import sys
80#
81# w = XMLWriter(sys.stdout)
82#
83# html = w.start("html")
84#
85# w.start("head")
86# w.element("title", "my document")
87# w.element("meta", name="generator", value="my application 1.0")
88# w.end()
89#
90# w.start("body")
91# w.element("h1", "this is a heading")
92# w.element("p", "this is a paragraph")
93#
94# w.start("p")
95# w.data("this is ")
96# w.element("b", "bold")
97# w.data(" and ")
98# w.element("i", "italic")
99# w.data(".")
100# w.end("p")
101#
102# w.close(html)
103# </pre>
104##
105
106import re, sys, string
107
108try:
109    unicode("")
110except NameError:
111
112    def encode(s, encoding):
113        # 1.5.2: application must use the right encoding
114        return s
115
116    _escape = re.compile(r"[&<>\"\x80-\xff]+")  # 1.5.2
117else:
118
119    def encode(s, encoding):
120        return s.encode(encoding)
121
122    _escape = re.compile(eval(r'u"[&<>\"\u0080-\uffff]+"'))
123
124
125def encode_entity(text, pattern=_escape):
126    # map reserved and non-ascii characters to numerical entities
127    def escape_entities(m):
128        out = []
129        for char in m.group():
130            out.append("&#%d;" % ord(char))
131        return string.join(out, "")
132
133    return encode(pattern.sub(escape_entities, text), "ascii")
134
135
136del _escape
137
138#
139# the following functions assume an ascii-compatible encoding
140# (or "utf-16")
141
142
143def escape_cdata(s, encoding=None):
144    s = s.replace("&", "&amp;")
145    s = s.replace("<", "&lt;")
146    s = s.replace(">", "&gt;")
147    if encoding:
148        try:
149            return encode(s, encoding)
150        except UnicodeError:
151            return encode_entity(s)
152    return s
153
154
155def escape_attrib(s, encoding=None):
156    s = s.replace("&", "&amp;")
157    s = s.replace("'", "&apos;")
158    s = s.replace("\"", "&quot;")
159    s = s.replace("<", "&lt;")
160    s = s.replace(">", "&gt;")
161    if encoding:
162        try:
163            return encode(s, encoding)
164        except UnicodeError:
165            return encode_entity(s)
166    return s
167
168
169##
170# XML writer class.
171#
172# @param file A file or file-like object.  This object must implement
173#    a <b>write</b> method that takes an 8-bit string.
174# @param encoding Optional encoding.
175
176
177class XMLWriter:
178    def __init__(self, file, encoding="us-ascii"):
179        if not hasattr(file, "write"):
180            file = open(file, "w")
181        self.__write = file.write
182        if hasattr(file, "flush"):
183            self.flush = file.flush
184        self.__open = 0  # true if start tag is open
185        self.__tags = []
186        self.__data = []
187        self.__encoding = encoding
188
189    def __flush(self):
190        # flush internal buffers
191        if self.__open:
192            self.__write(">")
193            self.__open = 0
194        if self.__data:
195            data = string.join(self.__data, "")
196            self.__write(escape_cdata(data, self.__encoding))
197            self.__data = []
198
199    ##
200    # Writes an XML declaration.
201
202    def declaration(self):
203        encoding = self.__encoding
204        if encoding == "us-ascii" or encoding == "utf-8":
205            self.__write("<?xml version='1.0'?>\n")
206        else:
207            self.__write("<?xml version='1.0' encoding='%s'?>\n" % encoding)
208
209    ##
210    # Opens a new element.  Attributes can be given as keyword
211    # arguments, or as a string/string dictionary. You can pass in
212    # 8-bit strings or Unicode strings; the former are assumed to use
213    # the encoding passed to the constructor.  The method returns an
214    # opaque identifier that can be passed to the <b>close</b> method,
215    # to close all open elements up to and including this one.
216    #
217    # @param tag Element tag.
218    # @param attrib Attribute dictionary.  Alternatively, attributes
219    #    can be given as keyword arguments.
220    # @return An element identifier.
221
222    def start(self, tag, attrib={}, **extra):
223        self.__flush()
224        tag = escape_cdata(tag, self.__encoding)
225        self.__data = []
226        self.__tags.append(tag)
227        self.__write("<%s" % tag)
228        if attrib or extra:
229            attrib = attrib.copy()
230            attrib.update(extra)
231            attrib = attrib.items()
232            attrib.sort()
233            for k, v in attrib:
234                k = escape_cdata(k, self.__encoding)
235                v = escape_attrib(v, self.__encoding)
236                self.__write(" %s=\"%s\"" % (k, v))
237        self.__open = 1
238        return len(self.__tags) - 1
239
240    ##
241    # Adds a comment to the output stream.
242    #
243    # @param comment Comment text, as an 8-bit string or Unicode string.
244
245    def comment(self, comment):
246        self.__flush()
247        self.__write("<!-- %s -->\n" % escape_cdata(comment, self.__encoding))
248
249    ##
250    # Adds character data to the output stream.
251    #
252    # @param text Character data, as an 8-bit string or Unicode string.
253
254    def data(self, text):
255        self.__data.append(text)
256
257    ##
258    # Closes the current element (opened by the most recent call to
259    # <b>start</b>).
260    #
261    # @param tag Element tag.  If given, the tag must match the start
262    #    tag.  If omitted, the current element is closed.
263
264    def end(self, tag=None):
265        if tag:
266            assert self.__tags, "unbalanced end(%s)" % tag
267            assert escape_cdata(tag, self.__encoding) == self.__tags[-1],\
268                   "expected end(%s), got %s" % (self.__tags[-1], tag)
269        else:
270            assert self.__tags, "unbalanced end()"
271        tag = self.__tags.pop()
272        if self.__data:
273            self.__flush()
274        elif self.__open:
275            self.__open = 0
276            self.__write(" />")
277            return
278        self.__write("</%s>" % tag)
279
280    ##
281    # Closes open elements, up to (and including) the element identified
282    # by the given identifier.
283    #
284    # @param id Element identifier, as returned by the <b>start</b> method.
285
286    def close(self, id):
287        while len(self.__tags) > id:
288            self.end()
289
290    ##
291    # Adds an entire element.  This is the same as calling <b>start</b>,
292    # <b>data</b>, and <b>end</b> in sequence. The <b>text</b> argument
293    # can be omitted.
294
295    def element(self, tag, text=None, attrib={}, **extra):
296        apply(self.start, (tag, attrib), extra)
297        if text:
298            self.data(text)
299        self.end()
300
301    ##
302    # Flushes the output stream.
303
304    def flush(self):
305        pass  # replaced by the constructor
306