| # Copyright (c) 2022 Project CHIP Authors |
| # |
| # Licensed under the Apache License, Version 2.0 (the "License"); |
| # you may not use this file except in compliance with the License. |
| # You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an "AS IS" BASIS, |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| # See the License for the specific language governing permissions and |
| # limitations under the License. |
| |
| import logging |
| import typing |
| import xml.sax.handler |
| |
| from dataclasses import dataclass |
| from typing import Optional, Union, List |
| |
| from idl.zapxml.handlers import Context, ZapXmlHandler |
| from idl.matter_idl_types import Idl |
| |
| |
| class ParseHandler(xml.sax.handler.ContentHandler): |
| """A parser for ZAP-style XML data definitions. |
| |
| Defers its processing to ZapXmlHandler and keeps track of: |
| - an internal context for all handlers |
| - the parsed Idl structure that is incrementally built |
| - sets up parsing location within the context |
| - keeps track of ParsePath |
| |
| Overall converts a python SAX handler into idl.zapxml.handlers |
| """ |
| |
| def __init__(self, include_meta_data=True): |
| super().__init__() |
| self._idl = Idl() |
| self._processing_stack = [] |
| # Context persists across all |
| self._context = Context() |
| self._include_meta_data = include_meta_data |
| |
| def PrepareParsing(self, filename): |
| # This is a bit ugly: filename keeps changing during parse |
| # IDL meta is not prepared for this (as source is XML and .matter is |
| # single file) |
| if self._include_meta_data: |
| self._idl.parse_file_name = filename |
| |
| def Finish(self) -> Idl: |
| self._context.PostProcess(self._idl) |
| return self._idl |
| |
| def startDocument(self): |
| if self._include_meta_data: |
| self._context.locator = self._locator |
| self._processing_stack = [ZapXmlHandler(self._context, self._idl)] |
| |
| def endDocument(self): |
| if len(self._processing_stack) != 1: |
| raise Exception("Unexpected nesting!") |
| |
| def startElement(self, name: str, attrs): |
| logging.debug("ELEMENT START: %r / %r" % (name, attrs)) |
| self._context.path.push(name) |
| self._processing_stack.append(self._processing_stack[-1].GetNextProcessor(name, attrs)) |
| |
| def endElement(self, name: str): |
| logging.debug("ELEMENT END: %r" % name) |
| |
| last = self._processing_stack.pop() |
| last.EndProcessing() |
| |
| # important to pop AFTER processing end to allow processing |
| # end to access the current context |
| self._context.path.pop() |
| |
| def characters(self, content): |
| self._processing_stack[-1].HandleContent(content) |
| |
| |
| @dataclass |
| class ParseSource: |
| """Represents an input sopurce for ParseXmls. |
| |
| Allows for named data sources to be parsed. |
| """ |
| source: Union[str, typing.IO] # filename or stream |
| name: Optional[str] = None # actual filename to use, None if the source is a filename already |
| |
| @ property |
| def source_file_name(self): |
| if self.name: |
| return self.name |
| return self.source # assume string |
| |
| |
| def ParseXmls(sources: List[ParseSource], include_meta_data=True) -> Idl: |
| """Parse one or more XML inputs and return the resulting Idl data. |
| |
| Params: |
| sources - what to parse |
| include_meta_data - if parsing location data should be included in the Idl |
| """ |
| handler = ParseHandler(include_meta_data=include_meta_data) |
| |
| for source in sources: |
| logging.info('Parsing %s...' % source.source_file_name) |
| handler.PrepareParsing(source.source_file_name) |
| |
| parser = xml.sax.make_parser() |
| parser.setContentHandler(handler) |
| parser.parse(source.source) |
| |
| return handler.Finish() |