minidoc项目

minidocx 是一个跨平台且易于使用的 C++ 库,用于从零开始创建 Microsoft Word 文档。因为DOCX本质上是一个包含多个XML文件的ZIP包。可以使用特定的库和工具来完成这种转换,从而生成符合要求的docx文档。因此这个项目当中就是采用pugixml来进行XML的处理。

minidoc项目中创建的docx文档是minidocx项目中提供的Document类的实例,而Document类的内部属性信息存储在impl_指针指向的内存中,这种设计方式通过一个指向实现细节的指针来隐藏类的实现,从而减少类接口与实现之间的耦合。其指向的其实际上是一个结构体,结构如下,可以看出Document类的内部数据是通过xml数据格式来进行存储,所以这里实际上是通过pugixml来进行XML数据的处理。

  struct Document::Impl
  {
    pugi::xml_document doc_;  // 定义了一个pugi::xml_document类型的成员变量doc_,用于存储整个文档的XML数据,包含了文档的所有节点、属性等。
    pugi::xml_node     w_body_;  //  定义了一个pugi::xml_node类型的成员变量w_body_,用于存储文档中w:body节点,这是文档的主要内容部分,包含了所有的段落、表格等内容。
    pugi::xml_node     w_sectPr_;  //  定义了一个pugi::xml_node类型的成员变量w_sectPr_,用于存储文档中w:sectPr节点,这是文档段落或部分的属性节点,包含了节属性信息。

    pugi::xml_document settings_;  //  定义了一个pugi::xml_document类型的成员变量settings_,用于存储文档的设置XML数据。
    pugi::xml_node     w_settings_;  //  定义了一个pugi::xml_node类型的成员变量w_settings_,用于存储文档设置中的w:settings节点。

    unsigned int nextBookmarkId_;  //  定义了一个无符号整数类型的成员变量nextBookmarkId_,用于存储下一个书签ID的值。
    std::vector<Bookmark> bookmarks_;  //  定义了一个std::vector类型的成员变量bookmarks_,用于存储文档中的书签集合。
  };

pugixml以类似于DOM的方式存储XML数据:整个XML文档(文档结构和元素数据)都以树的形式存储在内存中。树的根是文档本身,它对应于C ++ type xml_document。文档具有一个或多个子节点,它们对应于C ++ type xml_node。节点具有不同的类型;根据类型,节点可以具有子节点的集合,与C ++类型相对应的属性的集合xml_attribute以及一些其他数据(即名称)。

在项目当中通过Document doc;创建doc对象,doc对象的构造函数如下,主要是对impl_指针指向的数据内容进行初始化,

  Document::Document()
  {
    impl_ = new Impl;
    impl_->doc_.load_buffer(DOCUMENT_XML, std::strlen(DOCUMENT_XML), pugi::parse_declaration); // 这行代码将DOCUMENT_XML字符串加载到impl_对象的doc_成员变量中,doc_是一个pugi::xml_document对象。pugi::parse_declaration表示在解析时包含XML声明。
    impl_->w_body_ = impl_->doc_.child("w:document").child("w:body"); // 这行代码从解析的XML文档中找到名为w:document的根节点,然后找到它的子节点w:body并将其赋值给impl_对象的w_body_成员变量。
    impl_->w_sectPr_ = impl_->w_body_.child("w:sectPr"); // 这行代码从w:body节点中找到名为w:sectPr的子节点并将其赋值给impl_对象的w_sectPr_成员变量。
    impl_->settings_.load_buffer(SETTINGS_XML, std::strlen(SETTINGS_XML), pugi::parse_declaration);  // 这行代码将SETTINGS_XML字符串加载到impl_对象的settings_成员变量中,settings_是一个pugi::xml_document对象。
    impl_->w_settings_ = impl_->settings_.child("w:settings"); // 这行代码从解析的settings_文档中找到名为w:settings的根节点,并将其赋值给impl_对象的w_settings_成员变量。
    impl_->nextBookmarkId_ = 0; // 这行代码初始化impl_对象的nextBookmarkId_成员变量,并将其设置为0
  }

