""" Java org.xml.sax APIs implemented in Python. org.xml.sax package - XMLReader, ContentHandler, SAXException, etc. Wraps Python xml.sax module. """ from __future__ import annotations from .._core import Stub, AndroidOnlyError import xml.sax as _sax import xml.sax.handler as _handler import xml.sax.xmlreader as _reader from typing import List, Optional __all__ = [ "XMLReader", "ContentHandler", "DTDHandler", "EntityResolver", "ErrorHandler", "InputSource", "SAXException", "SAXParseException", "SAXNotRecognizedException", "SAXNotSupportedException", "Locator", "Attributes", "AttributeList", "HandlerBase", "DocumentHandler", "XMLFilter", "XMLFilterImpl", "DefaultHandler", "Helpers", ] # ============================================================ # SAXException # ============================================================ class SAXException(Exception, Stub): """ org.xml.sax.SAXException Encapsulate a general SAX error or warning. """ def __init__(self, message: str = "", exception: Exception = None): super().__init__(message) self._message = message self._exception = exception def getMessage(self) -> str: return self._message def getException(self) -> Optional[Exception]: return self._exception def toString(self) -> str: return f"SAXException: {self._message}" def equals(self, other) -> bool: if isinstance(other, SAXException): return self._message == other._message return False def hashCode(self) -> int: return hash(self._message) & 0x7FFFFFFF class SAXParseException(SAXException): """ org.xml.sax.SAXParseException Encapsulate an XML parse error or warning. """ def __init__(self, message: str = "", public_id: str = "", system_id: str = "", line: int = -1, column: int = -1, exception: Exception = None): super().__init__(message, exception) self._public_id = public_id self._system_id = system_id self._line = line self._column = column def getPublicId(self) -> str: return self._public_id def getSystemId(self) -> str: return self._system_id def getLineNumber(self) -> int: return self._line def getColumnNumber(self) -> int: return self._column def toString(self) -> str: return f"SAXParseException[line={self._line}, col={self._column}]: {self._message}" def equals(self, other) -> bool: if isinstance(other, SAXParseException): return (self._line == other._line and self._column == other._column and self._message == other._message) return False def hashCode(self) -> int: return hash((self._line, self._column, self._message)) & 0x7FFFFFFF class SAXNotRecognizedException(SAXException): """ org.xml.sax.SAXNotRecognizedException Exception for unrecognized feature or property. """ def __init__(self, message: str = ""): super().__init__(message or "Feature or property not recognized") def toString(self) -> str: return f"SAXNotRecognizedException: {self._message}" def equals(self, other) -> bool: return isinstance(other, SAXNotRecognizedException) def hashCode(self) -> int: return hash("SAXNotRecognizedException") & 0x7FFFFFFF class SAXNotSupportedException(SAXException): """ org.xml.sax.SAXNotSupportedException Exception for unsupported feature or property. """ def __init__(self, message: str = ""): super().__init__(message or "Feature or property not supported") def toString(self) -> str: return f"SAXNotSupportedException: {self._message}" def equals(self, other) -> bool: return isinstance(other, SAXNotSupportedException) def hashCode(self) -> int: return hash("SAXNotSupportedException") & 0x7FFFFFFF # ============================================================ # Locator # ============================================================ class Locator(Stub): """ org.xml.sax.Locator Interface for associating a SAX event with a document location. """ def __init__(self, public_id: str = "", system_id: str = "", line: int = -1, column: int = -1): super().__init__() self._public_id = public_id self._system_id = system_id self._line = line self._column = column def getPublicId(self) -> str: return self._public_id def getSystemId(self) -> str: return self._system_id def getLineNumber(self) -> int: return self._line def getColumnNumber(self) -> int: return self._column def toString(self) -> str: return f"Locator[line={self._line}, col={self._column}]" def equals(self, other) -> bool: return self is other def hashCode(self) -> int: return hash(self) & 0x7FFFFFFF # ============================================================ # InputSource # ============================================================ class InputSource(Stub): """ org.xml.sax.InputSource A single input source for a SAX parser. """ def __init__(self, system_id: str = None, byte_stream=None, character_stream=None, encoding: str = None): super().__init__() self._public_id = None self._system_id = system_id self._byte_stream = byte_stream self._character_stream = character_stream self._encoding = encoding def getPublicId(self) -> Optional[str]: return self._public_id def setPublicId(self, public_id: str): self._public_id = public_id def getSystemId(self) -> Optional[str]: return self._system_id def setSystemId(self, system_id: str): self._system_id = system_id def getByteStream(self): return self._byte_stream def setByteStream(self, byte_stream): self._byte_stream = byte_stream def getEncoding(self) -> Optional[str]: return self._encoding def setEncoding(self, encoding: str): self._encoding = encoding def getCharacterStream(self): return self._character_stream def setCharacterStream(self, character_stream): self._character_stream = character_stream def toString(self) -> str: return f"InputSource[systemId={self._system_id}]" def equals(self, other) -> bool: return self is other def hashCode(self) -> int: return hash(self) & 0x7FFFFFFF # ============================================================ # Attributes # ============================================================ class Attributes(Stub): """ org.xml.sax.Attributes Interface for a list of XML attributes. """ def __init__(self, attrs: List[dict] = None): super().__init__() self._attrs = attrs or [] def getLength(self) -> int: return len(self._attrs) def getURI(self, index: int) -> str: if 0 <= index < len(self._attrs): return self._attrs[index].get("uri", "") return "" def getLocalName(self, index: int) -> str: if 0 <= index < len(self._attrs): return self._attrs[index].get("localName", "") return "" def getQName(self, index: int) -> str: if 0 <= index < len(self._attrs): return self._attrs[index].get("qname", "") return "" def getType(self, index_or_name: Any) -> str: if isinstance(index_or_name, int): if 0 <= index_or_name < len(self._attrs): return self._attrs[index_or_name].get("type", "CDATA") return "" for attr in self._attrs: if attr.get("qname") == index_or_name: return attr.get("type", "CDATA") return "" def getValue(self, index_or_name: Any) -> str: if isinstance(index_or_name, int): if 0 <= index_or_name < len(self._attrs): return self._attrs[index_or_name].get("value", "") return "" for attr in self._attrs: if attr.get("qname") == index_or_name: return attr.get("value", "") return "" def getIndex(self, qname_or_uri: str, local_name: str = None) -> int: if local_name is None: for i, attr in enumerate(self._attrs): if attr.get("qname") == qname_or_uri: return i else: for i, attr in enumerate(self._attrs): if attr.get("uri") == qname_or_uri and attr.get("localName") == local_name: return i return -1 def getIndexByQName(self, qname: str) -> int: return self.getIndex(qname) def getIndexByURI(self, uri: str, local_name: str) -> int: return self.getIndex(uri, local_name) def toString(self) -> str: return f"Attributes@{hex(id(self))}[length={len(self._attrs)}]" def equals(self, other) -> bool: return self is other def hashCode(self) -> int: return hash(self) & 0x7FFFFFFF # ============================================================ # AttributeList (deprecated) # ============================================================ class AttributeList(Stub): """ org.xml.sax.AttributeList Deprecated SAX1 interface for attribute list. """ def __init__(self): super().__init__() self._attrs = [] def getLength(self) -> int: return len(self._attrs) def getName(self, index: int) -> str: if 0 <= index < len(self._attrs): return self._attrs[index].get("name", "") return "" def getType(self, index_or_name: Any) -> str: if isinstance(index_or_name, int): if 0 <= index_or_name < len(self._attrs): return self._attrs[index_or_name].get("type", "CDATA") return "" for attr in self._attrs: if attr.get("name") == index_or_name: return attr.get("type", "CDATA") return "" def getValue(self, index_or_name: Any) -> str: if isinstance(index_or_name, int): if 0 <= index_or_name < len(self._attrs): return self._attrs[index_or_name].get("value", "") return "" for attr in self._attrs: if attr.get("name") == index_or_name: return attr.get("value", "") return "" def toString(self) -> str: return f"AttributeList@{hex(id(self))}" def equals(self, other) -> bool: return self is other def hashCode(self) -> int: return hash(self) & 0x7FFFFFFF # ============================================================ # ContentHandler # ============================================================ class ContentHandler(Stub): """ org.xml.sax.ContentHandler Interface for receiving SAX content events. """ def setDocumentLocator(self, locator: Locator): pass def startDocument(self): pass def endDocument(self): pass def startPrefixMapping(self, prefix: str, uri: str): pass def endPrefixMapping(self, prefix: str): pass def startElement(self, namespace_uri: str, local_name: str, qname: str, atts: Attributes): pass def endElement(self, namespace_uri: str, local_name: str, qname: str): pass def characters(self, ch: str, start: int, length: int): pass def ignorableWhitespace(self, ch: str, start: int, length: int): pass def processingInstruction(self, target: str, data: str): pass def skippedEntity(self, name: str): pass def toString(self) -> str: return f"ContentHandler@{hex(id(self))}" def equals(self, other) -> bool: return self is other def hashCode(self) -> int: return hash(self) & 0x7FFFFFFF # ============================================================ # DTDHandler # ============================================================ class DTDHandler(Stub): """ org.xml.sax.DTDHandler Interface for receiving DTD events. """ def notationDecl(self, name: str, public_id: str, system_id: str): pass def unparsedEntityDecl(self, name: str, public_id: str, system_id: str, notation_name: str): pass def toString(self) -> str: return f"DTDHandler@{hex(id(self))}" def equals(self, other) -> bool: return self is other def hashCode(self) -> int: return hash(self) & 0x7FFFFFFF # ============================================================ # EntityResolver # ============================================================ class EntityResolver(Stub): """ org.xml.sax.EntityResolver Interface for resolving entities. """ def resolveEntity(self, public_id: str, system_id: str) -> InputSource: return InputSource(system_id) def toString(self) -> str: return f"EntityResolver@{hex(id(self))}" def equals(self, other) -> bool: return self is other def hashCode(self) -> int: return hash(self) & 0x7FFFFFFF # ============================================================ # ErrorHandler # ============================================================ class ErrorHandler(Stub): """ org.xml.sax.ErrorHandler Interface for receiving SAX error events. """ def warning(self, exception: SAXParseException): pass def error(self, exception: SAXParseException): pass def fatalError(self, exception: SAXParseException): raise exception def toString(self) -> str: return f"ErrorHandler@{hex(id(self))}" def equals(self, other) -> bool: return self is other def hashCode(self) -> int: return hash(self) & 0x7FFFFFFF # ============================================================ # HandlerBase (deprecated SAX1) # ============================================================ class HandlerBase(Stub): """ org.xml.sax.HandlerBase Deprecated SAX1 base class for handlers. """ def setDocumentLocator(self, locator: Locator): pass def startDocument(self): pass def endDocument(self): pass def startElement(self, name: str, atts: AttributeList): pass def endElement(self, name: str): pass def characters(self, ch: str, start: int, length: int): pass def ignorableWhitespace(self, ch: str, start: int, length: int): pass def processingInstruction(self, target: str, data: str): pass def notationDecl(self, name: str, public_id: str, system_id: str): pass def unparsedEntityDecl(self, name: str, public_id: str, system_id: str, notation_name: str): pass def resolveEntity(self, public_id: str, system_id: str) -> InputSource: return InputSource(system_id) def warning(self, exception: SAXParseException): pass def error(self, exception: SAXParseException): pass def fatalError(self, exception: SAXParseException): raise exception def toString(self) -> str: return f"HandlerBase@{hex(id(self))}" def equals(self, other) -> bool: return self is other def hashCode(self) -> int: return hash(self) & 0x7FFFFFFF # ============================================================ # DocumentHandler (deprecated SAX1) # ============================================================ class DocumentHandler(Stub): """ org.xml.sax.DocumentHandler Deprecated SAX1 interface for document handlers. """ def setDocumentLocator(self, locator: Locator): pass def startDocument(self): pass def endDocument(self): pass def startElement(self, name: str, atts: AttributeList): pass def endElement(self, name: str): pass def characters(self, ch: str, start: int, length: int): pass def ignorableWhitespace(self, ch: str, start: int, length: int): pass def processingInstruction(self, target: str, data: str): pass def toString(self) -> str: return f"DocumentHandler@{hex(id(self))}" def equals(self, other) -> bool: return self is other def hashCode(self) -> int: return hash(self) & 0x7FFFFFFF # ============================================================ # XMLReader # ============================================================ class XMLReader(Stub): """ org.xml.sax.XMLReader Interface for reading an XML document using callbacks. Wraps Python xml.sax. """ def __init__(self): super().__init__() self._content_handler = None self._dtd_handler = None self._error_handler = None self._entity_resolver = None self._locale = None self._features = {} self._properties = {} def parse(self, input_source: Any): """ Parse an XML document from an InputSource or system ID. """ if isinstance(input_source, InputSource): sys_id = input_source.getSystemId() elif isinstance(input_source, str): sys_id = input_source else: sys_id = str(input_source) try: parser = _sax.make_parser() if self._content_handler: parser.setContentHandler(self._content_handler) if self._error_handler: parser.setErrorHandler(self._error_handler) if self._dtd_handler: parser.setDTDHandler(self._dtd_handler) if self._entity_resolver: parser.setEntityResolver(self._entity_resolver) parser.parse(sys_id) except _sax.SAXException as e: if self._error_handler: self._error_handler.fatalError( SAXParseException(str(e), "", sys_id, 0, 0)) raise SAXException(str(e)) from e def setContentHandler(self, handler: ContentHandler): self._content_handler = handler def getContentHandler(self) -> Optional[ContentHandler]: return self._content_handler def setDTDHandler(self, handler: DTDHandler): self._dtd_handler = handler def getDTDHandler(self) -> Optional[DTDHandler]: return self._dtd_handler def setErrorHandler(self, handler: ErrorHandler): self._error_handler = handler def getErrorHandler(self) -> Optional[ErrorHandler]: return self._error_handler def setEntityResolver(self, resolver: EntityResolver): self._entity_resolver = resolver def getEntityResolver(self) -> Optional[EntityResolver]: return self._entity_resolver def setLocale(self, locale): self._locale = locale def getFeature(self, name: str) -> bool: return self._features.get(name, False) def setFeature(self, name: str, value: bool): if name == "http://xml.org/sax/features/namespaces": self._features[name] = value elif name == "http://xml.org/sax/features/validation": self._features[name] = value elif name == "http://xml.org/sax/features/external-general-entities": self._features[name] = value elif name == "http://xml.org/sax/features/external-parameter-entities": self._features[name] = value else: raise SAXNotRecognizedException(f"Feature not recognized: {name}") def getProperty(self, name: str) -> Any: return self._properties.get(name) def setProperty(self, name: str, value: Any): if name == "http://xml.org/sax/properties/lexical-handler": self._properties[name] = value else: raise SAXNotRecognizedException(f"Property not recognized: {name}") def toString(self) -> str: return f"XMLReader@{hex(id(self))}" def equals(self, other) -> bool: return self is other def hashCode(self) -> int: return hash(self) & 0x7FFFFFFF # ============================================================ # XMLFilter # ============================================================ class XMLFilter(XMLReader): """ org.xml.sax.XMLFilter Interface for an XML filter. """ def setParent(self, parent: XMLReader): self._parent = parent def getParent(self) -> Optional[XMLReader]: return getattr(self, '_parent', None) def toString(self) -> str: return f"XMLFilter@{hex(id(self))}" def equals(self, other) -> bool: return self is other def hashCode(self) -> int: return hash(self) & 0x7FFFFFFF # ============================================================ # XMLFilterImpl # ============================================================ class XMLFilterImpl(XMLFilter, ContentHandler, DTDHandler, EntityResolver, ErrorHandler): """ org.xml.sax.helpers.XMLFilterImpl Base class for XML filters with default implementation. """ def __init__(self): super().__init__() self._parent = None def setParent(self, parent: XMLReader): self._parent = parent def getParent(self) -> Optional[XMLReader]: return self._parent def parse(self, input_source: Any): if self._parent: self._parent.setContentHandler(self) self._parent.setDTDHandler(self) self._parent.setEntityResolver(self) self._parent.setErrorHandler(self) self._parent.parse(input_source) # ContentHandler delegation def setContentHandler(self, handler: ContentHandler): self._content_handler = handler def getContentHandler(self) -> Optional[ContentHandler]: return getattr(self, '_content_handler', None) def setDTDHandler(self, handler: DTDHandler): self._dtd_handler = handler def getDTDHandler(self) -> Optional[DTDHandler]: return getattr(self, '_dtd_handler', None) def setEntityResolver(self, resolver: EntityResolver): self._entity_resolver = resolver def getEntityResolver(self) -> Optional[EntityResolver]: return getattr(self, '_entity_resolver', None) def setErrorHandler(self, handler: ErrorHandler): self._error_handler = handler def getErrorHandler(self) -> Optional[ErrorHandler]: return getattr(self, '_error_handler', None) def setLocale(self, locale): pass def getFeature(self, name: str) -> bool: return False def setFeature(self, name: str, value: bool): pass def getProperty(self, name: str): return None def setProperty(self, name: str, value): pass # Delegated implementations def resolveEntity(self, public_id: str, system_id: str) -> InputSource: if self._entity_resolver: return self._entity_resolver.resolveEntity(public_id, system_id) return InputSource(system_id) def notationDecl(self, name: str, public_id: str, system_id: str): if self._dtd_handler: self._dtd_handler.notationDecl(name, public_id, system_id) def unparsedEntityDecl(self, name: str, public_id: str, system_id: str, notation_name: str): if self._dtd_handler: self._dtd_handler.unparsedEntityDecl(name, public_id, system_id, notation_name) def setDocumentLocator(self, locator: Locator): if self._content_handler: self._content_handler.setDocumentLocator(locator) def startDocument(self): if self._content_handler: self._content_handler.startDocument() def endDocument(self): if self._content_handler: self._content_handler.endDocument() def startPrefixMapping(self, prefix: str, uri: str): if self._content_handler: self._content_handler.startPrefixMapping(prefix, uri) def endPrefixMapping(self, prefix: str): if self._content_handler: self._content_handler.endPrefixMapping(prefix) def startElement(self, namespace_uri: str, local_name: str, qname: str, atts: Attributes): if self._content_handler: self._content_handler.startElement(namespace_uri, local_name, qname, atts) def endElement(self, namespace_uri: str, local_name: str, qname: str): if self._content_handler: self._content_handler.endElement(namespace_uri, local_name, qname) def characters(self, ch: str, start: int, length: int): if self._content_handler: self._content_handler.characters(ch, start, length) def ignorableWhitespace(self, ch: str, start: int, length: int): if self._content_handler: self._content_handler.ignorableWhitespace(ch, start, length) def processingInstruction(self, target: str, data: str): if self._content_handler: self._content_handler.processingInstruction(target, data) def skippedEntity(self, name: str): if self._content_handler: self._content_handler.skippedEntity(name) def warning(self, exception: SAXParseException): if self._error_handler: self._error_handler.warning(exception) def error(self, exception: SAXParseException): if self._error_handler: self._error_handler.error(exception) def fatalError(self, exception: SAXParseException): if self._error_handler: self._error_handler.fatalError(exception) raise exception def toString(self) -> str: return f"XMLFilterImpl@{hex(id(self))}" def equals(self, other) -> bool: return self is other def hashCode(self) -> int: return hash(self) & 0x7FFFFFFF # ============================================================ # DefaultHandler # ============================================================ class DefaultHandler(ContentHandler, DTDHandler, EntityResolver, ErrorHandler): """ org.xml.sax.helpers.DefaultHandler Default base class for SAX2 event handlers. """ def setDocumentLocator(self, locator: Locator): pass def startDocument(self): pass def endDocument(self): pass def startPrefixMapping(self, prefix: str, uri: str): pass def endPrefixMapping(self, prefix: str): pass def startElement(self, namespace_uri: str, local_name: str, qname: str, atts: Attributes): pass def endElement(self, namespace_uri: str, local_name: str, qname: str): pass def characters(self, ch: str, start: int, length: int): pass def ignorableWhitespace(self, ch: str, start: int, length: int): pass def processingInstruction(self, target: str, data: str): pass def skippedEntity(self, name: str): pass def notationDecl(self, name: str, public_id: str, system_id: str): pass def unparsedEntityDecl(self, name: str, public_id: str, system_id: str, notation_name: str): pass def resolveEntity(self, public_id: str, system_id: str) -> InputSource: return InputSource(system_id) def warning(self, exception: SAXParseException): pass def error(self, exception: SAXParseException): pass def fatalError(self, exception: SAXParseException): raise exception def toString(self) -> str: return f"DefaultHandler@{hex(id(self))}" def equals(self, other) -> bool: return self is other def hashCode(self) -> int: return hash(self) & 0x7FFFFFFF # ============================================================ # Helpers (stub) # ============================================================ class Helpers(Stub): """ org.xml.sax.helpers (namespace placeholder) """ @staticmethod def makeParser(parser_class: str = None): return XMLReader() @staticmethod def parseXML(source: Any, handler: ContentHandler): reader = XMLReader() reader.setContentHandler(handler) reader.parse(source) def toString(self) -> str: return "Helpers" def equals(self, other) -> bool: return isinstance(other, Helpers) def hashCode(self) -> int: return hash("Helpers") & 0x7FFFFFFF