前言
最近公司要做新版的帮助中心文档展示(开发文档、用户帮助文档),叫我来调研技术方案。要以最低成本最快时间完成。由于公司给用户的帮助文档之前都维护在语雀中,考虑到迁移成本和时间成本,优先考虑接入语雀Api完成需求。但是发现语雀只支持返回html和markdown源码,没有文档大纲的数据。所以需要我们的后端开发同学来提取大纲的数据,在github上找到了两三个相关项目代码,但都是以python实现的,代码很乱,也没有耐心去阅读实现思路。在此记录一下实现过程。
正文
功能实现大致流程如下:
运营人员在语雀编写文档->从语雀获取文档目录和html信息->解析大纲数据->存储至文件系统
以下为语雀官方Api文档。
Doc - 文档 · 语雀获取一个仓库的文档列表GET /repos/:name...https://www.yuque.com/yuque/developer/doc
HTML大纲提取测试地址HTML 5 Outlinerhttps://gsnedders.html5.org/outliner/
使用到的包:
<!-- https://mvnrepository.com/artifact/org.jsoup/jsoup -->
<!-- jsoup是Java爬虫使用解析html的工具包。-->
<dependency>
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
<version>1.15.3</version>
</dependency>
<!-- 解析yaml文档或文本的工具包,语雀返回的知识库目录格式为yaml文档 -->
<dependency>
<groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId>
<version>1.26</version>
</dependency>
提取HTML目录大纲代码,使用递归,时间复杂度为O(n-1)2
/**
* @desc 获取html大纲目录
* @author Liangwz
* @date 2022/9/2 14:20
* @param html html body代码
* @return java.util.List<DocLinkDto>
*/
private List<DocLinkDto> getLinkNodes(String html) {
if (StringUtils.isBlank(html)) {
return new ArrayList<>();
}
//解析html
Document document = Jsoup.parseBodyFragment(html);
Elements allResult = new Elements();
//获取所有H标签集合
for (int i = 1; i < 7; i++) {
allResult.addAll(document.getElementsByTag("h"+i));
}
//按照标签位置先后排序
allResult = allResult.stream().sorted(Comparator.comparing(Node::siblingIndex)).collect(Collectors.toCollection(Elements::new));
1 //提取大纲信息返回
return getLinkNodes(allResult);
}
/**
* @desc 获取html大纲目录
* @author Liangwz
* @date 2022/9/2 14:20
* @param allTag h标签集合
* @return java.util.List<DocLinkDto>
*/
private static List<DocLinkDto> getLinkNodes(Elements allTag) {
List<DocLinkDto> result = new ArrayList<>();
for (int i = 0; i < allTag.size(); i++) {
Element elementI = allTag.get(i);
Integer hTagNumI = getHTagNum(elementI.tagName());
//获取子区间集合
Elements childrenElements = new Elements();
for (int j = i + 1; j < allTag.size(); j++) {
Element elementJ = allTag.get(j);
if (getHTagNum(allTag.get(j).tagName()) <= hTagNumI) {
break;
}
i = j;
childrenElements.add(elementJ);
}
DocLinkDto linkDto = new DocLinkDto(elementI.text(), "#" + elementI.id(), elementI.tagName(), new ArrayList<>());
if (childrenElements.isEmpty()) {
result.add(linkDto);
continue;
} else {
//递归获取子节点
linkDto.setChildren(getLinkNodes(childrenElements));
}
result.add(linkDto);
}
return result;
}
/**
* @desc 获取html h标签级别
* @author Liangwz
* @date 2022/9/2 14:19
* @param tagName
* @return java.lang.Integer
*/
private static Integer getHTagNum(String tagName) {
return Integer.valueOf(tagName.replace("h", ""));
}
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.util.List;
/**
* 类 名 称:LinkDto
* 类 描 述:文档大纲节点信息
* 创建时间:2022/9/1 19:26
* 创 建 人:liangwz
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class DocLinkDto implements Serializable {
//标题
private String title;
//路径
private String href;
//类型(h1,h2,h3...)
private String type;
//子节点
private List<DocLinkDto> children;
}