【题外话】

上篇文章极美丽被NPOI的大神回复了,同时也改良了自家3个问题,正是NPOI其实是有doc文件的分析,只可是一向未曾跟随正式版公布过,要得到那有的代码,能够移动CodePlex(http://npoi.codeplex.com/),访问在SourceCode中的NPOI.ScratchPad中即可看到。给大家造成的不便在此表示抱歉。

 

【体系索引】 

  1. Office文件的深邃——.NET平台下不借助Office达成Word、Powerpoint等公事的辨析(一) 赢得Office二进制文书档案的DocumentSummaryInformation以及SummaryInformation
  2. Office文件的深邃——.NET平台下不借助Office完结Word、Powerpoint等公事的解析(二) 获取Word二进制文书档案(.doc)的文字内容(包括正文、页眉、页脚、批注等等)
  3. Office文件的深邃——.NET平台下不借助Office实现Word、Powerpoint等文件的分析(三)
    详尽介绍Office二进制文书档案中的存款和储蓄结构,以及取得PowerPoint二进制文书档案(.ppt)的文字内容
  4. Office文件的深邃——.NET平台下不借助Office达成Word、Powerpoint等公事的辨析(完)
    介绍Office Open
    XML文书档案(.docx、.pptx)怎么着开始展览辨析以及解析Office文件常见开源类库

 

【小说索引】

  1. WordDocument和FIB
  2. Table Stream中的Piece Table
  3. 正规获得文字内容
  4. 连带链接

 

【一、WordDocument和FIB】

作者们随后第①篇的代码继续,不知大家有没有翻动过Directory获取到的始末,比如上次的文书档案摘要SummaryInformation和DocumentSummaryInformation,除此之外还有专门储存文书档案内容的DirectoryEntry,比如Word的为“WordDocument”和“1Table”,PowerPoint的为“PowerPoint
Document”,Excel的为“Workbook”。

笔者们先从WordDocument说起。不知我们发现了从未有过,其实无论是是哪些Word文件,WordDocument那个DirectoryEntry的SectorID总是0,也正是说,WordDocument其实正是Header之后的首先个Sector。对于WordDocument,其最关键的相应是内部带有的FIB(File
Information
Block)了,FIB位于WordDocument的发端,其蕴含着Word文件特别首要的参数,诸如文件的加密方法、文字的编码等等。

对于1个FIB,官方文书档案中身为可变长的,在那之中FIB中最起先的为固定32字节长的FibBase:

  1. 从000H到001H的2字节UInt16,是一向为0xA5EC,评释文书档案为Word二进制文件。
  2. 从002H到003H的2字节UInt16,是Word格式的本子(nFib),但实际上那里一般为0xC1,即Word97的格式,真实的本子在事后会现出。
  3. 从00AH到00BH的2字节UInt16,其实那几个UInt16实在被分为了13有个别,除了第⑤有的占了4bit外,其他12有的各站1bit,总括16bit,大家得以由此位运算分别读取每一bit的值,比如Boolean
    isDot = ((n & 0x1) ==
    1),就足以读取最低位是还是不是为真了。插张图来表明下13有个别是如何分配的,最左为UInt16的最低位。
    图片 1

    • A(第0位),为文书档案是不是是.Dot文件(Word模板文件)
    • B(第三人),没精晓这一位存的是何等。
    • C(第二位),为文书档案是或不是是复杂格式(快捷保存时生成的格式)。
    • D(第1位),为文书档案是或不是包括图表。
    • E(第四-四人),当nFib小于0x00D9时为高效保存(Quick
      Save)的次数,当大于0x00D9时始终为0x0F。
    • F(第八位),为文书档案是或不是加密。
    • G(第九个人),为1时文字存款和储蓄于1Table,为0时文字存款和储蓄于0Table。
    • H(第十一位),为是或不是“提出以只读格局打开文书档案”(保存时选拔“工具”->“常规选项”能够安装该属性)。
    • I(第壹1人),为是还是不是有写爱惜密码。
    • J(第叁3位),为固定值1。
    • K(第贰4个人),为是不是要用应用程序的言语暗许值覆盖段落格式中定义的言语和字体。
    • L(第三几人),为文书档案语言是否为南亚语言。
    • M(第贰十四个人),当文书档案加密时,文书档案假若选拔XO冠道混淆则为1,不然为0;文书档案不加密时马虎该属性。
  4. 从00CH到00DH的2字节UInt16,为一定的0x00BF或0x00C1(某个语言的Word97会为0x00C1)
  5. 从00EH到011H的4字节UInt32,当文书档案加密并且混淆,则为混淆的密钥;借使加密不混淆,则为加密头的长短;否则应置0。
  6. 从012H到012H的1字节Byte,应当置0,并且忽略。
  7. 从013H到013H的1字节Byte,被划分为6有个别,除了第伍有的占3bit之外,其他各占1bit。
    • 第③位,必须置0,并且忽略。
    • 第一位,通过右键菜单->新建->新建Word文件创建的空文件为1,别的应当为0。
    • 第1位,为是不是要用应用程序的暗许值覆盖页面中的页面大小、页面方向、页边距等。
    • 第5位和第几位,未定义,应当忽略。
    • 第⑥-6个人,未定义,应当忽略。
  8. 从014H到015H和016H到017H的各2字节,应当置0,并且忽略。
  9. 从018H到01BH和01CH到01FH的各4字节,未定义,应当忽略。

那FibBase之后呢?其实FIB包罗众多的剧情,从FibBase初步按梯次分别是:

  1. 2字节的UInt16,为其后Fib昂CoragW97块中十4位整数的个数,固定为0x000E。
  2. 28字节的FibRgW97块,包含14个UInt16。
  3. 2字节的UInt16,为未来FibOdysseygLw97块中30人整数的个数,固定为0x0016。
  4. 88字节的FibRgLw97块,包含22个UInt32。
  5. 2字节的UInt16,为以往Fib福特ExplorergFcLcb块中六11个人整数的个数(但FibSportagegFcLcb实际存款和储蓄的是叁拾2个人整数)。
  • 固然文书档案为Word97,该项为0x005D。
  • 一旦文书档案为Word2000,该项为0x006C。
  • 只要文书档案为Word2003,该项为0x0088。
  • 假设文书档案为Word2002,该项为0x00玛驰。
  • 倘使文书档案为Word二〇〇五,该项为0x00B7。

不定长的Fib宝马7系gFcLcb块,包含不定个数的三十几人UInt32(数量约等于上述个数的2倍),但可见至少存有1捌十个。

2字节的UInt16,为未来FibMuranogCswNew块中十几位整数的个数。

  • 只要文书档案为Word97,该项为0x00(实际上不分包FibXC60gCswNew)。
  • 假诺文书档案为Word3000-二零零四,该项为0x02。
  • 假若文档为Word2006,该项为0x05。

不定长的Fib君越gCswNew块,首先是固定长度的UInt16即Word文书档案的实在版本nFibNew,然后三个UInt16意味着文书档案在一体化存档后相当的慢存档的次数,之后假如是Word贰零零柒则还有一个UInt16文书档案说并未概念且须要忽略(大囧)。

看完FIB结构后我们先来看下nFib与公事版本对应的图景:

  1. 0x00C1(nFib)表示文件为Word97(或然为更高版本的文书档案)。
  2. 0x00D9(nFibNew)表示文件为Word2000。
  3. 0x0101(nFibNew)表示文件为Word二〇〇三。
  4. 0x010C(nFibNew)表示文件为Word二〇〇〇。
  5. 0x0112(nFibNew)表示文件为Word二〇〇七。

是因为FIB中剧情实在太多了,之后的部分就不再介绍了,然而为了读取文书档案的内容大家还应当看看如下的剧情(当然也不自然都选取)。

  1. FibPRADOgW97中的15个UInt16,为文书档案的语言(lidFE),比如0x0804为简体普通话。要是文书档案是Unicode存款和储蓄的本来无所谓,假设是ANSI码存款和储蓄的那么就须求取得那些了。
  2. FibLX570gLw97中的第3个Int32,为Word Document中有含义的字节数(即Word
    Document之后的字节数都能够忽略)。
  3. FibLacrossegLw97中的第四个Int32,为文书档案中正文(Main document)的总字数。
  4. FibEnclavegLw97中的第陆个Int32,为文书档案中页脚(Footnote
    subdocument)的总字数。
  5. Fib昂CoragLw97中的第伍个Int32,为文档中页眉(Header
    subdocument)的总字数。
  6. FibLX570gLw97中的第八个Int32,为文书档案中批注(Comment
    subdocument)的总篇幅。
  7. Fib纳瓦拉gLw97中的第八个Int32,为文书档案中尾注(Endnote
    subdocument)的总篇幅。
  8. Fib途胜gLw97中的第⑦个Int32,为文书档案普通话本框(Textbox
    subdocument)的总篇幅。
  9. Fib哈弗gLw97中的第壹二个Int32,为文书档案中页眉文本框(Textbox Subdocument of
    the header)的总字数。
  10. Fib凯雷德gFcLcb中的第48个UInt32,为Piece Table在Table
    Stream中的偏移(fcClx)。
  11. Fib凯雷德gFcLcb中的第五8个UInt32,为Piece Table的字节数(lcbClx)。

上述那个消息大家得以编写制定如下代码获取:

图片 2图片 3View Code

  1 #region 字段
  2 private UInt16 m_nFib;
  3 private Boolean m_isComplexFile;
  4 private Boolean m_hasPictures;
  5 private Boolean m_isEncrypted;
  6 private Boolean m_is1Table;
  7 
  8 private UInt16 m_lidFE;
  9 
 10 private Int32 m_cbMac;
 11 private Int32 m_ccpText;
 12 private Int32 m_ccpFtn;
 13 private Int32 m_ccpHdd;
 14 private Int32 m_ccpAtn;
 15 private Int32 m_ccpEdn;
 16 private Int32 m_ccpTxbx;
 17 private Int32 m_ccpHdrTxbx;
 18 
 19 private UInt32 m_fcClx;
 20 private UInt32 m_lcbClx;
 21 #endregion
 22 
 23 #region 读取WordDocument
 24 private void ReadWordDocument()
 25 {
 26     DirectoryEntry entry = this.m_dirRootEntry.GetChild("WordDocument");
 27 
 28     if (entry == null)
 29     {
 30         return;
 31     }
 32 
 33     Int64 entryStart = this.GetSectorOffset(entry.SectorID);
 34     this.m_stream.Seek(entryStart, SeekOrigin.Begin);
 35 
 36     this.ReadFileInformationBlock();
 37 }
 38 
 39 #region 读取FileInformationBlock
 40 private void ReadFileInformationBlock()
 41 {
 42     this.ReadFibBase();
 43     this.ReadFibRgW97();
 44     this.ReadFibRgLw97();
 45     this.ReadFibRgFcLcb();
 46     this.ReadFibRgCswNew();
 47 }
 48 
 49 #region FibBase
 50 private void ReadFibBase()
 51 {
 52     UInt16 wIdent = this.m_reader.ReadUInt16();
 53     if (wIdent != 0xA5EC)
 54     {
 55         throw new Exception("该文件不是Word文件!");
 56     }
 57 
 58     this.m_nFib = this.m_reader.ReadUInt16();
 59     this.m_reader.ReadUInt16();//unused
 60     this.m_reader.ReadUInt16();//lid
 61     this.m_reader.ReadUInt16();//pnNext
 62 
 63     UInt16 flags = this.m_reader.ReadUInt16();
 64     this.m_isComplexFile = this.GetBitFromInteger(flags, 2);
 65     this.m_hasPictures = this.GetBitFromInteger(flags, 3);
 66     this.m_isEncrypted = this.GetBitFromInteger(flags, 8);
 67     this.m_is1Table = this.GetBitFromInteger(flags, 9);
 68 
 69     if (this.m_isComplexFile)
 70     {
 71         throw new Exception("不支持复杂文件的读取!");
 72     }
 73 
 74     if (this.m_isEncrypted)
 75     {
 76         throw new Exception("不支持加密文件的读取!");
 77     }
 78 
 79     this.m_stream.Seek(32 - 12, SeekOrigin.Current);
 80 }
 81 #endregion
 82 
 83 #region FibRgW97
 84 private void ReadFibRgW97()
 85 {
 86     UInt16 count = this.m_reader.ReadUInt16();
 87 
 88     if (count != 0x000E)
 89     {
 90         throw new Exception("FibRgW97长度错误!");
 91     }
 92 
 93     this.m_stream.Seek(26, SeekOrigin.Current);
 94     this.m_lidFE = this.m_reader.ReadUInt16();
 95 }
 96 #endregion
 97 
 98 #region FibRgLw97
 99 private void ReadFibRgLw97()
100 {
101     UInt16 count = this.m_reader.ReadUInt16();
102 
103     if (count != 0x0016)
104     {
105         throw new Exception("FibRgLw97长度错误!");
106     }
107 
108     this.m_cbMac = this.m_reader.ReadInt32();
109     this.m_reader.ReadInt32();//reserved1
110     this.m_reader.ReadInt32();//reserved2
111     this.m_ccpText = this.m_reader.ReadInt32();
112     this.m_ccpFtn = this.m_reader.ReadInt32();
113     this.m_ccpHdd = this.m_reader.ReadInt32();
114     this.m_reader.ReadInt32();//reserved3
115     this.m_ccpAtn = this.m_reader.ReadInt32();
116     this.m_ccpEdn = this.m_reader.ReadInt32();
117     this.m_ccpTxbx = this.m_reader.ReadInt32();
118     this.m_ccpHdrTxbx = this.m_reader.ReadInt32();
119 
120     this.m_stream.Seek(44, SeekOrigin.Current);
121 }
122 #endregion
123 
124 #region FibRgFcLcb
125 private void ReadFibRgFcLcb()
126 {
127     UInt16 count = this.m_reader.ReadUInt16();
128     this.m_stream.Seek(66 * 4, SeekOrigin.Current);
129 
130     this.m_fcClx = this.m_reader.ReadUInt32();
131     this.m_lcbClx = this.m_reader.ReadUInt32();
132 
133     this.m_stream.Seek((count * 2 - 68) * 4, SeekOrigin.Current);
134 }
135 #endregion
136 
137 #region FibRgCswNew
138 private void ReadFibRgCswNew()
139 {
140     UInt16 count = this.m_reader.ReadUInt16();
141     this.m_nFib = this.m_reader.ReadUInt16();
142     this.m_stream.Seek((count - 1) * 2, SeekOrigin.Current);
143 }
144 #endregion
145 #endregion
146 #endregion
147 
148 private Boolean GetBitFromInteger(Int32 integer, Int32 bitIndex)
149 {
150     Int32 num = (Int32)Math.Pow(2, bitIndex);
151     return (integer & num) == num;
152 }

 

【二、Table Stream中的Piece Table】

Table
Stream其实就是1Table要么0Table的总称,具体文字存在分外Table中还要依据FIB中的消息。由于复合文件是以2个个Sector情势储存的,所以大家首先须求获得文字存储在怎样个Sector中。实际上,文本的囤积是由Piece
Element(近日这么叫吧)控制着,包含是或不是启用Unicode、每块的职分等等,那些内容都存放于Table
Stream中的Piece Table中,Piece Table相对Table
Stream的偏移量能够从FIB中拿走到。

至于Piece Element,官方是如此描述的:

图片 4

看上去这么多,其实大家必要的仅是fc中定义的是或不是利用Unicode存款和储蓄文本(fc中第1四位为0则为Unicode,为1则为Ansi),以及文本相对于WordDocument的偏移量(fc中没有三十二个人),大家先是对Piece
Element定义二个类,能够观察,一个Piece Element的分寸实际为2 + 4 + 2 =
8字节:

图片 5图片 6View Code

 1 public class PieceElement
 2 {
 3     #region 字段
 4     private UInt16 m_info;
 5     private UInt32 m_fc;
 6     private UInt16 m_prm;
 7     private Boolean m_isUnicode;
 8     #endregion
 9 
10     #region 属性
11     /// <summary>
12     /// 获取是否以Unicode形式存储文本
13     /// </summary>
14     public Boolean IsUnicode
15     {
16         get { return this.m_isUnicode; }
17     }
18 
19     /// <summary>
20     /// 获取文本偏移量
21     /// </summary>
22     public UInt32 Offset
23     {
24         get { return this.m_fc; }
25     }
26     #endregion
27 
28     #region 构造函数
29     public PieceElement(UInt16 info, UInt32 fcCompressed, UInt16 prm)
30     {
31         this.m_info = info;
32         this.m_fc = fcCompressed & 0x3FFFFFFF;//后30位
33         this.m_prm = prm;
34         this.m_isUnicode = (fcCompressed & 0x40000000) == 0;//第31位
35 
36         if (!this.m_isUnicode) this.m_fc = this.m_fc / 2;
37     }
38     #endregion
39 }

 

下一场大家来看Piece Table,其结构为:

  1. 从000H到000H的1字节Byte,是Piece Table的标识,为一定的0x02。
  2. 从001H到004H的4字节UInt32,是Piece
    Table的高低(即存款和储蓄文字的Sector的数据)。
    法定给了多少个Piece Table中个数的计算公式
    图片 7

    里面,cbPlc即Piece Table的轻重,cbData为三个Piece
    Element的轻重,所以Piece Table中的个数实际为n = (size – 4) / 12。

  3. 之后4*(n + 1)个字节,是各样Piece
    Element存储的文件的开端地点(甘休地方即下3个的始发地点)。
  4. 之后8*n个字节,是各样Piece Element的连带音信。

Piece Table音信咱们能够编写如下代码获取:

图片 8图片 9View Code

 1 private void ReadTableStream()
 2 {
 3     DirectoryEntry entry = this.m_dirRootEntry.GetChild((this.m_is1Table ? "1Table" : "0Table"));
 4 
 5     if (entry == null)
 6     {
 7         return;
 8     }
 9 
10     Int64 pieceTableStart = this.GetSectorOffset(entry.SectorID) + this.m_fcClx;
11     Int64 pieceTableEnd = pieceTableStart + this.m_lcbClx;
12     this.m_stream.Seek(pieceTableStart, SeekOrigin.Begin);
13 
14     Byte clxt = this.m_reader.ReadByte();
15     Int32 prcLen = 0;
16 
17     //判断如果是Prc不是Pcdt
18     while (clxt == 0x01 && this.m_stream.Position < pieceTableEnd)
19     {
20         this.m_stream.Seek(prcLen, SeekOrigin.Current);
21         clxt = this.m_reader.ReadByte();
22         prcLen = this.m_reader.ReadInt32();
23     }
24 
25     if (clxt != 0x02)
26     {
27         throw new Exception("该文件不存在内容!");
28     }
29 
30     UInt32 size = this.m_reader.ReadUInt32();
31     UInt32 count = (size - 4) / 12;
32 
33     this.m_lstPieceStartPosition = new List<UInt32>();
34     this.m_lstPieceEndPosition = new List<UInt32>();
35     this.m_lstPieceElement = new List<PieceElement>();
36 
37     for (Int32 i = 0; i < count; i++)
38     {
39         this.m_lstPieceStartPosition.Add(this.m_reader.ReadUInt32());
40         this.m_lstPieceEndPosition.Add(this.m_reader.ReadUInt32());
41         this.m_stream.Seek(-4, SeekOrigin.Current);
42     }
43 
44     this.m_stream.Seek(4, SeekOrigin.Current);
45 
46     for (Int32 i = 0; i < count; i++)
47     {
48         UInt16 info = this.m_reader.ReadUInt16();
49         UInt32 fcCompressed = this.m_reader.ReadUInt32();
50         UInt16 prm = this.m_reader.ReadUInt16();
51 
52         this.m_lstPieceElement.Add(new PieceElement(info, fcCompressed, prm));
53     }
54 }

 

【叁 、正式获得文本内容】

地方大家得以拿走到Word中文本的起来和停止地点,其实一个Word文书档案中,文字是按如下顺序存款和储蓄的:

  1. 本文内容(Main document)
  2. 脚注(Footnote subdocument)
  3. 页眉和页脚(Header subdocument)
  4. 批注(Comment subdocument)
  5. 尾注(Endnote subdocument)
  6. 文本框(Textbox subdocument)
  7. 页眉文本框(Textbox Subdocument of the header)

就此,大家得以遵照Fib奥迪Q5gLw97中获取的每一局地的字数以及Piece
Table中起初的任务来收获每一部分的文字。

比如正文内容的职位为[0, ccpText],页脚的岗位为[ccpText + 1, ccpText +
1 + ccpFtn]……

为此大家编辑如下代码获取:

图片 10图片 11View Code

  1 #region 字段
  2 private String m_paragraphText;
  3 private String m_footnoteText;
  4 private String m_headerText;
  5 private String m_commentText;
  6 private String m_endnoteText;
  7 private String m_textboxText;
  8 private String m_headerTextboxText;
  9 #endregion
 10 
 11 #region 属性
 12 /// <summary>
 13 /// 获取文档正文内容
 14 /// </summary>
 15 public String ParagraphText
 16 {
 17     get { return this.m_paragraphText; }
 18 }
 19 
 20 /// <summary>
 21 /// 获取文档页脚内容
 22 /// </summary>
 23 public String FootnoteText
 24 {
 25     get { return this.m_footnoteText; }
 26 }
 27 
 28 /// <summary>
 29 /// 获取文档页眉内容
 30 /// </summary>
 31 public String HeaderText
 32 {
 33     get { return this.m_headerText; }
 34 }
 35 
 36 /// <summary>
 37 /// 获取文档批注内容
 38 /// </summary>
 39 public String CommentText
 40 {
 41     get { return this.m_commentText; }
 42 }
 43 
 44 /// <summary>
 45 /// 获取文档尾注内容
 46 /// </summary>
 47 public String EndnoteText
 48 {
 49     get { return this.m_endnoteText; }
 50 }
 51 
 52 /// <summary>
 53 /// 获取文档文本框内容
 54 /// </summary>
 55 public String TextboxText
 56 {
 57     get { return this.m_textboxText; }
 58 }
 59 
 60 /// <summary>
 61 /// 获取文档页眉文本框内容
 62 /// </summary>
 63 public String HeaderTextboxText
 64 {
 65     get { return this.m_headerTextboxText; }
 66 }
 67 #endregion
 68 
 69 #region 读取文本内容
 70 private void ReadPieceText()
 71 {
 72     StringBuilder sb = new StringBuilder();
 73     DirectoryEntry entry = this.m_dirRootEntry.GetChild("WordDocument");
 74 
 75     for (Int32 i = 0; i < this.m_lstPieceElement.Count; i++)
 76     {
 77         Int64 pieceStart = this.GetSectorOffset(entry.SectorID) + this.m_lstPieceElement[i].Offset;
 78         this.m_stream.Seek(pieceStart, SeekOrigin.Begin);
 79 
 80         Int32 length = (Int32)((this.m_lstPieceElement[i].IsUnicode ? 2 : 1) * (this.m_lstPieceEndPosition[i] - this.m_lstPieceStartPosition[i]));
 81         Byte[] data = this.m_reader.ReadBytes(length);
 82         String content = GetString(this.m_lstPieceElement[i].IsUnicode, data);
 83         sb.Append(content);
 84     }
 85 
 86     String allContent = sb.ToString();
 87     Int32 paragraphEnd = this.m_ccpText;
 88     Int32 footnoteEnd = paragraphEnd + this.m_ccpFtn;
 89     Int32 headerEnd = footnoteEnd + this.m_ccpHdd;
 90     Int32 commentEnd = headerEnd + this.m_ccpAtn;
 91     Int32 endnoteEnd = commentEnd + this.m_ccpEdn;
 92     Int32 textboxEnd = endnoteEnd + this.m_ccpTxbx;
 93     Int32 headerTextboxEnd = textboxEnd + this.m_ccpHdrTxbx;
 94 
 95     this.m_paragraphText = allContent.Substring(0, this.m_ccpText);
 96     this.m_footnoteText = allContent.Substring(paragraphEnd, this.m_ccpFtn);
 97     this.m_headerText = allContent.Substring(footnoteEnd, this.m_ccpHdd);
 98     this.m_commentText = allContent.Substring(headerEnd, this.m_ccpAtn);
 99     this.m_endnoteText = allContent.Substring(commentEnd, this.m_ccpEdn);
100     this.m_textboxText = allContent.Substring(endnoteEnd, this.m_ccpTxbx);
101     this.m_headerTextboxText = allContent.Substring(textboxEnd, this.m_ccpHdrTxbx);
102 }
103 #endregion
104 
105 private String GetString(Boolean isUnicode, Byte[] data)
106 {
107     if (isUnicode)
108     {
109         return Encoding.Unicode.GetString(data);
110     }
111     else
112     {
113         return Encoding.GetEncoding("Windows-1252").GetString(data);
114     }
115 }

不过须要专注的是,由于Word文书档案中的换行为“\r”(CRAV4),而Windows中的换行符为“\r\n”(C凯雷德+LF),所以获得文字后必要将“\r”替换为“\r\n”,否则换行将不可能平常显示,除此之外,还有此外的一对特殊字符也急需替换或处理。

附,本文全部代码下载:https://github.com/mayswind/SimpleOfficeReader

 

【四 、相关链接】

*1、Microsoft Open
Specifications:http://www.microsoft.com/openspecifications/en/us/programs/osp/default.aspx
② 、用PHP读取MS
Word(.doc)中的文字:https://imethan.com/post-2009-10-06-17-59.html **
③ 、Office檔案格式:http://www.programmer-club.com.tw/ShowSameTitleN/general/2681.html** 4、LAOLA file
system:http://stuff.mit.edu/afs/athena/astaff/project/mimeutils/share/laola/guide.html*

 

【后记】

本还想周日夜晚发出去,结果恐怕没写完。希望此次的文章能对我们有用。假若您觉得好就点下推荐呗。

相关文章

网站地图xml地图