blob: 332d1c244bea3bfb8f642830e4d2168700935242 [file] [log] [blame]
# 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()