blob: 195f3eae7a732b584618e5e2c82a71d892f1e203 [file] [log] [blame]
# Copyright (c) 2023 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 xml.sax.xmlreader
from typing import List, Optional
from matter_idl.matter_idl_types import Cluster, Idl, ParseMetaData
class IdlPostProcessor:
"""Defines a callback that will apply after an entire parsing
is complete.
"""
def FinalizeProcessing(self, idl: Idl):
"""Update idl with any post-processing directives."""
pass
class ProcessingPath:
"""Maintains the current path of tags within xml processing.
As processing descents into an xml like `<configurator><cluster>....`
paths will have contents like ['configurator', 'cluster', ...].
The main purpose for this is to log and keep track of what was visited
and in general to report things like 'this path found but was not handled'.
"""
def __init__(self, paths: Optional[List[str]] = None):
if paths is None:
paths = []
self.paths = paths
def push(self, name: str):
self.paths.append(name)
def pop(self):
self.paths.pop()
def __str__(self):
return '::'.join(self.paths)
def __repr__(self):
return 'ProcessingPath(%r)' % self.paths
class Context:
"""
Contains a processing state during XML reading.
The purpose of this is to allow elements to interact with each other, share
data and defer processing.
Usage:
- globally shared data:
> locator: parsing location, for error reporting
> path: current ProcessingPath for any logging of where we are located
- post-processing support:
> can register AddIdlPostProcessor to perform some processing once
a full parsing pass has been done
More data may be added in time if it involves separate XML parse handlers
needing to interact with each other.
"""
def __init__(self, locator: Optional[xml.sax.xmlreader.Locator] = None):
self.path = ProcessingPath()
self.locator = locator
self.file_name = None
self._not_handled: set[str] = set()
self._idl_post_processors: list[IdlPostProcessor] = []
self.abstract_base_clusters: dict[str, Cluster] = {}
def AddAbstractBaseCluster(self, name: str, parse_meta: Optional[ParseMetaData] = None) -> Cluster:
"""Creates a new cluster entry for the given name in the list of known
base clusters.
"""
assert name not in self.abstract_base_clusters # be unique
cluster = Cluster(name=name, code=-1, parse_meta=parse_meta)
self.abstract_base_clusters[name] = cluster
return cluster
def GetCurrentLocationMeta(self) -> Optional[ParseMetaData]:
if not self.locator:
return None
return ParseMetaData(line=self.locator.getLineNumber(), column=self.locator.getColumnNumber())
def ParseLogLocation(self) -> Optional[str]:
if not self.file_name:
return None
meta = self.GetCurrentLocationMeta()
if not meta:
return None
return f"{self.file_name}:{meta.line}:{meta.column}"
def MarkTagNotHandled(self):
path = str(self.path)
if path not in self._not_handled:
msg = "TAG %s was not handled/recognized" % path
where = self.ParseLogLocation()
if where:
msg = msg + " at " + where
logging.warning(msg)
self._not_handled.add(path)
def AddIdlPostProcessor(self, processor: IdlPostProcessor, has_priority: bool = False):
if has_priority:
self._idl_post_processors.insert(0, processor)
else:
self._idl_post_processors.append(processor)
def PostProcess(self, idl: Idl):
for p in self._idl_post_processors:
p.FinalizeProcessing(idl)
self._idl_post_processors = []