工作记录:在线 word - 列表

news2024/12/23 10:07:33

需求:上传 word 文档,在页面的富文本编辑器中展示、编辑后,再导出成 word 格式。

我负责开发列表功能


为什么不用 ul

一开始想用<ul> <li> 去实现列表,但随即发现一些问题:

问题一:word 中的列表项允许跳过层级

在 word 中,可以手动调整列表项的编号级别

在这里插入图片描述
1.1. 的列表项降级一次就变为 1.1.1.

在这里插入图片描述
也可以降级多次:
在这里插入图片描述

问题二:列表项之间可能存在正文

下图四个列表项是属于同一个列表(共享同一套列表设置,延续编号)

在这里插入图片描述

将第三行变为正文。通过编号可以看出,前两行和最后一行仍然属于同一个列表

在这里插入图片描述

会出现这两个问题,是因为 word 的列表并不像 html 中的 <ul> <li> 一样有严格的层级嵌套结构。

如果要强行用 <ul> 。对于问题一,需要补齐跳过的层级。对于问题二,需要把插在列表中的中文也转为列表项。


采用平级标签

列表采用平级标签

<p
  list-id="Numbered_557ff913-3ef4-446f-9a00-5e369471e9cd"
  list-item-level="0"
>
  <span>美国宇航局的科学家们</span>
</p>
<p
  list-id="Numbered_557ff913-3ef4-446f-9a00-5e369471e9cd"
  list-item-level="1"
>
  <span>首次掌握了天王星上有极地气旋的有力证据。</span>
</p>

list-id :所属列表的 id

list-item-level:列表项的层级

计算列表序号

对于 word 内容:

在这里插入图片描述

在页面上变为平级的 p 标签:

<p level="0">美国宇航局的科学家们</p>
<p level="1">首次掌握了天王星上有极地气旋的有力证据。</p>
<p level="1">通过检查从这个冰雪巨人身上发出的无线电波,</p>
<p level="0">他们在这个星球的北极检测到了这种现象。</p>
<p level="5">这些发现证实了关于我们太阳系中所有具有大量大气层的行星的一个广泛的事实: </p>
<p>无论这些行星主要由岩石还是气体组成,</p>
<p level="0">它们的大气层在两极都有漩涡的迹象。</p>
<p level="1">美国宇航局的科学家们利用微波观测发现了天王星上的第一个极地气旋,</p>
<p level="1">在这里看到的是天王星每张图片中中心右侧的一个浅色的点。</p>
<p level="5">这些图像使用的是波长为K、Ka和Q的波段,</p>

为了算出指定列表项的序号,单靠它自身的属性是不够的,必须结合整个文档的内容去计算:

  1. 获取所在列表的所有列表项
  2. 补齐可能缺少的层级
  3. 构造成树
  4. 列表项在树中的位置就是他的序号

转换为不同的序号类型

在 word 中可以设置很多不同类型的序号,比如:甲乙丙丁、一二三四、罗马数字、英文字母26进制序号等

在上一步计算出数字序号后,再转成不同类型的序号。

比如,对于阿拉伯数字序号为 2.3.2 的项,使用 甲乙丙丁 类型就是:乙.丙.乙;使用罗马数字类型就是 II.III.II

应用 formatter

序号还有 formatter 属性。

比如,对于数字序号为 2.3.5 的项,使用 一二三四 类型的序号:

formatter = {0}.{1}.{2} ,对应序号:二.三.五
formatter: 第{0}章第{1}节第{2}段 ,对应序号:第二章第三节第五段
formatter: {1} * {2} ,对应序号:三 * 五


渲染序号

列表项的序号使用 ::before 实现,这样用户就不能随便修改序号的值了。

计算好序号之后,赋给 list-item-prefix 属性:

<p
  list-id="1"
  list-item-level="5"
  list-item-prefix="2.1.1.1.1.1."
>
  <span>这些发现证实了关于我们太阳系中所有具有大量大气层的行星的一个广泛的事实: </span>
</p>

<style>
p[list-id]::before {
    content: attr(list-item-prefix);
}
</style>

但是,由于项目富文本编辑器的限制,计算序号的方法没办法调用。平级标签的方案放弃了。不过计算序号的方法已经写好了,放这里吧:

