"""A tree representation of a linear markdown-it token stream.This module is not part of upstream JavaScript markdown-it."""importtextwrapfromtypingimport(Generator,NamedTuple,Sequence,Tuple,Dict,List,Optional,Any,TypeVar,overload,Union,)from.tokenimportTokenfrom.utilsimport_removesuffixclass_NesterTokens(NamedTuple):opening:Tokenclosing:Token_NodeType=TypeVar("_NodeType",bound="SyntaxTreeNode")
[文档]classSyntaxTreeNode:"""A Markdown syntax tree node. A class that can be used to construct a tree representation of a linear `markdown-it-py` token stream. Each node in the tree represents either: - root of the Markdown document - a single unnested `Token` - a `Token` "_open" and "_close" token pair, and the tokens nested in between """def__init__(self,tokens:Sequence[Token]=(),*,create_root:bool=True)->None:"""Initialize a `SyntaxTreeNode` from a token stream. If `create_root` is True, create a root node for the document. """# Only nodes representing an unnested token have self.tokenself.token:Optional[Token]=None# Only containers have nester tokensself.nester_tokens:Optional[_NesterTokens]=None# Root node does not have self.parentself._parent:Any=None# Empty list unless a non-empty container, or unnested token that has# children (i.e. inline or img)self._children:list=[]ifcreate_root:self._set_children_from_tokens(tokens)returnifnottokens:raiseValueError("Can only create root from empty token sequence."" Set `create_root=True`.")eliflen(tokens)==1:inline_token=tokens[0]ifinline_token.nesting:raiseValueError("Unequal nesting level at the start and end of token stream.")self.token=inline_tokenifinline_token.children:self._set_children_from_tokens(inline_token.children)else:self.nester_tokens=_NesterTokens(tokens[0],tokens[-1])self._set_children_from_tokens(tokens[1:-1])def__repr__(self)->str:returnf"{type(self).__name__}({self.type})"@overloaddef__getitem__(self:_NodeType,item:int)->_NodeType:...@overloaddef__getitem__(self:_NodeType,item:slice)->List[_NodeType]:...def__getitem__(self:_NodeType,item:Union[int,slice])->Union[_NodeType,List[_NodeType]]:returnself.children[item]
[文档]defto_tokens(self:_NodeType)->List[Token]:"""Recover the linear token stream."""defrecursive_collect_tokens(node:_NodeType,token_list:List[Token])->None:ifnode.type=="root":forchildinnode.children:recursive_collect_tokens(child,token_list)elifnode.token:token_list.append(node.token)else:assertnode.nester_tokenstoken_list.append(node.nester_tokens.opening)forchildinnode.children:recursive_collect_tokens(child,token_list)token_list.append(node.nester_tokens.closing)tokens:List[Token]=[]recursive_collect_tokens(self,tokens)returntokens
@propertydefchildren(self:_NodeType)->List[_NodeType]:returnself._children@children.setterdefchildren(self:_NodeType,value:List[_NodeType])->None:self._children=value@propertydefparent(self:_NodeType)->Optional[_NodeType]:returnself._parent@parent.setterdefparent(self:_NodeType,value:Optional[_NodeType])->None:self._parent=value@propertydefis_root(self)->bool:"""Is the node a special root node?"""returnnot(self.tokenorself.nester_tokens)@propertydefis_nested(self)->bool:"""Is this node nested?. Returns `True` if the node represents a `Token` pair and tokens in the sequence between them, where `Token.nesting` of the first `Token` in the pair is 1 and nesting of the other `Token` is -1. """returnbool(self.nester_tokens)@propertydefsiblings(self:_NodeType)->Sequence[_NodeType]:"""Get siblings of the node. Gets the whole group of siblings, including self. """ifnotself.parent:return[self]returnself.parent.children@propertydeftype(self)->str:"""Get a string type of the represented syntax. - "root" for root nodes - `Token.type` if the node represents an unnested token - `Token.type` of the opening token, with "_open" suffix stripped, if the node represents a nester token pair """ifself.is_root:return"root"ifself.token:returnself.token.typeassertself.nester_tokensreturn_removesuffix(self.nester_tokens.opening.type,"_open")@propertydefnext_sibling(self:_NodeType)->Optional[_NodeType]:"""Get the next node in the sequence of siblings. Returns `None` if this is the last sibling. """self_index=self.siblings.index(self)ifself_index+1<len(self.siblings):returnself.siblings[self_index+1]returnNone@propertydefprevious_sibling(self:_NodeType)->Optional[_NodeType]:"""Get the previous node in the sequence of siblings. Returns `None` if this is the first sibling. """self_index=self.siblings.index(self)ifself_index-1>=0:returnself.siblings[self_index-1]returnNonedef_add_child(self,tokens:Sequence[Token],)->None:"""Make a child node for `self`."""child=type(self)(tokens,create_root=False)child.parent=selfself.children.append(child)def_set_children_from_tokens(self,tokens:Sequence[Token])->None:"""Convert the token stream to a tree structure and set the resulting nodes as children of `self`."""reversed_tokens=list(reversed(tokens))whilereversed_tokens:token=reversed_tokens.pop()ifnottoken.nesting:self._add_child([token])continueiftoken.nesting!=1:raiseValueError("Invalid token nesting")nested_tokens=[token]nesting=1whilereversed_tokensandnesting:token=reversed_tokens.pop()nested_tokens.append(token)nesting+=token.nestingifnesting:raiseValueError(f"unclosed tokens starting {nested_tokens[0]}")self._add_child(nested_tokens)
[文档]defpretty(self,*,indent:int=2,show_text:bool=False,_current:int=0)->str:"""Create an XML style string of the tree."""prefix=" "*_currenttext=prefix+f"<{self.type}"ifnotself.is_rootandself.attrs:text+=" "+" ".join(f"{k}={v!r}"fork,vinself.attrs.items())text+=">"ifshow_textandnotself.is_rootandself.type=="text"andself.content:text+="\n"+textwrap.indent(self.content,prefix+" "*indent)forchildinself.children:text+="\n"+child.pretty(indent=indent,show_text=show_text,_current=_current+indent)returntext
[文档]defwalk(self:_NodeType,*,include_self:bool=True)->Generator[_NodeType,None,None]:"""Recursively yield all descendant nodes in the tree starting at self. The order mimics the order of the underlying linear token stream (i.e. depth first). """ifinclude_self:yieldselfforchildinself.children:yield fromchild.walk(include_self=True)
# NOTE:# The values of the properties defined below directly map to properties# of the underlying `Token`s. A root node does not translate to a `Token`# object, so calling these property getters on a root node will raise an# `AttributeError`.## There is no mapping for `Token.nesting` because the `is_nested` property# provides that data, and can be called on any node type, including root.def_attribute_token(self)->Token:"""Return the `Token` that is used as the data source for the properties defined below."""ifself.token:returnself.tokenifself.nester_tokens:returnself.nester_tokens.openingraiseAttributeError("Root node does not have the accessed attribute")@propertydeftag(self)->str:"""html tag name, e.g. \"p\""""returnself._attribute_token().tag@propertydefattrs(self)->Dict[str,Union[str,int,float]]:"""Html attributes."""returnself._attribute_token().attrs
[文档]defattrGet(self,name:str)->Union[None,str,int,float]:"""Get the value of attribute `name`, or null if it does not exist."""returnself._attribute_token().attrGet(name)
@propertydefmap(self)->Optional[Tuple[int,int]]:"""Source map info. Format: `Tuple[ line_begin, line_end ]`"""map_=self._attribute_token().mapifmap_:# Type ignore because `Token`s attribute types are not perfectreturntuple(map_)# type: ignorereturnNone@propertydeflevel(self)->int:"""nesting level, the same as `state.level`"""returnself._attribute_token().level@propertydefcontent(self)->str:"""In a case of self-closing tag (code, html, fence, etc.), it has contents of this tag."""returnself._attribute_token().content@propertydefmarkup(self)->str:"""'*' or '_' for emphasis, fence string for fence, etc."""returnself._attribute_token().markup@propertydefinfo(self)->str:"""fence infostring"""returnself._attribute_token().info@propertydefmeta(self)->dict:"""A place for plugins to store an arbitrary data."""returnself._attribute_token().meta@propertydefblock(self)->bool:"""True for block-level tokens, false for inline tokens."""returnself._attribute_token().block@propertydefhidden(self)->bool:"""If it's true, ignore this element when rendering. Used for tight lists to hide paragraphs."""returnself._attribute_token().hidden