office open xml序列化为tsl
Go to file
csh 16452c8362 improvement: 单位转换的调整 2025-08-06 18:16:15 +08:00
autounit improvement: 单位转换的调整 2025-08-06 18:16:15 +08:00
docx v2.1.1 大量更新,功能性更新见readme 2025-05-30 14:44:32 +08:00
openxml feat: 支持clone方法 2025-08-06 18:14:09 +08:00
pptx v2.1.1 大量更新,功能性更新见readme 2025-05-30 14:44:32 +08:00
utils improvement: 单位转换的调整 2025-08-06 18:16:15 +08:00
.gitignore Initial commit 2024-06-25 15:56:31 +08:00
README.md update README 2025-08-06 18:14:29 +08:00
cp.ps1 v2.1.1 大量更新,功能性更新见readme 2025-05-30 14:44:32 +08:00
迁移指南.md v2.0.0 2025-04-16 10:03:34 +08:00

README.md

OfficeXml

迁移指南

概述

将 docx、pptx、xlsx 等文件中的 xml 转为 tsl 对象

<w:p w14:paraId="6E3ED3BE" w14:textId="77777777" w:rsidR="00C57A1E"
    w:rsidRDefault="00C57A1E" w:rsidP="00C27AE9">
    <w:pPr>
        <w:jc w:val="left" />
    </w:pPr>
    <w:r>
        <w:rPr>
            <w:rFonts w:hint="eastAsia" />
        </w:rPr>
        <w:t>最小申购、赎回单位</w:t>
    </w:r>
    <w:r>
        <w:rPr>
            <w:rFonts w:hint="eastAsia" />
        </w:rPr>
        <w:t>(份)</w:t>
    </w:r>
</w:p>

上述是一个 docx 中的段落的 xml序列化为 tsl 过程如下

uses DocxML;      // 上述xml属于DocxML
p := new P();     // 创建一个P对象段落w:p
p.Init(node);     // 假设node节点是上面的xml指向的Node对象
p.Deserialize();  // 将node对象的xml序列化到tsl对象

// 序列化完毕后,可直接对应取值
echo p.PPr.Jc.Val;      // 输出left

// 如果需要修改内容通过Serialize()回写到node
p.PPr.Jc.Val := "center";
// 回写方法1适合修改单个对象
p.PPr.Jc.Serialize();
// 回写方法2大量修改不同的对象
p.Serialize(); // 或p.PPr.Serialize()

// 在获取存在多个节点的对象时比如上述的w:r对象是复数的则需要通过Rs()获取
// 直接调用Rs()会获取所有的R对象加上索引会获取第N+1个
echo p.Rs(1).T.Text;    // 输出:(份)

基类

OpenXmlAttribute是节点的属性类

OpenXmlElement是节点基类

OpenXmlSimpleType是简单类型的元素节点类,继承于 OpenXmlElement,如 <b />

OpenXmlTextElement是包含文本内容的元素节点类,继承于 OpenXmlElement,如 <t>文本</t>

OpenXmlCompositeElement是复杂节点的元素节点类,继承于 OpenXmlElement,会包含一系列的属性或其他元素节点类

基本功能

获取属性/子节点

获取同名但命名空间不同的属性/子节点内容时,遵守以下规范

  1. 默认情况下,获取的命名空间前缀与父节点一致
  2. 当父节点有前缀,但是属性/子节点没有前缀时候,通过 ("")空字符串参数获取
  3. 存在同名属性/子节点情况下,默认获取的一定是和父节点前缀相同的内容
<m:r w:val="testw" val="testN" m:val="testm">
    <m:rPr>
        <m:sty m:val="p" />
    </m:rPr>
    <a:rPr lang="en-US"
        altLang="zh-CN" sz="1100"
        i="0" kern="1200">
        <a:latin
            typeface="Cambria Math"
            panose="02040503050406030204"
            pitchFamily="18"
            charset="0" />
    </a:rPr>
    <m:t>cos</m:t>
</m:r>
r.Val; // 默认获取前缀为m即m:val的属性
r.Val("m"); // 指定前缀m
r.Val("");  // 无前缀的属性即val
r.Val("w"); // 获取前缀为w即w:val的属性

r.RPr; // 默认获取前缀为m即m:rPr的子节点
r.RPr("m"); // 指定前缀为m
r.RPr("a"); // 获取前缀为a即a:rPr的子节点

删除属性/节点

移除方式一通过RemoveAttribute或RemoveChild
// 移除r的属性w:val
// 因为r.Val("w")是直接获取了属性值所以不能作为参数传递给RemoveAttribute
r.RemoveAttribute(r.Attribute("w:val"));