function getCorrectIndex(htmlString, id) {
  const doc = new DOMParser().parseFromString(htmlString, "text/html");
  const el = doc.querySelector("#" + id);
  // 获取所在列表的所有列表项
  const listId = el.getAttribute("list-id");
  var elements = Array.from(doc.querySelectorAll(`[list-id="${listId}"]`)).map((o) => {
    return {
      el: o,
      level: parseInt(o.getAttribute("list-item-level")),
    };
  });
  // (这里偷了个懒,去掉位置在自己之后的项,因为它们不影响序号。这样el肯定是最后一项,算序号时直接算tree的最后一项就行了)
  elements = elements.filter(function (o) {
    return o.el === el || compareElementOrder(o.el, el);
  });
  // 补齐可能缺少的层级
  patchMissingLevels(elements);
  // 构造成树
  const tree = convertToTree(elements);
  // 列表项在树中的位置就是他的序号
  return calculateLastItemIndex(tree);
}

// 比较两个元素在文档中的位置
function compareElementOrder(element1, element2) {
  var position = element1.compareDocumentPosition(element2);

  if (position & Node.DOCUMENT_POSITION_FOLLOWING) {
    // element1 出现在 element2 之前
    return true;
  } else if (position & Node.DOCUMENT_POSITION_PRECEDING) {
    // element1 出现在 element2 之后
    return false;
  } else {
    // 其他(不在同一个文档中、包含关系、待定、是同一元素)
    return false;
  }
}

// 补齐可能缺少的层级
function patchMissingLevels(arr) {
  for (var i = 0; i < arr.length - 1; i++) {
    var level1 = arr[i].level;
    var level2 = arr[i + 1].level;

    if (level1 + 1 < level2) {
      var newLevel = level1 + 1;
      var newItem = { level: newLevel };
      arr.splice(i + 1, 0, newItem);
    }
  }

  return arr;
}

function convertToTree(arr) {
  var root = { children: [] }; // 根节点
  var stack = [root]; // 用于维护父节点的栈

  for (var i = 0; i < arr.length; i++) {
    var current = arr[i];
    var level = current.level;

    var parent = null;
    while (stack.length > 0) {
      var last = stack[stack.length - 1];
      if (level > last.level) {
        parent = last;
        break;
      }
      stack.pop();
    }

    var newNode = { ...current, children: [] };
    if (parent) {
      parent.children.push(newNode);
    } else {
      root.children.push(newNode);
    }

    stack.push(newNode);
  }

  return root.children;
}

function calculateLastItemIndex(arr) {
  var res = [];
  res.push(arr.length);
  var lastItem = arr[arr.length - 1];
  while (lastItem.children.length > 0) {
    arr = lastItem.children;
    res.push(arr.length);
    lastItem = arr[arr.length - 1];
  }
  return res;
}

调用方法:

const htmlString = `
  <p id="id1" list-id="1" list-item-level="0">美国宇航局的科学家们</p>
  <p id="id2" list-id="1" list-item-level="1">首次掌握了天王星上有极地气旋的有力证据。</p>
  <p id="id3" list-id="1" list-item-level="1">通过检查从这个冰雪巨人身上发出的无线电波,</p>
  <p id="id4" list-id="1" list-item-level="0">他们在这个星球的北极检测到了这种现象。</p>
  <p id="id5" list-id="1" list-item-level="5">这些发现证实了关于我们太阳系中所有具有大量大气层的行星的一个广泛的事实: </p>
  <p id="id6" >无论这些行星主要由岩石还是气体组成,</p>
  <p id="id7" list-id="1" list-item-level="0">它们的大气层在两极都有漩涡的迹象。</p>
  <p id="id8" list-id="1" list-item-level="1">美国宇航局的科学家们利用微波观测发现了天王星上的第一个极地气旋,</p>
  <p id="id9" list-id="1" list-item-level="1">在这里看到的是天王星每张图片中中心右侧的一个浅色的点。</p>
  <p id="id10" list-id="1" list-item-level="5">这些图像使用的是波长为K、Ka和Q的波段,</p>
`;
console.log(getCorrectIndex(htmlString, "id2")); // [1, 1]
console.log(getCorrectIndex(htmlString, "id5")); // [2, 1, 1, 1, 1, 1]
console.log(getCorrectIndex(htmlString, "id8")); // [3, 1]
console.log(getCorrectIndex(htmlString, "id9")); // [3, 2]
console.log(getCorrectIndex(htmlString, "id10")); // [3, 2, 1, 1, 1, 1]

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/568543.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

