一、站点转化为 PDF 文档 (输出目录树)

193 阅读4分钟

背景

文档中心需要将现有 web 内容转化为 PDF 文档 供用户下载。 如:

  1. docs.rongcloud.cn/android-iml…
  2. docs.rongcloud.cn/ios-imlib
  3. docs.rongcloud.cn/harmonyOS-i…

一个模块下有很多页面,需要将这些页面合并到一个PDF文件中输出。

技术方案

输出目录树

  1. 收集目录产出 目录树文件。

处理文档将内容输出到一个HTML文件中

  1. 按目录树收集 HTML 文件。
  2. 按目录树处理 文档内的 H1 - H5 的内容。
  3. 处理 HTML 输出到 output.html中, 删除头尾部分。

将 HTML 转化为 PDF

  1. 将合并后的 HTML 转化为 PDF 文档。
  2. 为 PDF 添加封面
  3. 为 PDF 添加目录
  4. 为 PDF 添加页码

输出目录树

项目是基于 docusaurus.io/ 二次开发的,依据 docusaurus 的文档规则把所有 页面放置到 docs 目录下。

模块目录如下

以 docs 目录为起点,它下面的每个文件夹都是一个模块,但并不是所有文件夹都是模块。

image.png

模块干预

有一个模块配置列表

Filefloder(不要修改这一列)ModuleCatalogVersionReleaseDate
android-callkit客户端 SDK 文档融云音视频 (RTC)Android CallKit 5.x2024-08-07
android-calllib客户端 SDK 文档融云音视频 (RTC)Android CallLIb 5.x2024-07-22
android-callplus客户端 SDK 文档融云音视频 (RTC)Android CallPlus 5.x2024-07-22
android-imkit客户端 SDK 文档融云即时通讯 (IM)Android IMKit 5.x2024-07-22
android-imlib客户端 SDK 文档融云即时通讯 (IM)Android IMLib 5.x2024-07-22

pdfOutline=/opt/com/docs/pdfOutline.md;

entry=$(cat $pdfOutline  | sed '1,2d;' | awk -F '|'  '{print $2}' | xargs)

for e in ${entry[@]} ;do
    echo $e; ## 需求处理的模块
    generateMap ./$e;
done

模块分析 如 android-callkit

结构分析

  1. assets 为静态资源不做为目录结构
  2. md 文件为一个目录《叶子节点》
  3. 文件夹为一个《分支节点》

image.png

内容分析

文件排序

文件 xxxxx.md

---
title: 状态码
sidebar_position: 70
---

# 状态码

## 其他信息
... ...
文件夹

文件 _category_.json

{
  "position": 40,
  "label": "升级指南",
  "collapsible": true
}

对文件进行排序

  1. md文件需要按文件内容sidebar_position 排序
  2. 文件夹也参与sidebar_position排序
  3. 文件夹的排序按 _category_.json 文件中的 position 参与到文件的排序中。

脚本

1. 提取出当前文件夹下 md 文件进行排序

## 按 sidebar_position 生成目录
generateSidebar() {
  find $1 -maxdepth 1 -type f  -exec grep sidebar_position {} + | awk -F: '{print $3 ,$1}';
}

输出内容

 60 ./release-notes.md
 20 ./integration.md
 1 ./prepare.md
 30 ./demo.md
 10 ./init.md
 1 ./import.md
 70 ./code.md
 50 ./api-reference.md

2. 抽取与当前文件夹 的序列号

generateCategorySidebar() {
  find $1 -mindepth 2 -maxdepth 2  -type f -name _category_.json  -exec grep -H -r  position {} +  | sed 's/_category_.json//;s/ //;s/,//' |  awk -F: '{print $3 ,$1}'
}

输出内容

 40 ./upgrade/

3. 将上述内容输入到临时文件中 /tmp/docs/tmp.xxxx.txt

 60 ./release-notes.md
 20 ./integration.md
 1 ./prepare.md
 30 ./demo.md
 10 ./init.md
 1 ./import.md
 70 ./code.md
 50 ./api-reference.md
 40 ./upgrade/

