前言

前置知识:OpenXml

首先描述下问题产生的场景。我们的业务需求是根据用户的在线作答(或导入的作答结果)数据批量产生报告。产生报告的方式是把通过工作流控制的复杂业务逻辑的产出--分析结果--和Word模板进行匹配产生新的word文档。接下来根据word文档按需生成Pdf、flash或者Html等其他文档。图-1是我们的word模板的截图。

图1   word模板截图

图中所示为一个模板的一部分,其中的自己定义标签会被报告生成程序替换为指定的数据,然后生成新的docx文档。不同的报告会由专业的设计人员设计word的排版和样式,其中为了分隔报告的不同部分,会经常使用分页符,如图2所示。

图2  分页符

分页符的作用我这里就不细说了,各位应该比我更熟悉,不论当前页剩余多少空白,分页符后面的内容都会被强制在下一页显示。分页符本身没有什么问题,但是当分页符上面的内容正好满一页 之后,分页符就会被挤压到下一页,那么分页符所在的页就不会有任何文字,形成一个空白页,如图3所示。

图3   由分页符形成的空白页

因为我们事先无法判断要绑定内容的多少,分页符没办法和自定义标签和谐的相处,每部分都可能产生空白页。

表格和有些自定义标签会产生一个换行符,而且无法有效的在制作模板阶段删除,那么如果最后一页的内容正好满一页的话,就会把这个换行符挤压下来,形成一个空白页,如图4所示。

图4  由换行符产生的空白页

在某些特殊的情况下,我们可以这样来处理由分页符产生空白页问题。

图5   让分页符跟在最后一行文字之后

如果让分页符跟在文字之后,可以很好的解决问题,似乎可以手工的解决这个问题,最终我们放弃了手工修改的计划,原因如下:

1)  特殊的自定义标签无法和分页符如图5那样和谐共处;

2)  已经开发好的大量模板都要重新修改,测试人员要重新测试,工作量很大;

3)无法解决由换行符带来的空白页问题

如果能在数据完全替换模板中的标签生成新的 word之后,我们再来在程序中将分页符转移到它上面的文字末尾,然后再删除最后一个换行符是不是就解决问题了呢?

正文

为了研究上诉问题,我们首先建立一个简单的word文档,内容如图6所示。

图6  分页符示例文档

然后我们使用来打开示例文档,如图7所示。

图7   查看文档的内容

打开body,WordML的内容如代码清单1-1.

代码清单1-1
   1:  <w:body xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"> 
   2:    <w:p w:rsidR="00373B8D" w:rsidP="004B265D" w:rsidRDefault="004B265D"> 
   3:      <w:pPr> 
   4:        <w:rPr> 
   5:          <w:rFonts w:hint="eastAsia" /> 
   6:        </w:rPr> 
   7:      </w:pPr> 
   8:      <w:r> 
   9:        <w:rPr> 
  10:          <w:rFonts w:hint="eastAsia" /> 
  11:        </w:rPr> 
  12:        <w:t>你好</w:t> 
  13:      </w:r> 
  14:    </w:p> 
  15:    <w:p w:rsidR="004B265D" w:rsidP="004B265D" w:rsidRDefault="004B265D"> 
  16:      <w:r> 
  17:        <w:rPr> 
  18:          <w:rFonts w:hint="eastAsia" /> 
  19:        </w:rPr> 
  20:        <w:t>我是分页符</w:t> 
  21:      </w:r> 
  22:    </w:p> 
  23:    <w:p w:rsidR="004B265D" w:rsidRDefault="004B265D"> 
  24:      <w:pPr> 
  25:        <w:widowControl /> 
  26:        <w:jc w:val="left" /> 
  27:      </w:pPr> 
  28:      <w:r> 
  29:        <w:br w:type="page" /> 
  30:      </w:r> 
  31:    </w:p> 
  32:    <w:p w:rsidR="004B265D" w:rsidP="004B265D" w:rsidRDefault="004B265D"> 
  33:      <w:pPr> 
  34:        <w:rPr> 
  35:          <w:rFonts w:hint="eastAsia" /> 
  36:        </w:rPr> 
  37:      </w:pPr> 
  38:    </w:p> 
  39:    <w:sectPr w:rsidR="004B265D" w:rsidSect="00E556CD"> 
  40:      <w:footerReference w:type="default" r:id="rId8" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" /> 
  41:      <w:pgSz w:w="11907" w:h="16839" w:code="9" /> 
  42:      <w:pgMar w:top="964" w:right="737" w:bottom="1021" w:left="737" w:header="567" w:footer="442" w:gutter="0" /> 
  43:      <w:pgNumType w:start="1" /> 
  44:      <w:cols w:space="425" /> 
  45:      <w:docGrid w:type="linesAndChars" w:linePitch="312" /> 
  46:    </w:sectPr> 
  47:  </w:body>