知乎+chatgpt炸了!撸点小钱~

大家好&#xff0c;我是五竹。 之前分享了一下&#xff0c;朋友圈的好友如何复制我的玩法利用chatgpt赚点小钱的实战&#xff1a;TMD&#xff0c;被人偷窥了一个月&#xff01; 紧接着我自己最近也在知乎小赚了一笔&#xff01;我以为在知乎上那篇文章的热点就消退了&#xff0…

字节25K就面试这些?简直惊呆我了...

互联网行业竞争是一年比一年严峻&#xff0c;作为软件测试工程师的我们唯有不停的学习&#xff0c;不断提升自己才能保证自己的核心竞争力从而拿到更好的薪水&#xff0c;进入心仪的企业&#xff08;阿里&#xff0c;字节跳动&#xff0c;腾讯&#xff0c;美团&#xff09; 话不…

获取安卓签名文件的MD5值与SHA1码

目录 前言 一、用命令行的方式仅能获取SH1、SHA256 ​编辑 二、用gradle的方式可以获取MD5值 1.打开Android Studio&#xff0c;点击右边的Gradle 2.点击大象&#xff0c;输入查询签名信息的命令 总结 前言 公司App项目要备案&#xff0c;要搜集MD5值与SHA1码&#xff0…

Software List

Ubuntu22 Goldendict Ubuntu安装划词翻译软件Goldendict 单词翻译 句子翻译_ubuntu划词翻译_Bourne_Boom的博客-CSDN博客有道词典长期未更新&#xff0c;由于某些模块不支持的问题已经无法在Ubuntu18.04中使用了。现在介绍另一款强大的翻译软件——Goldendict。1.安装&#…

Android Qcom Display学习(十四)

该系列文章总目录链接与各部分简介&#xff1a; Android Qcom Display学习(零) 本章主要就是学习一下开机logo和开机动画是怎么加载&#xff0c;客制化修改的。 Bootlogo QcomChargerApp.c QcomChargerApp_MonitorChargingQcomChargerAppDisplay_DispBattSymbol(DispImageTy…

SARscape连接图编辑(ConnectGraph)

SARscape连接图编辑ConnectGraph 0 连接图是什么1 什么时候需要编辑连接图2 连接图编辑步骤 0 连接图是什么 连接图ConnectGraph就是差分干涉数据对的关系图。 在SARscape中进行干涉叠加Interferometric Stacking处理&#xff0c;常见的包括PS和SBAS。 首先就要根据数据的空间…

美团小组长薪资被应届生员工倒挂7K,不把老员工当人?

一位美团的小管理爆出&#xff0c;无意中看到了整个部门薪资&#xff0c;本以为自己算比较高的&#xff0c;但看完之后整个人都傻眼了。小组长的职位月薪28K&#xff0c;而手下组员却是35K&#xff0c;当天晚上抽了一包烟也没想明白是为什么。 楼主表示&#xff0c;自己是美团的…

git提交分支

1. git提交分支相关 在本地新建分支&#xff0c;保证和远程分支一样 git checkout -b 分支名如果分支已存在&#xff0c;只需要切换的话 git checkout 分支名提交前先把代码拉下来更新一下&#xff0c;确保不会覆盖别人的代码 git pull origin 远程分支(如果有)解决冲突 g…

低代码平台名声臭,用起来却真香——90%重复工作给你完成喽

一、前言 开发过程中&#xff0c;只是觉得前端后端合起来&#xff0c;有很多冗余信息&#xff0c;被代码一遍遍重复表达&#xff0c;是一件很枯燥、无聊的事情。 这些枯燥的重复工作&#xff0c;完全可以由机器来做&#xff0c;以便解放出我们的时间&#xff0c;来做更有价值的…

智慧班牌源码源,SaaS云平台端、智慧校园管理平台端、家长/教师微信移动端、智慧班牌学生端

智慧电子班牌系统包括&#xff1a;SaaS云平台端、智慧校园管理平台端、家长/教师微信移动端、智慧班牌学生端四大软件平台。 文末获取联系&#xff01; 技术架构&#xff1a; 1、使用springboot框架Javavue2 2、数据库MySQL5.7 3、移动端小程序使用小程序原生语言开发 4、电子班…