脚本

cat /tmp/docs/tmp.xxxx.txt | sort -n

结果

 1 ./import.md
 1 ./prepare.md
 10 ./init.md
 20 ./integration.md
 30 ./demo.md
 40 ./upgrade/
 50 ./api-reference.md
 60 ./release-notes.md
 70 ./code.md

脚本

cat /tmp/docs/tmp.xxxx.txt | sort -n  | awk '{print $2}'

最终结果

./import.md
./prepare.md
./init.md
./integration.md
./demo.md
./upgrade/
./api-reference.md
./release-notes.md
./code.md

4. 输出为XML目录结构

处理 3. 产出的 文件,如果是一个目录则再次使用 1.2. 过程处理一下。

示例代码

generateMap() {
    generateSidebar $1 >> $tmpfile
    generateCategorySidebar $1 >> $tmpfile
    generateOtherMd $1 >> $tmpfile
    subdirs=$(cat $tmpfile | sort -n | awk '{print $2}')
    for subdir in $subdirs; do
        if [ -d $subdir ]; then
            currentDir=$(echo $subdir | sed 's/\/$//')
            if [ -f $currentDir/_category_.json ]; then
                echo "$2<item title='$dirlabel' index='$3'>"
                generateMap $currentDir "$2  " `expr $3 + 1`
                echo "$2</item>"
            fi
        else
            title=$(grep -o '^title:.*' $subdir | sed 's/title: //;s/"//g' | sed "s/'//g")
            echo "$2<item title='$title' file='$subdir' href='$href' index='$3' />"
        fi
    done
}

getAllDir() {
    for dir in $(find $1 \( -type d -name "_res" -o -name "partials" -o -name "assets" \) -prune -o -maxdepth 1  -type d -print); do
        if [ $dir != "$1" ]; then
            echo "generate $dir-menu.xml start"
            echo "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" > $dir-menu.xml
            echo "<outline>" >> $dir-menu.xml
            generateMap $dir '' 1 >> $dir-menu.xml
            echo "</outline>" >> $dir-menu.xml
            # break;
        fi
    done
}

完整内容

输出结果

<?xml version="1.0" encoding="UTF-8"?>
<outline>
<item title='导入 CallKit SDK' file='./android-callkit/import.md' href='http://docs.rongcloud.cn/android-callkit/import.html' index='1' />
<item title='实时音视频开发指导' file='./android-callkit/prepare.md' href='http://docs.rongcloud.cn/android-callkit/prepare.html' index='1' />
<item title='初始化' file='./android-callkit/init.md' href='http://docs.rongcloud.cn/android-callkit/init.html' index='1' />
<item title='实现音视频通话' file='./android-callkit/integration.md' href='http://docs.rongcloud.cn/android-callkit/integration.html' index='1' />
<item title='运行示例项目(Demo)' file='./android-callkit/demo.md' href='http://docs.rongcloud.cn/android-callkit/demo.html' index='1' />
<item title='升级指南' index='1'>
  <item title='CallKit 3.X 升级到 5.X' file='./android-callkit/upgrade/upgrade-callkit-from-3x.md' href='http://docs.rongcloud.cn/android-callkit/upgrade/upgrade-callkit-from-3x.html' index='2' />
  <item title='CallKit 5.X 升级' file='./android-callkit/upgrade/upgrade-callkit.md' href='http://docs.rongcloud.cn/android-callkit/upgrade/upgrade-callkit.html' index='2' />
</item>
<item title='客户端 API' file='./android-callkit/api-reference.md' href='http://docs.rongcloud.cn/android-callkit/api-reference.html' index='1' />
<item title='版本说明' file='./android-callkit/release-notes.md' href='http://docs.rongcloud.cn/android-callkit/release-notes.html' index='1' />
<item title='状态码' file='./android-callkit/code.md' href='http://docs.rongcloud.cn/android-callkit/code.html' index='1' />
<item title='' file='./android-callkit/test.123' href='http://docs.rongcloud.cn/android-callkit/test.123' index='1' />
</outline>