// 移除r的子节点rPr
// 因为r.RPr就是一个对象所以可直接使用
r.RemoveChild(r.RPr);

移除方式二通过赋值nil
// 这种方式不需要区分属性和子节点,也不需要知道属性的全称
r.Val("w") := nil;
r.RPr := nil;

最后需要回写才会生效
r.Serialize();

回落功能(Fallback)

项目的 fallback功能是指获取某个属性或节点不存在时候,会检查是否设置了 fallback,如果设置了会通过 fallback获取对应的属性或节点

// pPr1
<w:pPr>
    <w:jc w:val="left" />
</w:pPr>

// pPr2
<w:pPr>
    <w:jc w:val="left" />
    <w:wordWrap w:val="1" />
</w:pPr>
// 假设已经获取到了对象ppr1和ppr2
ppr1.SetFallback(ppr2); // 将ppr1的fallback设置为ppr2
ppr1.Jc.Val; // 得到"left"
ppr1.WordWrap.Val; // ppr1不存在wordWrap但是ppr2存在wordWrap所以回落到ppr2的wordWrap获取到"1"

Copy

copy方法支持将其他的类属性和子节点的属性复制过来,注意:复数的类无法进行复制,因为不确定怎么进行复制

// pPr1
<w:pPr>
    <w:jc w:val="left" />
    <w:spacing w:after="160" />
</w:pPr>

// pPr2
<w:pPr>
    <w:jc w:val="right" />
    <w:wordWrap w:val="1" />
</w:pPr>
// 假设要将ppr2的属性复制到ppr1
ppr1.Copy(ppr2);
// 复制完毕后ppr1 xml如下
// 因为ppr2没有w:spacing所以保留ppr1的w:spacing
<w:pPr>
    <w:jc w:val="right" />
    <w:wordWrap w:val="1" />
    <w:spacing w:after="160" />
</w:pPr>
// 这样是不能复制的w:r是复数
<w:p>
    <w:r w:rsidR="00CC4888">
        <w:t>test</w:t>
    </w:r>
    <w:r w:rsidR="00CD1015">
        <w:rPr>
            <w:rFonts w:hint="eastAsia"/>
        </w:rPr>
        <w:t></w:t>
    </w:r>
</w:p>

获取r并不是p.r进行获取而是p.Rs();

Clone

Clone方法是克隆出一个一样的对象出来,但是Parentnil

<w:p>
    <w:pPr>
        <w:jc w:val="right" />
        <w:wordWrap w:val="1" />
        <w:spacing w:after="160" />
    </w:pPr>
    <w:r w:rsidR="00CC4888">
        <w:t>test</w:t>
    </w:r>
    <w:r w:rsidR="00CD1015">
        <w:rPr>
            <w:rFonts w:hint="eastAsia"/>
        </w:rPr>
        <w:t></w:t>
    </w:r>
</w:p>
new_p := p.Clone(); // 与p的xml一致
echo new_p.Parent;  // nil
document.InsertAfter(new_p, p); // 插入一样的段落在p之后此时会自动设置new_p.Parent := document

Unit 单元

  • DocxML包含 docx文件独有的 xml 节点对象,一般 xml 的命名空间是 w,如 w:p
  • PptxML包含 pptx文件独有的 xml 节点对象,一般 xml 的命名空间是 p,如 p:spPr
  • XlsxML包含 xlsx文件独有的 xml 节点对象
  • DrawingML包含 docx,pptx,xlsx文件图形的 xml 节点对象,一般 xml 的命名空间是 a,如 a:xfrm
  • SharedML包含 docx,pptx,xlsx文件共有的 xml 节点对象
  • VML

参考链接 1

参考链接 2

部件

Components

一共有三个部件,分别是:

  1. DocxComponents.tsfdocx 文件的各部分 xml 内容
  2. XlsxComponentsxlsx 文件的各部分 xml 内容
  3. PptxComponentspptx 文件的各部分 xml 内容

DocxComponents.tsf为例,使用这个类,可以获取到对应的 docx 文件的 xml 对象

component := new DocxComponents(); // 创建对象
component.Open("", "xxx.docx");    // 打开文件
document := component.Document;    // 获取document.xml生成Document对象
document.Deserialize();            // 将xml对象的数据反序列化到tsl对象中
document.Body.Elements();          // 可以获取document的body下的所有对象列表
// 反序列化后,可进行读写

styles := component.Styles;     // 获取styles.xml生成Styles对象

document.xml 内容如下