mpl_->doc_.load_buffer(DOCUMENT_XML, std::strlen(DOCUMENT_XML), pugi::parse_declaration); 参数中的DOCUMENT_XML记录了一个Word文档的基础结构,包含了文档的页面设置和一些元数据,内容如下:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<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" 
            xmlns:cx2="http://schemas.microsoft.com/office/drawing/2015/10/21/chartex" 
            xmlns:cx3="http://schemas.microsoft.com/office/drawing/2016/5/9/chartex" 
            xmlns:cx4="http://schemas.microsoft.com/office/drawing/2016/5/10/chartex" 
            xmlns:cx5="http://schemas.microsoft.com/office/drawing/2016/5/11/chartex" 
            xmlns:cx6="http://schemas.microsoft.com/office/drawing/2016/5/12/chartex" 
            xmlns:cx7="http://schemas.microsoft.com/office/drawing/2016/5/13/chartex" 
            xmlns:cx8="http://schemas.microsoft.com/office/drawing/2016/5/14/chartex" 
            xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
            xmlns:aink="http://schemas.microsoft.com/office/drawing/2016/ink" 
            xmlns:am3d="http://schemas.microsoft.com/office/drawing/2017/model3d" 
            xmlns:o="urn:schemas-microsoft-com:office:office" 
            xmlns:oel="http://schemas.microsoft.com/office/2019/extlst" 
            xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" 
            xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math" 
            xmlns:v="urn:schemas-microsoft-com:vml" 
            xmlns:wp14="http://schemas.microsoft.com/office/word/2010/wordprocessingDrawing" 
            xmlns:wp="http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing" 
            xmlns:w10="urn:schemas-microsoft-com:office:word" 
            xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main" 
            xmlns:w14="http://schemas.microsoft.com/office/word/2010/wordml" 
            xmlns:w15="http://schemas.microsoft.com/office/word/2012/wordml" 
            xmlns:w16cex="http://schemas.microsoft.com/office/word/2018/wordml/cex" 
            xmlns:w16cid="http://schemas.microsoft.com/office/word/2016/wordml/cid" 
            xmlns:w16="http://schemas.microsoft.com/office/word/2018/wordml" 
            xmlns:w16sdtdh="http://schemas.microsoft.com/office/word/2020/wordml/sdtdatahash" 
            xmlns:w16se="http://schemas.microsoft.com/office/word/2015/wordml/symex" 
            xmlns:wpg="http://schemas.microsoft.com/office/word/2010/wordprocessingGroup" 
            xmlns:wpi="http://schemas.microsoft.com/office/word/2010/wordprocessingInk" 
            xmlns:wne="http://schemas.microsoft.com/office/word/2006/wordml" 
            xmlns:wps="http://schemas.microsoft.com/office/word/2010/wordprocessingShape" 
            mc:Ignorable="w14 w15 w16se w16cid w16 w16cex w16sdtdh wp14">
    <w:body>
        <w:sectPr>
            <w:pgSz w:w="11906" w:h="16838" />
            <w:pgMar w:top="1440" w:right="1800" w:bottom="1440" w:left="1800" w:header="851" w:footer="992" w:gutter="0" />
            <w:cols w:space="425" />
            <w:docGrid w:type="lines" w:linePitch="312" />
        </w:sectPr>
    </w:body>
</w:document>

注解:

文档主体 <w:body>:
节属性 <w:sectPr>:
页面大小 <w:pgSz>:
页面边距 <w:pgMar>
页面边距 <w:pgMar>
文档网格 <w:docGrid>
xmlnsxmlns是 “XML Namespace” 的缩写,ns 代表 “namespace”。命名空间在 XML 中用于确保元素和属性名称的唯一性,避免冲突,并提供明确的语义。
比如"xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main":是WordprocessingML的命名空间,定义了文档中使用的所有Word相关元素。而其他命名空间(如xmlns:wpcxmlns:cx等)是为了支持特定的Word功能,如绘图、图表、兼容性等。另外由于xml具有独立性,因此每个XML文档都需要声明它所使用的命名空间,即使这些命名空间在多个文档中是重复的。这样做可以确保每个文档都能独立地解析和理解。

因此当对XML解析之后的操作:
1、impl_->w_body_ = impl_->doc_.child("w:document").child("w:body");

这行代码从解析的XML文档中找到名为w:document的根节点,然后找到它的子节点w:body并将其赋值给impl_对象的w_body_成员变量。

2、impl_->w_sectPr_ = impl_->w_body_.child("w:sectPr");

这行代码从w:body节点中找到名为w:sectPr的子节点并将其赋值给impl_对象的w_sectPr_成员变量。也就是impl_->w_sectPr_ 中存储着文档的节属性信息sectPr

