OpenXml SDK学习笔记(2):新建段落和文本

正好,我今天下午就打算写这个工具类库了。那开发过程就随便写写哈。我个人开发是习惯先想最后如何调用,再按调用编写过程。那么,最后使用,我希望是这样的风格:

 1 using (WordDocument doc = WordDocument.CreateDocument("newDocx.docx", true))
 2 {
 3     doc.AppendParagraph(para =>
 4         {
 5             para.Append("你好,世界")
 6                 .Append("  ")
 7                 .Append("你好,Word");
 8         })
 9         .AppendParagraph()
10             .Append("Hello World")
11             .Append("  ")
12             .Append("Hello World");
13     doc.Save();
14 }

大致可以看出两种风格。上面那种属于ASP.NET CORE里经常用到的Builder模式,下面一种则是经典的流式接口。那么,经过思考,由于这里之后还需要设置格式。所以,流式接口可能更适合一些。因为设置格式的时候,更需要Builder。而一个函数里写两个Builder就非常奇怪。所以,在这里,选择流式接口。所以,在刚才的项目里,新建两个类:WordParagraph和WordRun。这里需要注意,在WordprocessingML里,Run这个结构不一定是隶属于Paragraph的,Paragraph这个结构也不一定是直接隶属于Document的。比如在表格里,用来表示单元格的TableCell里,也会有Paragraph。所以呢,这里需要表示一下整个树型结构,否则以后要扩展起来就非常麻烦。那么,整个WordprocessingML基本所有元素都符合这个特性。而且在原本的代码里,Paragraph和Run都是OpenXmlCompositeElement的一个子类。

所以,新建一个CompositeElementBase,大致代码如下:

 1 using System;
 2 using System.Collections.Generic;
 3 
 4 namespace Ricebird.Wordprocessing
 5 {
 6     public abstract class CompositeElementBase
 7     {
 8         #region ctor
 9         protected CompositeElementBase BaseElement
10         {
11             get;
12             set;
13         }
14 
15         protected WordDocument Document
16         {
17             get;
18             set;
19         }
20 
21         protected List<CompositeElementBase> Children
22         {
23             get; set;
24         } = new List<CompositeElementBase>();
25 
26         public CompositeElementBase(CompositeElementBase @base)
27         {
28             if (@base == null)
29             {
30                 throw new NullReferenceException("@base 不能为 null");
31             }
32             BaseElement = @base;
33             Document = @base.Document;
34         }
35 
36         public CompositeElementBase(WordDocument doc)
37         {
38             Document = doc;
39             BaseElement = null;
40         }
41         #endregion
42 
43         #region 流式接口
44         public abstract CompositeElementBase AppendParagraph();
45         public abstract CompositeElementBase Append(string text);
46         #endregion
47     }
48 }

这里注意,构造函数需要两个。因为一种情况是像顶级段落一样没有上级元素的。他们直接隶属于Document。接着,实现WordParagraph和WordRun。篇幅所限,就只放一个Run。

using DocumentFormat.OpenXml.Wordprocessing;

namespace Ricebird.Wordprocessing
{
    public class WordRun : CompositeElementBase
    {
        private Run InternalRun
        {
            get;set;
        }

        public WordRun(Run r, CompositeElementBase @base) : base(@base)
        {
            InternalRun = r;
        }

        #region 读取文本
        public void SetText(string text)
        {
            Text t = InternalRun.GetFirstChild<Text>();
            if (t == null)
            {
                t = new Text();
                InternalRun.Append(t);
            }
            t.Text = text;
        }

        public string GetText()
        {
            Text t = InternalRun.GetFirstChild<Text>();
            return t?.Text ?? string.Empty;
        }
        #endregion

        #region 流式接口
        public override CompositeElementBase Append(string text)
        {
            return BaseElement.Append(text);
        }

        public override CompositeElementBase AppendParagraph()
        {
            return BaseElement.AppendParagraph();
        }
        #endregion
    }
}

这个都是非常简单的代码,然后运行这个程序。就会发现:

 

 

对比一下我放在下面的代码图,是不是有哪里不对?比如说,空格呢?于是,再去翻阅文档。就可以从Text这个对象的属性里翻出一个叫Space的属性(https://docs.microsoft.com/zh-cn/dotnet/api/documentformat.openxml.wordprocessing.texttype.space)。他说了,不设定这个属性值,那么空格是不会显示的!所以,改动一下SetText函数:

 1 public void SetText(string text)
 2 {
 3     Text t = InternalRun.GetFirstChild<Text>();
 4     if (t == null)
 5     {
 6         t = new Text();
 7         InternalRun.Append(t);
 8     }
 9     t.Text = text;
10 
11     if (text.Contains(" "))
12     {
13         t.Space = new DocumentFormat.OpenXml.EnumValue<DocumentFormat.OpenXml.SpaceProcessingModeValues>(DocumentFormat.OpenXml.SpaceProcessingModeValues.Preserve);
14     }
15 }

对,就是为了设置一个空格可以用,要写辣么辣么长的代码!再运行一下程序。就不截图了哈,整个代码就正常了起来。然后,考虑到在很多时候,文章的一段就是一个连续文本。所以,再添加一个AppendParagraph(string text)函数。代码就不贴了,非常简单,自己加一下就行。然后整理一下流式接口里的调用关系,能用Document的就不要用BaseElement。全部整理完,再看一眼自己的需求,把正式文本填进去:

 

 这图左上是代码的运行图,右上是最后的效果图,下面是代码。对比两边,不能说一模一样只能说完全不像。。。。。。为什么呢?原来是。。。少了格式。那么下一篇就讲如果给文字加上格式

posted @ 2021-11-10 16:15  bluesky234  阅读(508)  评论(0编辑  收藏  举报