annotate CSP2/CSP2_env/env-d9b9114564458d9d-741b3de822f2aaca6c6caa4325c4afce/lib/python3.8/xml/sax/saxutils.py @ 69:33d812a61356

planemo upload commit 2e9511a184a1ca667c7be0c6321a36dc4e3d116d
author jpayne
date Tue, 18 Mar 2025 17:55:14 -0400
parents
children
rev   line source
jpayne@69 1 """\
jpayne@69 2 A library of useful helper classes to the SAX classes, for the
jpayne@69 3 convenience of application and driver writers.
jpayne@69 4 """
jpayne@69 5
jpayne@69 6 import os, urllib.parse, urllib.request
jpayne@69 7 import io
jpayne@69 8 import codecs
jpayne@69 9 from . import handler
jpayne@69 10 from . import xmlreader
jpayne@69 11
jpayne@69 12 def __dict_replace(s, d):
jpayne@69 13 """Replace substrings of a string using a dictionary."""
jpayne@69 14 for key, value in d.items():
jpayne@69 15 s = s.replace(key, value)
jpayne@69 16 return s
jpayne@69 17
jpayne@69 18 def escape(data, entities={}):
jpayne@69 19 """Escape &, <, and > in a string of data.
jpayne@69 20
jpayne@69 21 You can escape other strings of data by passing a dictionary as
jpayne@69 22 the optional entities parameter. The keys and values must all be
jpayne@69 23 strings; each key will be replaced with its corresponding value.
jpayne@69 24 """
jpayne@69 25
jpayne@69 26 # must do ampersand first
jpayne@69 27 data = data.replace("&", "&amp;")
jpayne@69 28 data = data.replace(">", "&gt;")
jpayne@69 29 data = data.replace("<", "&lt;")
jpayne@69 30 if entities:
jpayne@69 31 data = __dict_replace(data, entities)
jpayne@69 32 return data
jpayne@69 33
jpayne@69 34 def unescape(data, entities={}):
jpayne@69 35 """Unescape &amp;, &lt;, and &gt; in a string of data.
jpayne@69 36
jpayne@69 37 You can unescape other strings of data by passing a dictionary as
jpayne@69 38 the optional entities parameter. The keys and values must all be
jpayne@69 39 strings; each key will be replaced with its corresponding value.
jpayne@69 40 """
jpayne@69 41 data = data.replace("&lt;", "<")
jpayne@69 42 data = data.replace("&gt;", ">")
jpayne@69 43 if entities:
jpayne@69 44 data = __dict_replace(data, entities)
jpayne@69 45 # must do ampersand last
jpayne@69 46 return data.replace("&amp;", "&")
jpayne@69 47
jpayne@69 48 def quoteattr(data, entities={}):
jpayne@69 49 """Escape and quote an attribute value.
jpayne@69 50
jpayne@69 51 Escape &, <, and > in a string of data, then quote it for use as
jpayne@69 52 an attribute value. The \" character will be escaped as well, if
jpayne@69 53 necessary.
jpayne@69 54
jpayne@69 55 You can escape other strings of data by passing a dictionary as
jpayne@69 56 the optional entities parameter. The keys and values must all be
jpayne@69 57 strings; each key will be replaced with its corresponding value.
jpayne@69 58 """
jpayne@69 59 entities = {**entities, '\n': '&#10;', '\r': '&#13;', '\t':'&#9;'}
jpayne@69 60 data = escape(data, entities)
jpayne@69 61 if '"' in data:
jpayne@69 62 if "'" in data:
jpayne@69 63 data = '"%s"' % data.replace('"', "&quot;")
jpayne@69 64 else:
jpayne@69 65 data = "'%s'" % data
jpayne@69 66 else:
jpayne@69 67 data = '"%s"' % data
jpayne@69 68 return data
jpayne@69 69
jpayne@69 70
jpayne@69 71 def _gettextwriter(out, encoding):
jpayne@69 72 if out is None:
jpayne@69 73 import sys
jpayne@69 74 return sys.stdout
jpayne@69 75
jpayne@69 76 if isinstance(out, io.TextIOBase):
jpayne@69 77 # use a text writer as is
jpayne@69 78 return out
jpayne@69 79
jpayne@69 80 if isinstance(out, (codecs.StreamWriter, codecs.StreamReaderWriter)):
jpayne@69 81 # use a codecs stream writer as is
jpayne@69 82 return out
jpayne@69 83
jpayne@69 84 # wrap a binary writer with TextIOWrapper
jpayne@69 85 if isinstance(out, io.RawIOBase):
jpayne@69 86 # Keep the original file open when the TextIOWrapper is
jpayne@69 87 # destroyed
jpayne@69 88 class _wrapper:
jpayne@69 89 __class__ = out.__class__
jpayne@69 90 def __getattr__(self, name):
jpayne@69 91 return getattr(out, name)
jpayne@69 92 buffer = _wrapper()
jpayne@69 93 buffer.close = lambda: None
jpayne@69 94 else:
jpayne@69 95 # This is to handle passed objects that aren't in the
jpayne@69 96 # IOBase hierarchy, but just have a write method
jpayne@69 97 buffer = io.BufferedIOBase()
jpayne@69 98 buffer.writable = lambda: True
jpayne@69 99 buffer.write = out.write
jpayne@69 100 try:
jpayne@69 101 # TextIOWrapper uses this methods to determine
jpayne@69 102 # if BOM (for UTF-16, etc) should be added
jpayne@69 103 buffer.seekable = out.seekable
jpayne@69 104 buffer.tell = out.tell
jpayne@69 105 except AttributeError:
jpayne@69 106 pass
jpayne@69 107 return io.TextIOWrapper(buffer, encoding=encoding,
jpayne@69 108 errors='xmlcharrefreplace',
jpayne@69 109 newline='\n',
jpayne@69 110 write_through=True)
jpayne@69 111
jpayne@69 112 class XMLGenerator(handler.ContentHandler):
jpayne@69 113
jpayne@69 114 def __init__(self, out=None, encoding="iso-8859-1", short_empty_elements=False):
jpayne@69 115 handler.ContentHandler.__init__(self)
jpayne@69 116 out = _gettextwriter(out, encoding)
jpayne@69 117 self._write = out.write
jpayne@69 118 self._flush = out.flush
jpayne@69 119 self._ns_contexts = [{}] # contains uri -> prefix dicts
jpayne@69 120 self._current_context = self._ns_contexts[-1]
jpayne@69 121 self._undeclared_ns_maps = []
jpayne@69 122 self._encoding = encoding
jpayne@69 123 self._short_empty_elements = short_empty_elements
jpayne@69 124 self._pending_start_element = False
jpayne@69 125
jpayne@69 126 def _qname(self, name):
jpayne@69 127 """Builds a qualified name from a (ns_url, localname) pair"""
jpayne@69 128 if name[0]:
jpayne@69 129 # Per http://www.w3.org/XML/1998/namespace, The 'xml' prefix is
jpayne@69 130 # bound by definition to http://www.w3.org/XML/1998/namespace. It
jpayne@69 131 # does not need to be declared and will not usually be found in
jpayne@69 132 # self._current_context.
jpayne@69 133 if 'http://www.w3.org/XML/1998/namespace' == name[0]:
jpayne@69 134 return 'xml:' + name[1]
jpayne@69 135 # The name is in a non-empty namespace
jpayne@69 136 prefix = self._current_context[name[0]]
jpayne@69 137 if prefix:
jpayne@69 138 # If it is not the default namespace, prepend the prefix
jpayne@69 139 return prefix + ":" + name[1]
jpayne@69 140 # Return the unqualified name
jpayne@69 141 return name[1]
jpayne@69 142
jpayne@69 143 def _finish_pending_start_element(self,endElement=False):
jpayne@69 144 if self._pending_start_element:
jpayne@69 145 self._write('>')
jpayne@69 146 self._pending_start_element = False
jpayne@69 147
jpayne@69 148 # ContentHandler methods
jpayne@69 149
jpayne@69 150 def startDocument(self):
jpayne@69 151 self._write('<?xml version="1.0" encoding="%s"?>\n' %
jpayne@69 152 self._encoding)
jpayne@69 153
jpayne@69 154 def endDocument(self):
jpayne@69 155 self._flush()
jpayne@69 156
jpayne@69 157 def startPrefixMapping(self, prefix, uri):
jpayne@69 158 self._ns_contexts.append(self._current_context.copy())
jpayne@69 159 self._current_context[uri] = prefix
jpayne@69 160 self._undeclared_ns_maps.append((prefix, uri))
jpayne@69 161
jpayne@69 162 def endPrefixMapping(self, prefix):
jpayne@69 163 self._current_context = self._ns_contexts[-1]
jpayne@69 164 del self._ns_contexts[-1]
jpayne@69 165
jpayne@69 166 def startElement(self, name, attrs):
jpayne@69 167 self._finish_pending_start_element()
jpayne@69 168 self._write('<' + name)
jpayne@69 169 for (name, value) in attrs.items():
jpayne@69 170 self._write(' %s=%s' % (name, quoteattr(value)))
jpayne@69 171 if self._short_empty_elements:
jpayne@69 172 self._pending_start_element = True
jpayne@69 173 else:
jpayne@69 174 self._write(">")
jpayne@69 175
jpayne@69 176 def endElement(self, name):
jpayne@69 177 if self._pending_start_element:
jpayne@69 178 self._write('/>')
jpayne@69 179 self._pending_start_element = False
jpayne@69 180 else:
jpayne@69 181 self._write('</%s>' % name)
jpayne@69 182
jpayne@69 183 def startElementNS(self, name, qname, attrs):
jpayne@69 184 self._finish_pending_start_element()
jpayne@69 185 self._write('<' + self._qname(name))
jpayne@69 186
jpayne@69 187 for prefix, uri in self._undeclared_ns_maps:
jpayne@69 188 if prefix:
jpayne@69 189 self._write(' xmlns:%s="%s"' % (prefix, uri))
jpayne@69 190 else:
jpayne@69 191 self._write(' xmlns="%s"' % uri)
jpayne@69 192 self._undeclared_ns_maps = []
jpayne@69 193
jpayne@69 194 for (name, value) in attrs.items():
jpayne@69 195 self._write(' %s=%s' % (self._qname(name), quoteattr(value)))
jpayne@69 196 if self._short_empty_elements:
jpayne@69 197 self._pending_start_element = True
jpayne@69 198 else:
jpayne@69 199 self._write(">")
jpayne@69 200
jpayne@69 201 def endElementNS(self, name, qname):
jpayne@69 202 if self._pending_start_element:
jpayne@69 203 self._write('/>')
jpayne@69 204 self._pending_start_element = False
jpayne@69 205 else:
jpayne@69 206 self._write('</%s>' % self._qname(name))
jpayne@69 207
jpayne@69 208 def characters(self, content):
jpayne@69 209 if content:
jpayne@69 210 self._finish_pending_start_element()
jpayne@69 211 if not isinstance(content, str):
jpayne@69 212 content = str(content, self._encoding)
jpayne@69 213 self._write(escape(content))
jpayne@69 214
jpayne@69 215 def ignorableWhitespace(self, content):
jpayne@69 216 if content:
jpayne@69 217 self._finish_pending_start_element()
jpayne@69 218 if not isinstance(content, str):
jpayne@69 219 content = str(content, self._encoding)
jpayne@69 220 self._write(content)
jpayne@69 221
jpayne@69 222 def processingInstruction(self, target, data):
jpayne@69 223 self._finish_pending_start_element()
jpayne@69 224 self._write('<?%s %s?>' % (target, data))
jpayne@69 225
jpayne@69 226
jpayne@69 227 class XMLFilterBase(xmlreader.XMLReader):
jpayne@69 228 """This class is designed to sit between an XMLReader and the
jpayne@69 229 client application's event handlers. By default, it does nothing
jpayne@69 230 but pass requests up to the reader and events on to the handlers
jpayne@69 231 unmodified, but subclasses can override specific methods to modify
jpayne@69 232 the event stream or the configuration requests as they pass
jpayne@69 233 through."""
jpayne@69 234
jpayne@69 235 def __init__(self, parent = None):
jpayne@69 236 xmlreader.XMLReader.__init__(self)
jpayne@69 237 self._parent = parent
jpayne@69 238
jpayne@69 239 # ErrorHandler methods
jpayne@69 240
jpayne@69 241 def error(self, exception):
jpayne@69 242 self._err_handler.error(exception)
jpayne@69 243
jpayne@69 244 def fatalError(self, exception):
jpayne@69 245 self._err_handler.fatalError(exception)
jpayne@69 246
jpayne@69 247 def warning(self, exception):
jpayne@69 248 self._err_handler.warning(exception)
jpayne@69 249
jpayne@69 250 # ContentHandler methods
jpayne@69 251
jpayne@69 252 def setDocumentLocator(self, locator):
jpayne@69 253 self._cont_handler.setDocumentLocator(locator)
jpayne@69 254
jpayne@69 255 def startDocument(self):
jpayne@69 256 self._cont_handler.startDocument()
jpayne@69 257
jpayne@69 258 def endDocument(self):
jpayne@69 259 self._cont_handler.endDocument()
jpayne@69 260
jpayne@69 261 def startPrefixMapping(self, prefix, uri):
jpayne@69 262 self._cont_handler.startPrefixMapping(prefix, uri)
jpayne@69 263
jpayne@69 264 def endPrefixMapping(self, prefix):
jpayne@69 265 self._cont_handler.endPrefixMapping(prefix)
jpayne@69 266
jpayne@69 267 def startElement(self, name, attrs):
jpayne@69 268 self._cont_handler.startElement(name, attrs)
jpayne@69 269
jpayne@69 270 def endElement(self, name):
jpayne@69 271 self._cont_handler.endElement(name)
jpayne@69 272
jpayne@69 273 def startElementNS(self, name, qname, attrs):
jpayne@69 274 self._cont_handler.startElementNS(name, qname, attrs)
jpayne@69 275
jpayne@69 276 def endElementNS(self, name, qname):
jpayne@69 277 self._cont_handler.endElementNS(name, qname)
jpayne@69 278
jpayne@69 279 def characters(self, content):
jpayne@69 280 self._cont_handler.characters(content)
jpayne@69 281
jpayne@69 282 def ignorableWhitespace(self, chars):
jpayne@69 283 self._cont_handler.ignorableWhitespace(chars)
jpayne@69 284
jpayne@69 285 def processingInstruction(self, target, data):
jpayne@69 286 self._cont_handler.processingInstruction(target, data)
jpayne@69 287
jpayne@69 288 def skippedEntity(self, name):
jpayne@69 289 self._cont_handler.skippedEntity(name)
jpayne@69 290
jpayne@69 291 # DTDHandler methods
jpayne@69 292
jpayne@69 293 def notationDecl(self, name, publicId, systemId):
jpayne@69 294 self._dtd_handler.notationDecl(name, publicId, systemId)
jpayne@69 295
jpayne@69 296 def unparsedEntityDecl(self, name, publicId, systemId, ndata):
jpayne@69 297 self._dtd_handler.unparsedEntityDecl(name, publicId, systemId, ndata)
jpayne@69 298
jpayne@69 299 # EntityResolver methods
jpayne@69 300
jpayne@69 301 def resolveEntity(self, publicId, systemId):
jpayne@69 302 return self._ent_handler.resolveEntity(publicId, systemId)
jpayne@69 303
jpayne@69 304 # XMLReader methods
jpayne@69 305
jpayne@69 306 def parse(self, source):
jpayne@69 307 self._parent.setContentHandler(self)
jpayne@69 308 self._parent.setErrorHandler(self)
jpayne@69 309 self._parent.setEntityResolver(self)
jpayne@69 310 self._parent.setDTDHandler(self)
jpayne@69 311 self._parent.parse(source)
jpayne@69 312
jpayne@69 313 def setLocale(self, locale):
jpayne@69 314 self._parent.setLocale(locale)
jpayne@69 315
jpayne@69 316 def getFeature(self, name):
jpayne@69 317 return self._parent.getFeature(name)
jpayne@69 318
jpayne@69 319 def setFeature(self, name, state):
jpayne@69 320 self._parent.setFeature(name, state)
jpayne@69 321
jpayne@69 322 def getProperty(self, name):
jpayne@69 323 return self._parent.getProperty(name)
jpayne@69 324
jpayne@69 325 def setProperty(self, name, value):
jpayne@69 326 self._parent.setProperty(name, value)
jpayne@69 327
jpayne@69 328 # XMLFilter methods
jpayne@69 329
jpayne@69 330 def getParent(self):
jpayne@69 331 return self._parent
jpayne@69 332
jpayne@69 333 def setParent(self, parent):
jpayne@69 334 self._parent = parent
jpayne@69 335
jpayne@69 336 # --- Utility functions
jpayne@69 337
jpayne@69 338 def prepare_input_source(source, base=""):
jpayne@69 339 """This function takes an InputSource and an optional base URL and
jpayne@69 340 returns a fully resolved InputSource object ready for reading."""
jpayne@69 341
jpayne@69 342 if isinstance(source, os.PathLike):
jpayne@69 343 source = os.fspath(source)
jpayne@69 344 if isinstance(source, str):
jpayne@69 345 source = xmlreader.InputSource(source)
jpayne@69 346 elif hasattr(source, "read"):
jpayne@69 347 f = source
jpayne@69 348 source = xmlreader.InputSource()
jpayne@69 349 if isinstance(f.read(0), str):
jpayne@69 350 source.setCharacterStream(f)
jpayne@69 351 else:
jpayne@69 352 source.setByteStream(f)
jpayne@69 353 if hasattr(f, "name") and isinstance(f.name, str):
jpayne@69 354 source.setSystemId(f.name)
jpayne@69 355
jpayne@69 356 if source.getCharacterStream() is None and source.getByteStream() is None:
jpayne@69 357 sysid = source.getSystemId()
jpayne@69 358 basehead = os.path.dirname(os.path.normpath(base))
jpayne@69 359 sysidfilename = os.path.join(basehead, sysid)
jpayne@69 360 if os.path.isfile(sysidfilename):
jpayne@69 361 source.setSystemId(sysidfilename)
jpayne@69 362 f = open(sysidfilename, "rb")
jpayne@69 363 else:
jpayne@69 364 source.setSystemId(urllib.parse.urljoin(base, sysid))
jpayne@69 365 f = urllib.request.urlopen(source.getSystemId())
jpayne@69 366
jpayne@69 367 source.setByteStream(f)
jpayne@69 368
jpayne@69 369 return source