“我是分页符”对应的WordML为:

   1:    <w:p w:rsidR="004B265D" w:rsidP="004B265D" w:rsidRDefault="004B265D"> 
   2:      <w:r> 
   3:        <w:rPr> 
   4:          <w:rFonts w:hint="eastAsia" /> 
   5:        </w:rPr> 
   6:        <w:t>我是分页符</w:t> 
   7:      </w:r> 
   8:    </w:p> 

而分页符对应的WordML为  :

   1:   
   2:   
   3:    <w:p w:rsidR="004B265D" w:rsidRDefault="004B265D"> 
   4:      <w:pPr> 
   5:        <w:widowControl /> 
   6:        <w:jc w:val="left" /> 
   7:      </w:pPr> 
   8:      <w:r> 
   9:        <w:br w:type="page" /> 
  10:      </w:r> 
  11:    </w:p> 
  12:   

   1:  那么我们如果想把分页符放到它上一行的文字的后面,只需把上面两段代码合并为:
   2:   
   3:    <w:p w:rsidR="004B265D" w:rsidP="004B265D" w:rsidRDefault="004B265D"> 
   4:      <w:r> 
   5:        <w:rPr> 
   6:          <w:rFonts w:hint="eastAsia" /> 
   7:        </w:rPr> 
   8:        <w:t>我是分页符</w:t> 
   9:      </w:r>
  10:   
  11:  <w:r> 
  12:        <w:br w:type="page" /> 
  13:      </w:r> 
  14:    </w:p>
  15:   