impl_->settings_.load_buffer(SETTINGS_XML, std::strlen(SETTINGS_XML), pugi::parse_declaration);这行代码将SETTINGS_XML字符串加载到impl_对象的settings_成员变量中,settings_是一个pugi::xml_document对象。

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<w:settings xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
            xmlns:o="urn:schemas-microsoft-com:office:office"
            xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"
            xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math"
            xmlns:v="urn:schemas-microsoft-com:vml"
            xmlns:w10="urn:schemas-microsoft-com:office:word"
            xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"
            xmlns:w14="http://schemas.microsoft.com/office/word/2010/wordml"
            xmlns:w15="http://schemas.microsoft.com/office/word/2012/wordml"
            xmlns:w16cex="http://schemas.microsoft.com/office/word/2018/wordml/cex"
            xmlns:w16cid="http://schemas.microsoft.com/office/word/2016/wordml/cid"
            xmlns:w16="http://schemas.microsoft.com/office/word/2018/wordml"
            xmlns:w16sdtdh="http://schemas.microsoft.com/office/word/2020/wordml/sdtdatahash"
            xmlns:w16se="http://schemas.microsoft.com/office/word/2015/wordml/symex"
            xmlns:sl="http://schemas.openxmlformats.org/schemaLibrary/2006/main"
            mc:Ignorable="w14 w15 w16se w16cid w16 w16cex w16sdtdh">
</w:settings>

这段XML片段定义了Word文档的设置部分,并声明了许多命名空间,以支持不同版本和扩展的Word功能。这些命名空间确保了文档可以在不同的Word版本中正确解析和显示。

AppendParagraph()接口的细节:

在创建了Document对象后,可以通过auto p1 = doc.AppendParagraph("Hello, World!", 12, "Times New Roman");来向文档添加段落,DocumentAppendParagraph方法内部实现如下:

Paragraph Document::AppendParagraph(const std::string& text,  // 文本
    const double fontSize,  // 字体大小
    const std::string& fontAscii,  //
    const std::string& fontEastAsia)
  {
    Paragraph p = AppendParagraph();
    p.AppendRun(text, fontSize, fontAscii, fontEastAsia);
    return p;
  }

因此文档内部添加段落是通过Document::AppendParagraph()实现的,实现细节:

Paragraph Document::AppendParagraph() // 定义了Document类的成员函数AppendParagraph,返回类型是Paragraph。
  {
    if (!impl_) return Paragraph();  //  检查impl_指针是否为空。如果为空,返回一个默认构造的Paragraph对象,表示无法添加段落。

    pugi::xml_node w_p = impl_->w_body_.insert_child_before("w:p", impl_->w_sectPr_);  // 在impl_->w_body_节点中,在impl_->w_sectPr_节点之前插入一个新的子节点w:p,表示一个段落,并将其赋值给变量w_p。

    pugi::xml_node w_pPr = w_p.append_child("w:pPr"); // 在新创建的段落节点w_p中添加一个子节点w:pPr,表示段落属性,并将其赋值给变量w_pPr

    Paragraph::Impl* impl = new Paragraph::Impl; // 动态分配内存并创建一个新的Paragraph::Impl对象,并将其指针赋值给变量impl
    impl->w_body_ = impl_->w_body_;  //  将Document对象的impl_成员中的w_body_节点指针赋值给新创建的Paragraph::Impl对象的w_body_成员。
    impl->w_p_ = w_p;  // 将新创建的段落节点w_p赋值给新创建的Paragraph::Impl对象的w_p_成员
    impl->w_pPr_ = w_pPr;  // 将新创建的段落属性节点w_pPr赋值给新创建的Paragraph::Impl对象的w_pPr_成员。
    return Paragraph(impl);
  }

解析:

impl_->w_body_当中存储的是

<w:body>
        <w:sectPr>
                <w:pgSz w:w="11906" w:h="16838" />
                <w:pgMar w:top="1440" w:right="1800" w:bottom="1440" w:left="1800" w:header="851" w:footer="992" w:gutter="0" />
                <w:cols w:space="425" />
                <w:docGrid w:type="lines" w:linePitch="312" />
        </w:sectPr>
</w:body>

因此pugi::xml_node w_p = impl_->w_body_.insert_child_before("w:p", impl_->w_sectPr_);是先在w:sectPr节点前插入新的子节点w:p,表示一个段落,并将其赋值给变量w_p。插入新的子节点后,

<w:body>
	<w:p />
	<w:sectPr>
		<w:pgSz w:w="11906" w:h="16838" />
		<w:pgMar w:top="1440" w:right="1800" w:bottom="1440" w:left="1800" w:header="851" w:footer="992" w:gutter="0" />
		<w:cols w:space="425" />
		<w:docGrid w:type="lines" w:linePitch="312" />
	</w:sectPr>
</w:body>

实际就是在<w:sectPr>...</w:sectPr>前添加了<w:p />节点,要注意一下段落对象的impl_指针指向的结构体中存储的内容如下,也就是

