Python Lark解析非0列退缩进文本遇DedentError求解决
问题分析与解决方法
核心问题
Lark默认的_INDENT/_DEDENT机制是严格层级化的:它会维护一个缩进栈,每次dedent必须退到栈顶记录的上一层级(比如从4空格退到0),不允许退到中间的缩进级别(比如从4退到2),这就是你遇到DedentError的直接原因。
最优解决思路
放弃Lark自带的自动缩进处理,改用手动解析缩进列数的方式,完全掌控缩进层级的判断逻辑:
1. 修改语法规则,禁用自动缩进处理
去掉%declare _INDENT _DEDENT,不再依赖Lark的自动缩进栈,转而在词法阶段捕获每行的缩进列数:
from lark import Lark, Tree grammar = """ start : [_NL] textblock textblock : line+ line : INDENT_LEVEL STRING _NL STRING : LETTER+ INDENT_LEVEL : INT %import common.LETTER %import common.INT %ignore " " _NL: /(\r?\n[\t ]*)+/ """
2. 预处理文本,提取每行缩进列数
在解析前先处理原始文本,把每行的实际缩进空格数转换成INDENT_LEVEL token,供后续解析使用:
def preprocess_indent(text): lines = text.splitlines() processed = [] for line in lines: stripped = line.lstrip() if not stripped: continue # 跳过空行 # 计算当前行的缩进列数(原始长度减去去除左侧空格后的长度) indent = len(line) - len(stripped) processed.append(f"{indent} {stripped}") return "\n".join(processed) # 处理示例文本 raw_text = """ firstline indentline partialdedent """ processed_text = preprocess_indent(raw_text) # 执行解析 parser = Lark(grammar) tree = parser.parse(processed_text)
3. 手动构建层级结构
解析完成后,根据INDENT_LEVEL的值手动构建文本的层级关系,完全支持任意缩进/退缩进逻辑:
def build_hierarchy(tree): stack = [] root = [] # 遍历所有解析出的行 for line_node in tree.children[0].children: indent = int(line_node.children[0].value) content = line_node.children[1].value # 找到当前缩进对应的父节点 while stack and stack[-1][0] >= indent: stack.pop() # 创建当前节点 current_node = {"content": content, "children": []} # 挂载到对应层级 if not stack: root.append(current_node) else: stack[-1][1]["children"].append(current_node) stack.append((indent, current_node)) return root # 生成层级结构 hierarchy = build_hierarchy(tree) print(hierarchy)
4. 适配日志编号场景
针对日志编号变两位数导致的缩进变化(比如1. xxx变成10. xxx,内容起始列左移1位),只需调整预处理逻辑,计算内容实际起始的列数即可:
def preprocess_log_indent(text): lines = text.splitlines() processed = [] for line in lines: stripped = line.lstrip() if not stripped: continue # 提取编号后的内容起始位置(假设日志格式为「编号+空格+内容」) if ' ' in stripped: content_part = stripped.split(' ', 1)[1] content_start = line.find(content_part) else: content_start = len(line) - len(stripped) indent = content_start processed.append(f"{indent} {stripped}") return "\n".join(processed)
内容的提问来源于stack exchange,提问作者thevoiddancer
相关产品推荐
相关产品推荐

