.NET平台下不借助Office实现Word、Powerpoint等文件的解析(完)
.NET平台下不借助Office实现Word、Powerpoint等文件的解析(完)
【题外话】
这是这个系列的最后一篇文章了,为了不让自己觉得少点什么,顺便让自己感觉完美一些,就再把OOXML说一下吧。不过说实话,OOXML真的太容易解析了,而且这方面的文档包括成熟的开源类库也特别特别特别的多,所以我就稍微说一下,文章中引用了不少的链接,感兴趣的话可以深入了解下。
【系列索引】
Office文件的奥秘——.NET平台下不借助Office实现Word、Powerpoint等文件的解析(一)获取Office二进制文档的DocumentSummaryInformation以及SummaryInformation Office文件的奥秘——.NET平台下不借助Office实现Word、Powerpoint等文件的解析(二)
获取Word二进制文档(.doc)的文字内容(包括正文、页眉、页脚、批注等等) Office文件的奥秘——.NET平台下不借助Office实现Word、Powerpoint等文件的解析(三)
详细介绍Office二进制文档中的存储结构,以及获取PowerPoint二进制文档(.ppt)的文字内容 Office文件的奥秘——.NET平台下不借助Office实现Word、Powerpoint等文件的解析(完)
介绍Office Open XML文档(.docx、.pptx)如何进行解析以及解析Office文件常见开源类库
【文章索引】
初见Office Open XML(OOXML) OOXML文档属性的解析 Word 2007文件的解析 PowerPoint 2007文件的解析 常见Office文档(Word、PowerPoint、Excel)文件的开源类库 相关链接【一、初见Office Open XML(OOXML)】
先来看一段微软官方对Office Open XML的说明(详细见 http://office.microsoft.com/zh-cn/support/HA010205815.aspx?CTT=3 ):
可以看到,与Windows 复合文档不同的是,OOXML生来就是开放的,而且由于基于zip+xml的格式,使得读取变得更容易,如果仅是为了抽取文字,我们甚至不需要读取文档的任何参数!
如果您之前不了解OOXML的话,我们可以把手头docx、pptx以及xlsx文件的扩展名改为zip,然后用压缩软件打开看看。
打开的这三个文件分别是docx、pptx和xlsx,我们可以看到,目录结构清晰可见,所以我们只需要使用读取zip的类库读取zip文件,然后再解析xml文件即可。对于使用.NET Framework 3.0及以上的,可以直接使用.NET自带的Package类(System.IO.Packaging,在WindowsBase.dll中)进行解压,个人感觉如果只是读取zip流中的文件流或内容,WindowsBase中的Package还是很好用的。如果用于.NET CF或者2.0甚至以下的CLR可以使用SharpZipLib(支持CLR 1.1、2.0、4.0,官方网站 http://www.icsharpcode.net/ ),也可以使用DotNetZip(支持CLR 2.0,官方网站 http://dotnetzip.codeplex.com/ ),个人感觉后者的License更友好些。
比如我们使用自带的Package打开OOXML文件:
View Code
1 #region 字段 2 protected FileStream m_stream; 3 protected Package m_package; 4 #endregion 5 6 #region 构造函数 7 /// <summary> 8 /// 初始化OfficeOpenXMLFile 9 /// </summary> 10 /// <param name="filePath"> 文件路径 </param> 11 public OfficeOpenXMLFile(String filePath) 12 { 13 try 14 { 15 this .m_stream = new FileStream(filePath, FileMode.Open, FileAccess.Read); 16 this .m_package = Package.Open( this .m_stream); 17 18 this .ReadProperties(); 19 this .ReadCoreProperties(); 20 this .ReadContent(); 21 } 22 finally 23 { 24 if ( this .m_package != null ) 25 { 26 this .m_package.Close(); 27 } 28 29 if ( this .m_stream != null ) 30 { 31 this .m_stream.Close(); 32 } 33 } 34 } 35 #endregion
【二、OOXML文档属性的解析】
OOXML文件的文档属性其实存在于docProps目录下,比较重要的有三个文件
app.xml:记录文档的属性,内容类似之前的DocumentSummaryInformation。 core.xml:记录文档核心的属性,比如创建时间、最后修改时间等等,内容类似之前的SummaryInformation。 thumbnail.*:文档的缩略图,不同文件存储的是不同的格式,比如Word为emf,Excel为wmf,PowerPoint为jpeg。我们只需要遍历XML文件中所有的子节点就可以读出所有的属性,为了好看,这里还用的Windows复合文件中的名称:
View Code
1 #region 常量 2 private const String PropertiesNameSpace = " http://schemas.openxmlformats.org/officeDocument/2006/extended-properties " ; 3 private const String CorePropertiesNameSpace = " http://schemas.openxmlformats.org/package/2006/metadata/core-properties " ; 4 #endregion 5 6 #region 字段 7 protected Dictionary<String, String> m_properties; 8 protected Dictionary<String, String> m_coreProperties; 9 #endregion 10 11 #region 属性 12 /// <summary> 13 /// 获取DocumentSummaryInformation 14 /// </summary> 15 public override Dictionary<String, String> DocumentSummaryInformation 16 { 17 get 18 { 19 return this .m_properties; 20 } 21 } 22 23 /// <summary> 24 /// 获取SummaryInformation 25 /// </summary> 26 public override Dictionary<String, String> SummaryInformation 27 { 28 get 29 { 30 return this .m_coreProperties; 31 } 32 } 33 #endregion 34 35 #region 读取Properties 36 private void ReadProperties() 37 { 38 if ( this .m_package == null ) 39 { 40 return ; 41 } 42 43 PackagePart part = this .m_package.GetPart( new Uri( " /docProps/app.xml " , UriKind.Relative)); 44 if (part == null ) 45 { 46 return ; 47 } 48 49 XmlDocument doc = new XmlDocument(); 50 doc.Load(part.GetStream()); 51 52 XmlNodeList nodes = doc.GetElementsByTagName( " Properties " , PropertiesNameSpace); 53 if (nodes.Count < 1 ) 54 { 55 return ; 56 } 57 58 this .m_properties = new Dictionary<String, String> (); 59 foreach (XmlElement element in nodes[ 0 ]) 60 { 61 this .m_properties.Add(element.LocalName, element.InnerText); 62 } 63 } 64 #endregion 65 66 #region 读取CoreProperties 67 private void ReadCoreProperties() 68 { 69 if ( this .m_package == null ) 70 { 71 return ; 72 } 73 74 PackagePart part = this .m_package.GetPart( new Uri( " /docProps/core.xml " , UriKind.Relative)); 75 if (part == null ) 76 { 77 return ; 78 } 79 80 XmlDocument doc = new XmlDocument(); 81 doc.Load(part.GetStream()); 82 83 XmlNodeList nodes = doc.GetElementsByTagName( " coreProperties " , CorePropertiesNameSpace); 84 if (nodes.Count < 1 ) 85 { 86 return ; 87 } 88 89 this .m_coreProperties = new Dictionary<String, String> (); 90 foreach (XmlElement element in nodes[ 0 ]) 91 { 92 this .m_coreProperties.Add(element.LocalName, element.InnerText); 93 } 94 } 95 #endregion
【三、Word 2007文件的解析】
Word文件(.docx)主要的内容基本都存在于word目录下,比较重要的有以下的内容
document.xml:记录Word文档的正文内容 footer*.xml:记录Word文档的页脚 header*.xml:记录Word文档的页眉 comments.xml:记录Word文档的批注 endnotes.xml:记录WOrd文档的尾注这里我们只读取Word文档的正文内容,由于OOXML文档在存储文字时也是嵌套结构存储的,比如对于Word而言,<w:p></w:p>之间存储的是段落,段落中会嵌套着<w:t></w:t>,而这个存储的是文字。除此之外<w:tab/>是Tab符号,<w:br w:type="page"/>是分页符等等,所以我们需要写一个方法递归处理这些标签:
View Code
1 /// <summary> 2 /// 抽取Node中的文字 3 /// </summary> 4 /// <param name="node"> XmlNode </param> 5 /// <returns> Node中的文字 </returns> 6 public static String ReadNode(XmlNode node) 7 { 8 if ((node == null ) || (node.NodeType != XmlNodeType.Element)) // 如果node为空 9 { 10 return String.Empty; 11 } 12 13 StringBuilder nodeContent = new StringBuilder(); 14 15 foreach (XmlNode child in node.ChildNodes) 16 { 17 if (child.NodeType != XmlNodeType.Element) 18 { 19 continue ; 20 } 21 22 switch (child.LocalName) 23 { 24 case " t " : // 正文 25 nodeContent.Append(child.InnerText.TrimEnd()); 26 27 String space = ((XmlElement)child).GetAttribute( " xml:space " ); 28 if ((!String.IsNullOrEmpty(space)) && (space == " preserve " )) nodeContent.Append( ' ' ); 29 break ; 30 case " cr " : // 换行符 31 case " br " : // 换页符 32 nodeContent.Append(Environment.NewLine); 33 break ; 34 case " tab " : // Tab 35 nodeContent.Append( " \t " ); 36 break ; 37 case " p " : // 段落 38 nodeContent.Append(ReadNode(child)); 39 nodeContent.Append(Environment.NewLine); 40 break ; 41 default : // 其他情况 42 nodeContent.Append(ReadNode(child)); 43 break ; 44 } 45 } 46 47 return nodeContent.ToString(); 48 }
然后我们从根标签开始读取就可以了
View Code
1 #region 常量 2 private const String WordNameSpace = " http://schemas.openxmlformats.org/wordprocessingml/2006/main " ; 3 #endregion 4 5 #region 字段 6 private String m_paragraphText; 7 #endregion 8 9 #region 属性 10 /// <summary> 11 /// 获取文档正文内容 12 /// </summary> 13 public String ParagraphText 14 { 15 get { return this .m_paragraphText; } 16 } 17 #endregion 18 19 #region 读取内容 20 protected override void ReadContent() 21 { 22 if ( this .m_package == null ) 23 { 24 return ; 25 } 26 27 PackagePart part = this .m_package.GetPart( new Uri( " /word/document.xml " , UriKind.Relative)); 28 if (part == null ) 29 { 30 return ; 31 } 32 33 StringBuilder content = new StringBuilder(); 34 XmlDocument doc = new XmlDocument(); 35 doc.Load(part.GetStream()); 36 37 XmlNamespaceManager nsManager = new XmlNamespaceManager(doc.NameTable); 38 nsManager.AddNamespace( " w " , WordNameSpace); 39 40 XmlNode node = doc.SelectSingleNode( " /w:document/w:body " , nsManager); 41 42 if (node == null ) 43 { 44 return ; 45 } 46 47 content.Append(NodeHelper.ReadNode(node)); 48 49 this .m_paragraphText = content.ToString(); 50 } 51 #endregion
【四、PowerPoint 2007文件的解析】
PowerPoint文件(.pptx)主要的内容都存在于ppt目录下,而幻灯片的信息则又在slides子目录下,这里边幻灯片按照slide + 页序号 +.xml的名称进行存储,我们挨个顺序读取就可以。不过需要注意的是,由于字符串比较的问题,如“slide10.xml”<"slide2.xml",所以如果你按顺序读取的话可能会出现页码错乱的情况,所以我们可以先进行排序然后再挨个页面从根标签读取就可以了。
View Code
1 #region 常量 2 private const String PowerPointNameSpace = " http://schemas.openxmlformats.org/presentationml/2006/main " ; 3 #endregion 4 5 #region 字段 6 private StringBuilder m_allText; 7 #endregion 8 9 #region 属性 10 /// <summary> 11 /// 获取PowerPoint幻灯片中所有文本 12 /// </summary> 13 public String AllText 14 { 15 get { return this .m_allText.ToString(); } 16 } 17 #endregion 18 19 #region 构造函数 20 /// <summary> 21 /// 初始化PptxFile 22 /// </summary> 23 /// <param name="filePath"> 文件路径 </param> 24 public PptxFile(String filePath) : 25 base (filePath) { } 26 #endregion 27 28 #region 读取内容 29 protected override void ReadContent() 30 { 31 if ( this .m_package == null ) 32 { 33 return ; 34 } 35 36 this .m_allText = new StringBuilder(); 37 38 XmlDocument doc = null ; 39 PackagePartCollection col = this .m_package.GetParts(); 40 SortedList<Int32, XmlDocument> list = new SortedList<Int32, XmlDocument> (); 41 42 foreach (PackagePart part in col) 43 { 44 if (part.Uri.ToString().IndexOf( " ppt/slides/slide " , StringComparison.OrdinalIgnoreCase) > - 1 ) 45 { 46 doc = new XmlDocument(); 47 doc.Load(part.GetStream()); 48 49 String pageName = part.Uri.ToString().Replace( " /ppt/slides/slide " , "" ).Replace( " .xml " , "" ); 50 Int32 index = 0 ; 51 Int32.TryParse(pageName, out index); 52 53 list.Add(index, doc); 54 } 55 } 56 57 foreach (KeyValuePair<Int32, XmlDocument> pair in list) 58 { 59 XmlNamespaceManager nsManager = new XmlNamespaceManager(doc.NameTable); 60 nsManager.AddNamespace( " p " , PowerPointNameSpace); 61 62 XmlNode node = pair.Value.SelectSingleNode( " /p:sld " , nsManager); 63 64 if (node == null ) 65 { 66 continue ; 67 } 68 69 this .m_allText.Append(NodeHelper.ReadNode(node)); 70 } 71 } 72 #endregion
附,本系列全部代码下载: https://files.cnblogs.com/mayswind/DotMaysWind.OfficeReader_4.rar
【五、常见Office文档(Word、PowerPoint、Excel)文件的开源类库】
1、NPOI: http://npoi.codeplex.com
这个没的说,.NET上最好的,没有之一,Office文档类库,提供完整的Excel读取与编辑操作,目前支持二进制(.xls)文件和OOXML(.xlsx)两种格式。如果用过Apache的Java类库POI的话,NPOI提供几乎一样的类库。实际上,对于ASP.NET,需要编辑的Office文档大多都是Excel文件,或者也可以使用Excel文件代替,所以使用NPOI几乎已经能满足所有需要。目前已经支持docx文件,而doc的支持则在NPOI.ScratchPad中,大家可以去Source Code中下载自己编译。如果不需要OOXML的话,类库仅有1.5MB,并且支持.NET CLR 2.0和4.0。
2、Open XML SDK 2.0 for Microsoft Office: http://msdn.microsoft.com/en-us/library/bb448854(office.14).aspx
微软提供的Open XML SDK,支持读写任意OOXML文档,其同时提供了一个工具,可以打开Office文档然后直接生成使用该类库生成该文档的程序代码。只不过类库确实大了些,有5MB之多,并且需要.NET Framework 3.5的支持。
3、Office Binary Translator to Open XML: http://b2xtranslator.sourceforge.net/
这是我最近才知道的一个类库,其实很早很早以前就有了,其可以将Windows复合文档(.doc、.ppt、.xls)转换为对应的OOXML格式(.docx、.pptx、.xlsx),当然你也可以获取文件中存储的内容。不知道为什么,这个网站被墙了。如果你想研究Windows复合文档的话,我比较推荐这个类库,因为NPOI实在是太完美的一个类库,要想走一遍文件读取的流程实在是太复杂,但是如果用这个类库单步的话还是很容易懂的。这个类库将每种文件的支持(以及支持的模块等)都拆分到了不同的项目中,支持每种文件仅需要几百KB,而且是基于.NET CLR 2.0的。
4、EPPlus: http://epplus.codeplex.com
在2010年NPOI还不支持OOXML的时候,个人感觉EPPlus是最好的.xlsx文件处理的类库,其仅有几百KB,非常轻量,对于zip文件的读取,这个类库没有选择SharpZipLib或者DotNetZip,老版本需要.NET Framework 3.0就行,刚看了下新版本得需要.NET Framework 3.5才可以。
5、ExcelDataReader: http://exceldatareader.codeplex.com
也是一个非常轻量并且好用的库,同时支持读取.xls和.xlsx,当年在使用EPPlus之前使用的这个类库,记不得是因为什么问题替换成了EPPlus,也不知道这个问题现在解决了没有。这个类库的好处是仅需要.NET CLR 2.0,并且支持.NET CF,只不过现在已经不需要开发Windows Mobile的应用了。
【六、相关链接】
1、OpenXMLDeveloper.org: http://openxmldeveloper.org
2、如何:从 Office Open XML 文档检索段落: http://msdn.microsoft.com/zh-cn/library/bb669175.aspx
3、如何操作 Office Open XML 格式文档: http://www.microsoft.com/china/msdn/library/office/office/howManipulateOfficexml.mspx
4、如何实现...(打开 XML SDK): http://msdn.microsoft.com/zh-cn/library/bb491088.aspx
【后记】
终于到了最后一篇,这个系列就到这结束了,感谢大家的捧场,我也终于实现了两年前的心愿。说实话,我确实没想到第一篇会有那么多的访问和推荐,因为需要解析Office文档的毕竟是少数的。写这四篇文章也希望起到抛砖引玉的作用,起码可以对Office文档有个最基础的了解,而之后如果想深入了解下去也会容易得多,这也是我要把这些内容写出来的原因。
版权声明:本文发布于 博客园 ,作者为 大魔王mAysWINd ,文章欢迎转载,但请保留此段版权声明和原始链接,谢谢合作!
分类: C#
标签: .NET C# Office Open XML 文件 文字 解析 读取
作者: Leo_wl
出处: http://www.cnblogs.com/Leo_wl/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
版权信息查看更多关于.NET平台下不借助Office实现Word、Powerpoint等文件的解析(完)的详细内容...