那么如何实现上面的“乾坤大挪移”呢?我们先看代码再分析。

   1:  using System;
   2:  using System.Collections.Generic;
   3:  using System.Linq;
   4:  using System.Text;
   5:  using DocumentFormat.OpenXml.Packaging;
   6:  using System.IO;
   7:  using DocumentFormat.OpenXml.Wordprocessing;
   8:   
   9:  namespace FilterBlankPage
  10:  {
  11:      public class WordBlankPageFilter
  12:      {
  13:          public static void Filter(Stream word)
  14:          {
  15:             
  16:              using (WordprocessingDocument wordDocument = WordprocessingDocument.Open(word, true))
  17:              {
  18:                  Body body = wordDocument.MainDocumentPart.Document.Body;
  20:                  var breaks = body.Descendants<Break>();
  21:                  List<Paragraph> ps = new List<Paragraph>();
  22:                  foreach (var br in breaks)
  23:                  {
  24:                      if (br.Type == null || br.Type != BreakValues.Page)
  25:                          continue;
  26:                      else if (br.Parent != null && br.Parent.Parent != null)
  27:                      {
  28:                          Paragraph paragraph = br.Parent.Parent as Paragraph;
  29:                          if (paragraph.Descendants<Text>().Count() == 0&&paragraph.Descendants<Drawing>().Count()==0)
  30:                          {
  31:                              var toAppend = paragraph.ElementsBefore().Last();
  32:                              if (toAppend.GetType().Name == "Table")
  33:                              {
  34:   
  35:                                  Table t = toAppend as Table;
  36:   
  37:                                  TableProperties oldProperty = t.GetFirstChild<TableProperties>();
  38:                                  //TableProperties newProperty = oldProperty.CloneNode(true);
  39:                                  TablePositionProperties oldPositionProp = oldProperty.GetFirstChild<TablePositionProperties>();
  40:                                  if (oldPositionProp == null)//设置文字环绕
  41:                                  {
  42:                                      TablePositionProperties tablePositionProperties1 = new TablePositionProperties()
  43:                                      {
  44:                                          LeftFromText = 180,
  45:                                          RightFromText = 180,
  46:                                          VerticalAnchor = VerticalAnchorValues.Text,
  47:                                          //TablePositionXAlignment = HorizontalAlignmentValues.Center,
  48:                                          TablePositionY = 1
  49:                                      };
  50:                                      oldProperty.Append(tablePositionProperties1);
  51:                                  }
  52:                                  TableWidth oldWidth = oldProperty.GetFirstChild<TableWidth>();
  53:                                  if (oldWidth == null)
  54:                                  {
  55:                                      TableWidth tableWidth1 = new TableWidth() { Width = "9180", Type = TableWidthUnitValues.Dxa };
  56:                                      oldProperty.Append(tableWidth1);
  57:                                  }
  58:                                  else
  59:                                  {
  60:                                      if (oldWidth.Type == TableWidthUnitValues.Dxa)
  61:                                      {
  62:                                          oldWidth.Width = int.Parse(oldWidth.Width.Value) > 9180 ? "9180" : oldWidth.Width.Value;
  63:                                      }
  64:                                      else if (oldWidth.Type == TableWidthUnitValues.Auto)
  65:                                      {
  66:                                          oldWidth.Type = TableWidthUnitValues.Dxa;
  67:                                          oldWidth.Width = "9180";
  68:                                      }
  69:                                  }
  70:                              }
  71:                              else
  72:                              {
  73:                                  Run r = new Run();
  74:                                  Break b = new Break();
  75:                                  b.Type = BreakValues.Page;
  76:                                  r.Append(b);
  77:                                  toAppend.Append(r);
  78:                                  ps.Add(paragraph);
  79:                              }
  80:                          }
  81:                      }
  82:                  }
  83:                  ps.ForEach(t => t.Remove());
  84:                  //尝试去除最后一个回车符
  85:                  var lastP = body.Elements<Paragraph>().Last();
  86:                  if (lastP.Descendants<Text>().Count() == 0)
  87:                  {
  88:                      lastP.Remove();
  89:                  }
  90:              }
  91:          }
  92:      }
  93:  }

第16行代码中我们从Word文档的流数据中获取WordprocessingDocument对象,然后在第18行代码获取主文档的Body对象,一个Word文档的主要内容都会在Body中找到。在第20行代码,取得Body中所有的break标签,当break标签的type为Page时,该标签就是分页符。由于每个分页符肯定存在一个段落(p,paragraph)标签中,所以我们在循环中寻找分页符时同时将该分页符所在的P标签存储在一个列表中,最后集中删除,同时寻找p标签的上一个P标签,并在上一个p标签中添加分页符,一删一加达到“移动”的效果。如果分页符所在的段落中含有文字或者图片,我们无需删除该段落,第29行代码做了这样的判断。73行到78行代码是向p标签中添加分页符的代码。当分页符和其他内容在同一个P内的时候,会紧跟在该内容的后面而不会单独成行而造成空白页,如图-8.

图-8  转移分页符后的效果

第32行代码判断分页符所在的段落的上一个标签是不是Tabel,由于表格和P是同级标签,同时表格内不能插入分页符,所以我们要使用特殊的方式,使分页符不在表格的下方单独成段。这个特殊的手段也很简单就是设置合适的表格宽度和环绕。如图-9所示。

图-9  设置表格的环绕使分页符在表格的一侧