struct Paragraph::Impl
  {
    pugi::xml_node w_body_;  //  定义了一个pugi::xml_node类型的成员变量w_body_,用于存储段落所在文档的w:body节点
    pugi::xml_node w_p_;  // 定义了一个pugi::xml_node类型的成员变量w_p_,用于存储当前段落的w:p节点
    pugi::xml_node w_pPr_;  //  定义了一个pugi::xml_node类型的成员变量w_pPr_,用于存储当前段落的属性w:pPr节点
  };

因此新建段落,也就是调用Paragraph Document::AppendParagraph() ,之后,实际上就是创建了一个Paragraph对象,而该对象中的impl_指针指向的就是上诉结构体。

向段落中添加文字:Run Paragraph::AppendRun()接口如下:

Run Paragraph::AppendRun()
  {
    if (!impl_) return Run();  // 检查impl_指针是否为空。如果为空,返回一个默认构造的Run对象,表示无法添加运行节点。
//    impl_->w_p_.print(std::cout);
//    std::cout.flush();
    pugi::xml_node w_r = impl_->w_p_.append_child("w:r");  //  在当前段落节点impl_->w_p_中添加一个子节点w:r,表示一个新的运行节点,并将其赋值给变量w_r。
    pugi::xml_node w_rPr = w_r.append_child("w:rPr");  //  在新创建的运行节点w_r中添加一个子节点w:rPr,表示运行属性,并将其赋值给变量w_rPr。

    Run::Impl* impl = new Run::Impl;
    impl->w_p_ = impl_->w_p_;  //  将当前段落节点impl_->w_p_的指针赋值给新创建的Run::Impl对象的w_p_成员。
    impl->w_r_ = w_r;  // 将新创建的运行节点w_r赋值给新创建的Run::Impl对象的w_r_成员。
    impl->w_rPr_ = w_rPr;  //  将新创建的运行属性节点w_rPr赋值给新创建的Run::Impl对象的w_rPr_成员。
    return Run(impl);
  }

从上面代码可以看出,向段落添加文本主要分成几步:
1、pugi::xml_node w_r = impl_->w_p_.append_child("w:r"); :向impl_->w_p_中添加子节点,这里的imp_Paragraph::imp_,也就是段落的成员变量,impl_->w_p_在添加子节点之前的内容是:

<w:p>
	<w:pPr />
</w:p>

而在执行pugi::xml_node w_r = impl_->w_p_.append_child("w:r"); pugi::xml_node w_rPr = w_r.append_child("w:rPr");之后,impl_->w_p_的内容变为:

<w:p>
	<w:pPr />
	<w:r>
		<w:rPr />
	</w:r>
</w:p>

可以看出其实添加文本实际上也就是新建了一个Run对象,impl->w_p_ = impl_->w_p_; 表示创建的Run::Impl对象的w_p_成员存储着当前段落节点impl_->w_p_,除此之外对象成员还存储着当前运行节点w_r和运行节点属性w_rPr,这也对应着Run::Impl对应的成员变量:

struct Run::Impl
  {
    pugi::xml_node w_p_;
    pugi::xml_node w_r_;
    pugi::xml_node w_rPr_;
  };

设置文本字体接口:void Run::SetFontSize(const double fontSize)

void Run::SetFontSize(const double fontSize)
  {
    if (!impl_) return;
    pugi::xml_node sz = impl_->w_rPr_.child("w:sz");
    if (!sz) {
      sz = impl_->w_rPr_.append_child("w:sz");
    }
    pugi::xml_attribute szVal = sz.attribute("w:val");
    if (!szVal) {
      szVal = sz.append_attribute("w:val");
    }
    // font size in half-points (1/144 of an inch)
    szVal.set_value(fontSize * 2);
  }

设置字体格式接口:void Run::SetFont( const std::string& fontAscii, const std::string& fontEastAsia)

void Run::SetFont(
    const std::string& fontAscii,
    const std::string& fontEastAsia)
  {
    if (!impl_) return;
    pugi::xml_node rFonts = impl_->w_rPr_.child("w:rFonts");
    if (!rFonts) {
      rFonts = impl_->w_rPr_.append_child("w:rFonts");
    }
    pugi::xml_attribute rFontsAscii = rFonts.attribute("w:ascii");
    if (!rFontsAscii) {
      rFontsAscii = rFonts.append_attribute("w:ascii");
    }
    pugi::xml_attribute rFontsEastAsia = rFonts.attribute("w:eastAsia");
    if (!rFontsEastAsia) {
      rFontsEastAsia = rFonts.append_attribute("w:eastAsia");
    }
    rFontsAscii.set_value(fontAscii.c_str());
    rFontsEastAsia.set_value(fontEastAsia.empty()
      ? fontAscii.c_str()
      : fontEastAsia.c_str());
  }```

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值