处理脚本

#!/bin/bash

cd /opt/com.rcloud/cn.rongcloud.docs/docs/;

mkdir -p /opt/com.rcloud/cn.rongcloud.docs/pdf-menu;

## 按 sidebar_position 生成目录
generateSidebar() {
  find $1 -maxdepth 1 -type f  -exec grep sidebar_position {} + | awk -F: '{print $3 ,$1}';
}

## 按 _category_.json 生成目录
generateCategorySidebar() {
  find $1 -mindepth 2 -maxdepth 2  -type f -name _category_.json  -exec grep -H -r  position {} +  | sed 's/_category_.json//;s/ //;s/,//' |  awk -F: '{print $3 ,$1}'
}

## 没有 sidebar_position 的文件
generateOtherMd() {
   find $1 \( -type f -name "_category_.json" \) -prune -o -maxdepth 1  -type f  -exec grep -L sidebar_position {} + | awk -F: '{print 10000, $1}';
}

generateMap() {
    subfile=$(echo $1 | sed 's/^\.\///')
    mkdir -p /tmp/docs/$subfile
    tmpfile=/tmp/docs/$subfile/$(date +%s);
    touch $tmpfile;
    generateSidebar $1 >> $tmpfile
    generateCategorySidebar $1 >> $tmpfile
    generateOtherMd $1 >> $tmpfile
    subdirs=$(cat $tmpfile | sort -n | awk '{print $2}')
    for subdir in $subdirs; do
        if [ -d $subdir ]; then
            # echo "dir  $subdir"
            currentDir=$(echo $subdir | sed 's/\/$//')
            if [ -f $currentDir/_category_.json ]; then
                dirlabel=$(grep '"label":.*' -o $currentDir/_category_.json  | sed 's/"label": "\(.*\)",/\1/;s/"//g' | sed "s/'//g")
                echo "$2<item title='$dirlabel' index='$3'>"
                generateMap $currentDir "$2  " `expr $3 + 1`
                echo "$2</item>"
            fi
        else
            title=$(grep -o '^title:.*' $subdir | sed 's/title: //;s/"//g' | sed "s/'//g")
            href=$( echo $subdir | sed 's/^\./http:\/\/docs.rongcloud.cn/;s/md$/html/;s/mdx$/html/')
            echo "$2<item title='$title' file='$subdir' href='$href' index='$3' />"
        fi
    done
}

getAllDir() {
    for dir in $(find $1 \( -type d -name "_res" -o -name "partials" -o -name "assets" \) -prune -o -maxdepth 1  -type d -print); do
    # for dir in "./sms"; do
        if [ $dir != "$1" ]; then
            echo "generate $dir-menu.xml start"
            echo "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" > $dir-menu.xml
            echo "<outline>" >> $dir-menu.xml
            generateMap $dir '' 1 >> $dir-menu.xml
            echo "</outline>" >> $dir-menu.xml
            # break;
        fi
    done
}

date "+%Y-%m-%d %H:%M:%S"
getAllDir . '  '
date "+%Y-%m-%d %H:%M:%S"



# 遍历源目录中所有以 "-menu.xml" 结尾的文件
for file in ./*-menu.xml; do
    # 获取文件名(不包括路径)
    filename=$(basename "$file")
    # 构建目标文件路径
    dest_file="../pdf-menu/$filename"
    # 检查目标文件是否存在
    if [ ! -e "$dest_file" ]; then
        # 如果目标文件不存在,则移动文件
        mv "$file" "$dest_file"
        echo "Moved: $filename"
    else
        # 如果目标文件已经存在,则跳过并给出提示
        echo "Skipped: $filename (already exists)"
    fi
done

find . -name "*-menu.xml" | xargs rm -fr