xlsx是什么格式

xlsx是什么格式? xlsx是Excel文档的扩展名&#xff0c;其基于Office Open XML标准的压缩文件格式&#xff0c;取代了其以前专有的默认文件格式&#xff0c;在传统的文件名扩展名后面添加了字母x&#xff0c;即.xlsx取代.xls。 xlsx文件是什么格式? xlsx是Excel表格的文件格…

【Linux】-- 基础IO和动静态库

一、系统文件IO 1.文件与读写字符串 对于C文件接口&#xff0c;假如想向特定文件写入字符串&#xff1a; cFile.c #include<stdio.h>int main() {FILE * fp fopen("./log.txt","w");if(NULL fp){perror("fopen error");return 1;}int co…

CC1打不通时的另外一条链CC3

在CC1和CC6中&#xff0c;我们最终弹计算器都是通过Runtime.exec进行调用&#xff0c;从CC3我们要介绍一种不通过Runtime来弹计算器的方法&#xff0c;也就是Java中常提到的动态类加载&#xff0c;动态类加载可以让我们通过一个路径来加载一个恶意类&#xff0c;如果这个恶意类…

照片中对象识别模型YOLOv3在iOS项目中的浅析与使用

YOLOv3模型为苹果开发者官网提供的图形识别对象的CoreML模型&#xff0c;可识别80种对象&#xff0c;并给识别出的对象在图形中的位置和大小。 我们可以直接在官网下载该模型&#xff1a; 机器学习 - 模型 - Apple Developer 然后直接将模型拖入工程中&#xff08;使用的是x…

面试进阶齐飞!Github一天万赞的阿里Java系统性能优化有多牛?

前两天在知乎上看到一个问答&#xff0c;说的是&#xff1a; 一个Java程序员具备什么样的素质和能力才可以称得上高级工程师&#xff1f; 这个问题也引发了我的一些思考&#xff0c;可能很多人会说&#xff0c;“作为高级工程师&#xff0c;基础得过硬、得熟练掌握一门编程语…

信创办公–基于WPS的PPT最佳实践系列 (项目11 绘制流程图)

信创办公–基于WPS的PPT最佳实践系列 &#xff08;项目11 绘制流程图&#xff09; 目录 应用背景操作步骤1、流程图制作过程 应用背景 如果在smartart图形当中没有找到你所需要的图形&#xff0c;想要快速绘制&#xff0c;可以试试以下方法。 操作步骤 以图1为例&#xff0c…

AI智慧安防平台EasyCVR无法成功通过RTMP协议接入是什么原因?

EasyCVR视频融合平台基于云边端协同架构&#xff0c;具有强大的数据接入、处理及分发能力。平台可支持多协议、多类型的前端设备接入&#xff0c;包括市场主流标准协议与厂家私有协议及SDK&#xff0c;如&#xff1a;国标GB28181、RTMP、RTSP/Onvif、海康Ehome、海康SDK、宇视S…

AIGC和ChatGPT推进百度、阿里、腾讯、华为大模型技术创新

AIGC | PC集群 | PC Farm | GPU服务器 生成式AI | Stable Diffusion | ChatGPT 2022 年 12 月&#xff0c;OpenAI 推出了 ChatGPT&#xff0c;这是一种高性能计算的大型语言生成模型。它的出现推动了人机对话技术的发展&#xff0c;并在网络上引起了极大的关注。目前&#xff…

K-verse “韩流崛起”合作伙伴介紹

你们可以通过这次 K-verse LAND 销售活动认识许多品牌和 IP。更多详情见下文。 Netmarble Netmarble IP LAND 以全新方式来享受一系列以 Netmarble 的标志性 IP 为特色的内容&#xff01; Netmarble Corporation 于 2000 年在韩国成立&#xff0c;是全球最畅销手机游戏的领先…

【数据结构】顺序二叉树的实现—以堆的实现为例、堆的调整、堆的创建、堆的插入和删除、堆排序

文章目录 1.堆的概念及结构2.堆的实现&#xff08;以大堆为例&#xff09;2.1堆的插入2.1.1堆的向上调整算法 2.2堆的删除2.2.1堆的向下调整算法 2.3堆的创建2.4有关建堆的时间复杂度 3.堆排序4.C语言堆实现源码 1.堆的概念及结构 堆就是顺序结构二叉树。 如果有一个关键码的集…