Chronaxis用户手册English
本页目录

chronaxis.configuration.yaml_parser

源码基线:8904c2f744951857659abd40aaa3535d57faf53c

1"""通过独立纯Python安全解析器读取YAML1.2受限子集,不构造任意标签对象。"""2from math import isfinite34from .models import ConfigurationError, ConfigurationLocation5from .json_parser import MAX_DEPTH678def parse_yaml(text, source):9    """用事件流构造一次原生值树。参数: 文本/来源。返回: 数据和位置表。异常: 受限语法错误。"""10    # JSON入口不依赖YAML导入;每次新实例,失败或并发加载不共享解析器状态。11    from ruamel.yaml import YAML12    from ruamel.yaml.events import (StreamStartEvent,StreamEndEvent,DocumentStartEvent,13        DocumentEndEvent,ScalarEvent,SequenceStartEvent,SequenceEndEvent,14        MappingStartEvent,MappingEndEvent,AliasEvent)15    from ruamel.yaml.nodes import ScalarNode16    from ruamel.yaml.error import YAMLError1718    class Yaml12(YAML):19        """版本在交给依赖校验前受限,避免依赖对1.3等版本抛出AssertionError。"""2021        @YAML.version.setter22        def version(self, selected):23            """只允许默认或1.2。参数: 解析器版本。返回: None。异常: 明确配置版本错误。"""24            if selected is not None and tuple(selected)!=(1,2):25                # 依赖在文档起点事件前设版本;没有可靠mark时只报告文件,不猜行号。26                raise ConfigurationError(source,(),None,'yaml_version','只支持YAML 1.2')27            YAML.version.fset(self,selected)2829    parser=Yaml12(typ='safe',pure=True)30    parser.version=(1,2)31    parser.allow_duplicate_keys=False32    positions={}33    events=parser.parse(text)3435    def location(event):36        """复制事件位置,不让结果引用整份输入文本。参数: 事件。返回: 标量位置。异常: 无。"""37        return ConfigurationLocation(event.start_mark.line+1,event.start_mark.column+1)3839    def reject(event,path,code,message):40        """抛定位错误。参数: 事件/路径/代码/原因。返回: 无。异常: ConfigurationError。"""41        raise ConfigurationError(source,path,location(event),code,message)4243    def check(event,path):44        """在构造前拒绝别名、锚点和显式标签。返回: None。异常: 定位配置错误。"""45        if isinstance(event,AliasEvent):reject(event,path,'yaml_alias','不支持YAML别名;请直接写出配置内容')46        if getattr(event,'anchor',None) is not None:reject(event,path,'yaml_anchor','不支持YAML锚点')47        if getattr(event,'tag',None) is not None:reject(event,path,'yaml_tag','不支持显式标签;字符串请加引号')4849    def scalar(event,path):50        """仅转换JSON原生标量;日期时间保文本。返回: 原生值。异常: 不支持类型或非有限数。"""51        check(event,path)52        tag=str(parser.resolver.resolve(ScalarNode,event.value,event.implicit))53        if tag in ('tag:yaml.org,2002:str','tag:yaml.org,2002:timestamp',54                   'tag:yaml.org,2002:merge','tag:yaml.org,2002:value'):55            # <<只在未加引号的映射键位置有合并含义;普通值仍是原始文本。56            return event.value57        if tag not in {'tag:yaml.org,2002:null','tag:yaml.org,2002:bool',58                       'tag:yaml.org,2002:int','tag:yaml.org,2002:float'}:59            reject(event,path,'yaml_scalar','仅支持文本、布尔、有限数字和null')60        # 报文ID只接受约定的三种进制;不能让YAML的0o或下划线数字先转换后绕过检查。61        if path and path[-1] == 'frame_id' and tag == 'tag:yaml.org,2002:int':62            from ..legacy.records import normalize_frame_id63            try:64                return normalize_frame_id(event.value)65            except (TypeError, ValueError) as error:66                reject(event, path, 'invalid_frame_id', str(error))67        # 直接调用安全标量构造,不创建带缓存的YAML节点树或任意Python对象。68        try:69            value=parser.constructor.yaml_constructors[tag](parser.constructor,ScalarNode(tag,event.value))70        except (ValueError,OverflowError) as error:71            raise ConfigurationError(source,path,location(event),'invalid_number',72                '数字超出当前Python可解析的范围') from error73        if type(value) is float and not isfinite(value):74            reject(event,path,'nonfinite_number','数值必须有限,不能使用NaN或Infinity')75        return value7677    def value(event,path=(),depth=0):78        """递归消费一次事件流并保位置。返回: 原生值。异常: 受限语法或嵌套超限。"""79        check(event,path)80        if depth>MAX_DEPTH:reject(event,path,'nesting_limit','配置嵌套不能超过64层')81        positions[path]=location(event)82        if isinstance(event,ScalarEvent):return scalar(event,path)83        if isinstance(event,SequenceStartEvent):84            result=[]85            for child in events:86                if isinstance(child,SequenceEndEvent):return result87                result.append(value(child,(*path,len(result)),depth+1))88        elif isinstance(event,MappingStartEvent):89            result={}90            for key_event in events:91                if isinstance(key_event,MappingEndEvent):return result92                check(key_event,path)93                if not isinstance(key_event,ScalarEvent):reject(key_event,path,'non_string_key','配置对象的键必须为字符串')94                if key_event.value=='<<' and key_event.style is None:95                    reject(key_event,path,'yaml_merge','不支持YAML合并键')96                key=scalar(key_event,path)97                if type(key) is not str:reject(key_event,path,'non_string_key','配置对象的键必须为字符串;数字键请加引号')98                child_path=(*path,key)99                if key in result:reject(key_event,child_path,'duplicate_key',f'重复键:{key}')100                result[key]=value(next(events),child_path,depth+1)101        reject(event,path,'invalid_yaml','配置值结构不完整')102103    try:104        if not isinstance(next(events),StreamStartEvent):raise AssertionError('YAML解析器缺少流起点')105        start=next(events)106        if isinstance(start,StreamEndEvent):return None,{():ConfigurationLocation(1,1)}107        if not isinstance(start,DocumentStartEvent):reject(start,(),'invalid_yaml','需要一个YAML文档')108        if start.version not in (None,(1,2)):reject(start,(),'yaml_version','只支持YAML 1.2')109        if start.tags:reject(start,(),'yaml_tag','不支持标签指令')110        data=value(next(events))111        end=next(events)112        if not isinstance(end,DocumentEndEvent):reject(end,(),'invalid_yaml','文档结束位置不正确')113        end=next(events)114        if not isinstance(end,StreamEndEvent):reject(end,(),'multiple_documents','一个配置文件只能包含一个YAML文档')115        return data,positions116    except YAMLError as error:117        mark=getattr(error,'problem_mark',None)118        place=None if mark is None else ConfigurationLocation(mark.line+1,mark.column+1)119        offset=getattr(error,'position',None)120        if place is None and type(offset) is int:121            place=ConfigurationLocation(text.count('\n',0,offset)+1,offset-text.rfind('\n',0,offset))122        message=getattr(error,'problem',None) or getattr(error,'reason',None) or 'YAML语法无效'123        raise ConfigurationError(source,(),place,'invalid_yaml',message) from error124    finally:125        events.close()126
Chronaxis 用户手册 · 离线阅读