那么在代码中如何设置表格的环绕和宽度呢?代码如下:

   1:   
   2:                                  Table t = toAppend as Table;
   3:   
   4:                                  TableProperties oldProperty = t.GetFirstChild<TableProperties>();
   5:                                  //TableProperties newProperty = oldProperty.CloneNode(true);
   6:                                  TablePositionProperties oldPositionProp = oldProperty.GetFirstChild<TablePositionProperties>();
   7:                                  if (oldPositionProp == null)//设置文字环绕
   8:                                  {
   9:                                      TablePositionProperties tablePositionProperties1 = new TablePositionProperties()
  10:                                      {
  11:                                          LeftFromText = 180,
  12:                                          RightFromText = 180,
  13:                                          VerticalAnchor = VerticalAnchorValues.Text,
  14:                                          //TablePositionXAlignment = HorizontalAlignmentValues.Center,
  15:                                          TablePositionY = 1
  16:                                      };
  17:                                      oldProperty.Append(tablePositionProperties1);
  18:                                  }
  19:                                  TableWidth oldWidth = oldProperty.GetFirstChild<TableWidth>();
  20:                                  if (oldWidth == null)
  21:                                  {
  22:                                      TableWidth tableWidth1 = new TableWidth() { Width = "9180", Type = TableWidthUnitValues.Dxa };
  23:                                      oldProperty.Append(tableWidth1);
  24:                                  }
  25:                                  else
  26:                                  {
  27:                                      if (oldWidth.Type == TableWidthUnitValues.Dxa)
  28:                                      {
  29:                                          oldWidth.Width = int.Parse(oldWidth.Width.Value) > 9180 ? "9180" : oldWidth.Width.Value;
  30:                                      }
  31:                                      else if (oldWidth.Type == TableWidthUnitValues.Auto)
  32:                                      {
  33:                                          oldWidth.Type = TableWidthUnitValues.Dxa;
  34:                                          oldWidth.Width = "9180";
  35:                                      }
  36:                                  }

第四行代码获取的是表格属性对象,在该属性中可以设置表格宽(通过 TableWidth对象)高等属性。TablePositionProperties是表格的定位属性,设置该属性就是设置环绕。定位的属性设置各位可以参考word,如图-10所示。

图-10  表格定位属性

单单设置环绕,如果表格过宽而没有留足够的空间给分页符环绕上去也达不到环绕的效果,可以通过第22行代码所示的方式设置表格的宽度。

最后一个问题是由最后一个换行符带来的空白页 ,可以通过如下的代码获取最后一个换行符并删除掉:

   1:  var lastP = body.Elements<Paragraph>().Last();
   2:                  if (lastP.Descendants<Text>().Count() == 0)
   3:                  {
   4:                      lastP.Remove();
   5:                  }

还有很多灵活的运用方式,大家可以留言讨论。

本文转自悬魂博客园博客,原文链接:http://www.cnblogs.com/xuanhun/archive/2011/05/31/2065024.html,如需转载请自行联系原作者