<w:document xmlns:wpc="http://schemas.microsoft.com/office/word/2010/wordprocessingCanvas"
    xmlns:cx="http://schemas.microsoft.com/office/drawing/2014/chartex"
    xmlns:cx1="http://schemas.microsoft.com/office/drawing/2015/9/8/chartex"
    mc:Ignorable="w14 w15 w16se w16cid w16 w16cex w16sdtdh w16du wp14">
    <w:body>
        <w:p w14:paraId="7B19BB62" w14:textId="33E8D5E1" w:rsidR="00B118EF"
            w:rsidRDefault="00B118EF" w:rsidP="00B118EF">
            <w:pPr>
                <w:tabs>
                    <w:tab w:val="left" w:pos="5670" />
                </w:tabs>
            </w:pPr>
            <w:r>
                <w:rPr>
                    <w:rFonts w:hint="eastAsia" />
                </w:rPr>
                <w:t>分栏前</w:t>
            </w:r>
        </w:p>
        <w:sectPr w:rsidR="00B118EF" w:rsidSect="002E4343">
            <w:type w:val="continuous" />
            <w:pgSz w:w="11906" w:h="16838" w:code="9" />
            <w:pgMar w:top="1440" w:right="1797" w:bottom="1440" w:left="1797" w:header="851"
                w:footer="992" w:gutter="0" />
            <w:cols w:space="720" />
            <w:docGrid w:type="linesAndChars" w:linePitch="312" />
        </w:sectPr>
    </w:body>
</w:document>

单位装饰器 UnitDecorator

每个对象都有一个单位装饰器,能统一转成磅(point)单位(如果有配置属性转换),还能保留原来的接口

每个 ML都有装饰器 tsf统一命名是 Unit的名称+UnitDecorator,如 docxdocx大部分 xml隶属于 DocxML)的 SectPr对象的装饰器是 SectPrUnitDecorator

如:有下面一段 xml其中的 pgSz.w = "11906", pgSz.h = "16838"都需要转换成 point

<w:sectPr w:rsidR="00B118EF" w:rsidSect="002E4343">
    <w:type w:val="continuous" />
    <w:pgSz w:w="11906" w:h="16838" w:code="9" />
    <w:pgMar w:top="1440" w:right="1797" w:bottom="1440" w:left="1797" w:header="851"
        w:footer="992" w:gutter="0" />
    <w:cols w:space="720" />
    <w:docGrid w:type="linesAndChars" w:linePitch="312" />
</w:sectPr>
uses DocxMLUnitDecorator;
component := new DocxComponents();  // 创建对象
component.Open("", "xxx.docx"); // 打开文件
document := component.Document; // 获取document.xml生成Document对象
document.Deserialize();         // 将xml对象的数据反序列化到tsl对象中

sect_pr := document.Body.SectPr; // 获取SectPr对象
sect_pr_unit_decorator := new SectPrUnitDecorator(sect_pr); // 装饰器构造需要原本的对象
echo "w = ", sect_pr.PgSz.W;    // 输出的是字符串原本的单位是twips
echo "\n";
echo "w = ", sect_pr_unit_decorator.PgSz.W;     // 此时输出的是数字类型单位是point

适配器 Adapter

适配器是通过 key 获取对应的对象,比如样式可以通过样式 ID 获取对应的样式对象

只有部分对象才有适配器(具体可见 autounit/xxxMLAdapter),比如 DocxML.Styles的适配器是 StylesAdapter

styles.xml 部分如下

<w:styles xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006">
    <w:style w:type="character" w:customStyle="1" w:styleId="a4">
        <w:name w:val="页眉 字符" />
        <w:basedOn w:val="a0" />
        <w:link w:val="a3" />
        <w:uiPriority w:val="99" />
        <w:rsid w:val="00B118EF" />
        <w:rPr>
            <w:sz w:val="18" />
            <w:szCs w:val="18" />
        </w:rPr>
    </w:style>
    <w:style w:type="character" w:customStyle="1" w:styleId="a6">
        <w:name w:val="页脚 字符" />
        <w:basedOn w:val="a0" />
        <w:link w:val="a5" />
        <w:uiPriority w:val="99" />
        <w:rsid w:val="00B118EF" />
        <w:rPr>
            <w:sz w:val="18" />
            <w:szCs w:val="18" />
        </w:rPr>
    </w:style>
</w:styles>
uses DocxMLAdapter;
component := new DocxComponents();  // 创建对象
component.Open("", "xxx.docx"); // 打开文件
document := component.Document; // 获取document.xml生成Document对象
document.Deserialize();         // 将xml对象的数据反序列化到tsl对象中

styles := document.Styles;
// 现在需要通过styleId获取Style对象
styles_adapter := new StylesAdapter(styles);
// 通过StyleId获取Style对象
style := styles_adapter.GetStyleByStyleId("a6");
echo style.Name;    // 输出的是"页脚 字符"