You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.17 19:37:48