OpenXml编程--去除自动生成的word文档中由分页符和换行符产生的空白页相关推荐

  1. rmd转换html怎么换页,如何在由RStudiomarkdown生成的单词文档中添加分页符

    您尝试做的是强制在使用Pandoc生成的单词文档中的"分页符"或"新页面".我已经找到了在我的环境中做到这一点的方法,但我不确定它会在每个环境中工作. 我的环境 ...

  2. 如何快速删除 Word 文档中的分页符

    概要:我们可以在 Word 文档当中插入非常多的符号,比如说换行符.分页符以及分节符等等.这些不同的符号在我的文档当中的表现及功能也不一样.有时候可能这些分页符我们来说是多余的,所以我们就要想办法去删 ...

  3. 如何修改word文档中每行字符的最大默认值和每页最大行数默认值

    事情起因是这样的,小明在写论文的过程中,发现自己的文档的字与字的间距看起来比其他人的字符间距大,于是觉得奇怪,明明设置了一样的格式啊,设置每行38个字符,每页34行,为什么小明写的文档字符间距看着比较 ...

  4. Java实现数据自动填充到WORD文档并下载

    模拟业务场景:假设有一个OA系统,员工在里面填了请假申请,填好后可以通过下载自动生成一个word文档的请假条.比如数据表有姓名,请假原因,时间等字段,将这些字段填充到一个word模板中. 1. 准备w ...

  5. 用Aspose.Words for .NET动态生成word文档中的图片或水印

    1.概述 在项目中生成word文档,这个功能很普遍的,一般生成都是纯文字或是列表的比较多,便于客户打印,而要把图片也生成到word文档中的需求有些客户也是需要的,例如产品图片.这次我们介绍的是如何利用 ...

  6. Word处理控件Aspose.Words功能演示:从 Java 中的 Word 文档中提取图像

    图像通常用于表示 Word 文档中的重要信息.在文本旁边包含图像使内容更具吸引力.在某些情况下,您可能需要以编程方式提取嵌入在 Word 文档中的图像.为此,本文介绍了如何使用 Java 从 Word ...

  7. Word处理控件Aspose.Words功能演示:使用 Python 查找和替换 Word 文档中的文本

    很多时候,您需要替换 Word 文档中的特定文本或短语.MS Word 具有针对此类情况的内置功能,您可以一键替换所需的文本.在本文中,您将学习如何使用 Python 以编程方式查找和替换 Word ...

  8. 怎样在Word文档中插入空白页

    在编辑Word文档时,如果想在光标插入处插入空白页,该怎样操作呢?下面用我常用的speedoffice分享一下我的经验. 方法1 1,打开要编辑的word文档,将光标移到需要插入空白页的地方.点击工具 ...

  9. 在word文档中如何自动生成目录,两种方法制作目录,总有一种适合你

    在word文档中如何自动生成目录,两种方法制作目录,总有一种适合你 目录 在word文档中如何自动生成目录,两种方法制作目录,总有一种适合你 1.文章中的标题较多,每个单独调整格式太费劲,这里我们用一 ...

最新文章

  1. python嵌套循环执行顺序_两个嵌套for循环的执行顺序
  2. linux 安装 tao环境,linux环境安装hbase------不一定需要hadoop
  3. oracle sequrnce_Oracle Sqlldr简单用法
  4. 我的世界一个程序导致JAVA,Java地位无可动摇的12个原因
  5. python re模块compile_Python re模块的match方法
  6. 设置搜狗浏览器为默认浏览器时被360拦截怎么办?
  7. Python正则表达式子模式扩展语法与应用
  8. 同样可以建站,云服务器和虚拟主机的区别在哪?
  9. 知识蒸馏方法的演进历史综述
  10. 学习《华为基本法》(8):人力资源管理准则
  11. 秋招 | 携程 | 携程集团2022秋招内推正式启动啦~!
  12. mysql删除密码代码_mysql 用户新建、受权、删除。密码修改
  13. 给移动互联网创业公司的六条建议
  14. 分子动力学模拟自由能计算gmx_mmpbsa脚本原理和使用
  15. 银河麒麟linux找不到网卡,中标麒麟Linux v7系统下设置双网卡bond或team绑定详细过程-网卡设置...
  16. 一眼就吸引人的网名「引人注目」
  17. 使用Mailgun Store():应用程序传入电子邮件的临时邮箱
  18. Redis高并发点赞
  19. app分享到微信的方案
  20. 手机号与邮箱正则表达式

热门文章

  1. dell服务器网卡em1改成eth0
  2. 指尖触碰样书,梦想照进现实「博客出书的故事③」
  3. Windows Server 2003 R2 修复Windows Server 2003
  4. 计算机维护方面的知识和技巧,电脑硬件维护常识和方法【图文详解】
  5. 一个NSObject对象占多少内存?
  6. 抓包概念大比较:数据报、数据包、分组
  7. 计算机应用基础形考报告2020,放大学计算机应用基础形考本学习报告
  8. linux中流设备_Linux设备驱动子系统终极弹
  9. win10系统打开更新服务器失败怎么回事,Win10系统一直无法安装更新怎么办 Win10更新一直安装失败的3种解决方法...
  10. 用python画玫瑰花简单-利用python的turtle库画一朵简单的玫瑰花,并添加文字