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("&", "&") 145 s = s.replace("<", "<") 146 s = s.replace(">", ">") 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("&", "&") 157 s = s.replace("'", "'") 158 s = s.replace("\"", """) 159 s = s.replace("<", "<") 160 s = s.replace(">", ">") 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