一、问题背景
在文档处理场景中,用户踢出需求,需要根据以下规则隐藏或保留段落:
- 标题保留条件:仅当标题的子段落中存在有权限的填报项时,该标题才被保留。
- 段落类型区分
- 正文/表格:根据权限决定是否保留。
- 标题:需结合子段落的保留状态判断是否保留。
- 层级嵌套复杂性:标题的层级(如一级标题、二级标题)需通过段落层级信息(
OutLevel
)动态计算其子段落范围。
存在的典型问题
- 标题范围计算错误:未正确记录标题的结束位置,导致误判子段落范围。
- 保留条件不精准:未区分“有权限填报项”与其他保留类型(如正文),导致标题被错误保留。
二、解决思路
1. 分阶段处理逻辑
将问题拆解为三个阶段,逐步解决:
- 基础保留标记:标记每个段落的保留状态(
retained
数组)。
- 标题范围与保留判断:通过层级嵌套关系确定标题的子段落范围,并判断是否保留。
- 结果生成:根据保留标记生成最终的删除/保留指令。
2. 关键逻辑设计
(1) 标题范围计算
- 使用栈结构:通过栈记录当前层级的标题,动态维护标题的嵌套关系。
- 层级比较:当遇到新标题时,若其层级≤栈顶标题层级,则弹出栈顶标题并记录其结束位置。
- 结束位置处理:遍历结束后,栈中剩余标题的结束位置设为文档末尾。
(2) 保留条件细化
- 段落保留规则
- 正文/表格:仅保留有权限的填报项。
- 标题:仅保留子段落中存在有权限填报项的标题。
- 权限判断:通过
FillInfo
对象获取段落的填报项权限信息。
(3) 标题保留判断
- 遍历子段落:对每个标题的子段落范围(
start
到end
),检查是否存在有权限的填报项。
- 标记保留状态:若满足条件,将标题的
titleShouldKeep
标记为true
。
三、关键代码解析
1. 基础保留标记(retained
数组)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| for (int i = 0; i < size; i++) { Para p = paras.get(i); int paraOutLevel = XxxUtil.getParaOutLevel(p, styles); FillInfo fillItemPermission = getFillItemPermission(fillItemList, p.getPos()); boolean hasPermission = fillItemPermission.isHasPermission(); FillItem fillItem = fillItemPermission.getFillItem(); if (!hasPermission && fillItem != null) { retained[i] = false; } else if (paraOutLevel == 0) { retained[i] = hasPermission; } else { if (levelsToKeep.contains(paraOutLevel)) { retained[i] = true; } } }
|
关键点:
- 权限优先:无权限的填报项直接标记为不保留。
- 层级过滤:仅保留需要显示的层级(如
levelsToKeep
中的层级)。
2. 标题范围计算(栈结构)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| List<Range> titleRanges = new ArrayList<>(); Stack<Range> stack = new Stack<>(); for (int i = 0; i < size; i++) { Para p = allPapx.get(i); int paraOutLevel = DocDataUtil.getParaOutLevel(p, styles); if (paraOutLevel > 0 && levelsToKeep.contains(paraOutLevel)) { while (!stack.isEmpty() && paraOutLevel <= stack.peek().getLevel()) { Range popped = stack.pop(); popped.setEnd(i - 1); titleRanges.add(popped); } Range newRange = new Range(); newRange.setStart(i); newRange.setLevel(paraOutLevel); stack.push(newRange); } }
while (!stack.isEmpty()) { Range r = stack.pop(); r.setEnd(size - 1); titleRanges.add(r); }
|
关键点:
- 栈的层级判断:通过层级比较确保标题范围的正确嵌套。
- 结束位置修正:遍历结束后,确保所有标题范围到文档末尾。
3. 标题保留判断
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| for (Range r : titleRanges) { boolean hasPermissionFilledForm = false; for (int j = r.getStart() + 1; j <= r.getEnd(); j++) { FillInfo fillItemPermission = getFillItemPermission(fillItemList, allPapx.get(j).getPos()); if (retained[j] && fillItemPermission.isHasPermission()) { hasPermissionFilledForm = true; break; } } if (hasPermissionFilledForm) { titleShouldKeep[r.getStart()] = true; } }
|
关键点:
- 子段落遍历:检查标题的子段落(
start+1
到end
)中是否存在有权限的填报项。
- 权限双重验证:仅当
retained[j]
为true
(被基础保留)且hasPermission
为true
(有权限)时,才视为有效。
4. 结果生成
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| List<ResultItem> result = new ArrayList<>(); int i = 0; while (i < size) { Para p = paras.get(i); int paraOutLevel = XxxUtil.getParaOutLevel(p, styles); if (paraOutLevel > 0 && levelsToKeep.contains(paraOutLevel)) { retained[i] = titleShouldKeep[i]; } if () { } else { if (!retained[i]) { result.add(new ResultItem(p.getPos(), p.getLen(), 0)); } i++; } }
|
关键点:
- 标题保留状态更新:根据
titleShouldKeep
覆盖标题的retained
标记。
- 统一处理:通过
retained
数组统一判断段落的保留或删除。
四、验证与测试
1. 测试案例
以用户提供的案例为例:
1 2 3 4 5 6 7 8
| 段落索引 | 内容 | 层级 | 是否保留 ----------------------------------------------- 0 | 一级1 | 1 | false(无子段落有权限填报项) 1 | 二级1 | 2 | false ... | ... | ... | ... 18 | 一级2 | 1 | true(子段落包含有权限填报项) 21 | 二级10 | 2 | true(子段落包含有权限填报项) 25 | 有权限填报项 | 0 | true
|
验证逻辑:
- 一级2的保留:子段落(索引19-25)包含索引25的有权限填报项 → 保留。
- 二级10的保留:子段落包含索引25的有权限填报项 → 保留。
- 一级1的不保留:子段落中无有权限的填报项 → 不保留。
2. 边界条件测试
- 无子段落的标题:若标题无子段落,不保留。
- 层级嵌套交叉:如
一级→二级→三级
的复杂结构,确保范围计算正确。
五、总结与扩展
1. 解决方案优势
- 精准控制:通过层级栈和权限双重判断,确保标题保留的条件严格符合需求。
- 可扩展性:通过
levelsToKeep
和权限接口,可灵活适配不同业务场景。
2. 可优化方向
- 性能优化:对大规模文档,可优化子段落遍历逻辑(如预处理子段落列表)。
- 异常处理:增加对层级不合法或权限信息缺失的容错机制。