【题外话】

自家猛然发现未来做Office文书档案的解析要比二〇一〇年的时候便于得多,因为文书档案从二零零六年上马更新了很多过多次,读起来也愈加简单。写前两篇作品的时候参考的不在少数大概微软的旧文书档案(二零零六年的),写那篇的时候重下了具备的文书档案,发现各类文书档案都好读得多,整理得也更系统,感觉微软真的是用心在做那一个开放的事。当然,那么些文书档案半数以上也是贰零零捌年的时候才起来披表露去的,仔细揣摩当年依然很幸运的。

 

【连串索引】 

  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. 什么人知的文书档案与FAT和DIFAT
  2. 奇怪的DocumentSummary和Summary
  3. PowerPoint
    Document的组织与分析
  4. 相关链接

 

【壹 、奇怪的文书档案与FAT和DIFAT】

在刚起头做分析的时候,大都以从Word文书档案(.doc)动手,而doc文书档案没有太多复杂的东西,所以依据流程都能够轻松实现,也不会产出什么错误。不过做PowerPoint解析的时候就会遇上海重机厂重难题,比如假若按第二节讲的进行解析Directory的话会发现,很多PowerPoint文档是从未有过DocumentSummaryInformation的,那还不是重点,关键是,还有一些竟是连PowerPoint
Document都没有,见下图。

图片 1

其实那种题材不仅仅解析PowerPoint的时候会碰到,解析Excel的时候同样会碰着,那么这到底是如何难点吗?其实我们在读取Directory时,认为Directory所在的Sector是按EntryID从小到大排列的,但实质上DirectoryEntry并不一定是这么的,并且有些Entry所在的Sector有大概在RootEntry此前。

不知大家是还是不是还记得FAT和DIFAT那八个布局,尽管从第二篇就读取了例如开始的岗位和个数,可是一贯尚未运用,那么本篇先详细介绍一下那俩结构。

第③来看下微软的文书档案是哪些描述那俩结构的:

图片 2

咱俩得以看出,FAT、DIFAT其实是4字节的结构,那她们有啥样作用吗?咱们了然,Windows复合文书档案是以Sector为单位存款和储蓄的文书档案,不过Sector的次第并不一定是储存的光景相继,所以大家须求有3个记下着拥有Sector顺序的结构,那么这几个就是FAT表。

那么FAT表里储存的是怎么呢?FAT表其实本人也是一个Sector,只然则那么些Sector存款和储蓄的是其他Sector的ID,即每一个FAT表存款和储蓄了1贰二十一个SectorID,并且这几个顺序正是Sector的实在顺序。所以,获取了颇具的FAT表,然后再得到具有的SectorID,其实就收获了全数Sector的顺序。当然,大家实际上只必要仓库储存全部FAT表的SectorID就行,然后依据依照SectorID在FAT表中寻找下3个SectorID就可。

还记得首先篇读取文件头Header么?在文书头的尾声有109块指向FAT表的SectorID,经过计量,假如那10柒个FAT表全体填满,那么一共能够总结109
* 1二十六个SectorID,也便是除了文件头一共有109 * 128 *
512字节,所以任何文件最多是512 + 109 * 128 * 512 = 7143936 Byte =
6976.5 KB = 6.81
MB。假诺文件再大如何是好?那时候就有了DIFAT,DIFAT是记录剩余FAT表的SectorID的,也正是一定于Header中10八个FAT表的SectorID的扩张。所以,大家得以由此文件头Header和DIFAT获取具有FAT表的SectorID,然后经过那么些FAT表的SectorID再取得具有的Sector的一一。

第三我们取得文件头中前107个FAT表的SectorID:

图片 3图片 4View Code

 1 protected List<UInt32> m_fatSectors;
 2 
 3 private void ReadFirst109FatSectors()
 4 {
 5     for (Int32 i = 0; i < 109; i++)
 6     {
 7         UInt32 nextSector = this.m_reader.ReadUInt32();
 8 
 9         if (nextSector == CompoundBinaryFile.FreeSector)
10         {
11             break;
12         }
13 
14         this.m_fatSectors.Add(nextSector);
15     }
16 }

内需表明的是,那里并从未看清FAT的数目是还是不是抢先109块,因为假诺FAT为空,则标识为FreeSector,即0xFFFFFFFF,所以读取到FreeSector时表明之后不再有FAT,即能够脱离读取。全数大规模的标识见下。

protected const UInt32 MaxRegSector = 0xFFFFFFFA;
protected const UInt32 DifSector = 0xFFFFFFFC;
protected const UInt32 FatSector = 0xFFFFFFFD;
protected const UInt32 EndOfChain = 0xFFFFFFFE;
protected const UInt32 FreeSector = 0xFFFFFFFF;

假定FAT的数码超过109,我们还必要经过读取DIFAT来赢得剩余FAT的岗位,必要验证的是,每一种DIFAT只存款和储蓄12多少个FAT,而最终4字节则为下3个DIFAT的SectorID,所以我们能够通过此遍历全数的FAT。

图片 5图片 6View Code

 1 private void ReadLastFatSectors()
 2 {
 3     UInt32 difSectorID = this.m_difStartSectorID;
 4 
 5     while (true)
 6     {
 7         Int64 entryStart = this.GetSectorOffset(difSectorID);
 8         this.m_stream.Seek(entryStart, SeekOrigin.Begin);
 9 
10         for (Int32 i = 0; i < 127; i++)
11         {
12             UInt32 fatSectorID = this.m_reader.ReadUInt32();
13 
14             if (fatSectorID == CompoundBinaryFile.FreeSector)
15             {
16                 return;
17             }
18 
19             this.m_fatSectors.Add(fatSectorID);
20         }
21 
22         difSectorID = this.m_reader.ReadUInt32();
23         if (difSectorID == CompoundBinaryFile.EndOfChain)
24         {
25             break;
26         }
27     }
28 }

小说到那,大家应该能驾驭接下去做什么样了啊?此前由于“理所当然”地觉得Sector的逐一正是储存的一一,所以造成多如牛毛DirectoryEntry不可能读取出来。所以往后大家相应首先得到DirectoryEntry所占Sector的真实性顺序。

图片 7图片 8View Code

 1 protected List<UInt32> m_dirSectors;
 2 
 3 protected UInt32 GetNextSectorID(UInt32 sectorID)
 4 {
 5     UInt32 sectorInFile = this.m_fatSectors[(Int32)(sectorID / 128)];
 6     this.m_stream.Seek(this.GetSectorOffset(sectorInFile) + 4 * (sectorID % 128), SeekOrigin.Begin);
 7 
 8     return this.m_reader.ReadUInt32();
 9 }
10 
11 private void ReadDirectory()
12 {
13     if (this.m_reader == null)
14     {
15         return;
16     }
17 
18     this.m_dirSectors = new List<UInt32>();
19     UInt32 sectorID = this.m_dirStartSectorID;
20 
21     while (true)
22     {
23         this.m_dirSectors.Add(sectorID);
24         sectorID = this.GetNextSectorID(sectorID);
25 
26         if (sectorID == CompoundBinaryFile.EndOfChain)
27         {
28             break;
29         }
30     }
31 
32     UInt32 leftSiblingEntryID, rightSiblingEntryID, childEntryID;
33     this.m_dirRootEntry = GetDirectoryEntry(0, null, out leftSiblingEntryID, out rightSiblingEntryID, out childEntryID);
34     this.ReadDirectoryEntry(this.m_dirRootEntry, childEntryID);
35 }

下一场拿走各个DirectoryEntry偏移的不二法门也相应改为:

图片 9图片 10View Code

1 protected Int64 GetDirectoryEntryOffset(UInt32 entryID)
2 {
3     UInt32 sectorID = this.m_dirSectors[(Int32)(entryID * CompoundBinaryFile.DirectoryEntrySize / this.m_sectorSize)];
4     return this.GetSectorOffset(sectorID) + (entryID * CompoundBinaryFile.DirectoryEntrySize) % this.m_sectorSize;
5 }

那般全部的DirectoryEntry就都能博取到了。注意,除了Directory应该先读取SectorID和一一再依照那几个顺序读取DirectoryEntry外,读取每种DirectoryEntry也应当率先读取这么些Entry所占的Sector的ID和顺序,然后再开始展览读取,思路类似就不再贴代码(能够见这里),详见作品最终附的主次。

 

【二、奇怪的DocumentSummary和Summary】

在能真正得到具有的DirectoryEntry之后,不清楚大家发现了从未有过,很多文书档案的DocumentSummary和Summary却照旧无力回天获取到的,一般说来正是得到SectorID后Seek到内定地点后读到的数目跟预期的有太大的两样。可是有个很好玩的事正是,这一个非常小概读取的DocumentSummary和Summary的尺寸都以低于4096的,如下图。

图片 11

那么难点出在哪儿呢?还记得不记得大家第1篇到读取的什么协会现在还没用到?没错,正是MiniFAT。或然您想到了,DirectoryEntry中记录的SectorID不自然正是FAT的SectorID,还有也许是Mini-SectorID,那也就导致了实在读取的剧情与预期的不比。在Windows复合文件中有诸如此类一个规定,便是凡是小于4096字节的始末,都要放置于Mini-Sector中,当然这么些4096以此数也是存在于文件头Header中,大家得以在如下图的岗位读取它,可是这几个数是稳定4096的。

图片 12

宛如FAT一样,Mini-Sector的音讯也是存放在Mini-FAT表中的,不过Sector是从文件头Header之后启幕的,那么Mini-Sector是从哪里发轫的啊?官方文书档案是这样说的,Mini-Sector所占的第二个Sector地方即Root
Entry指向的SectorID,Mini-Sector总共的尺寸即Root
Entry所记录的长短。我们能够经过刚才的FAT表获取具有Mini-Sector所占的Sector的顺序。

图片 13图片 14View Code

 1 protected List<UInt32> m_miniSectors;
 2 
 3 private void ReadMiniFatSectors()
 4 {
 5     UInt32 sectorID = this.m_miniFatStartSectorID;
 6 
 7     while (true)
 8     {
 9         this.m_minifatSectors.Add(sectorID);
10         sectorID = this.GetNextSectorID(sectorID);
11 
12         if (sectorID == CompoundBinaryFile.EndOfChain)
13         {
14             break;
15         }
16     }
17 }

光有了Mini-Sector所占的Sector的相继还不够,我们还亟需精通Mini-Sector是何等的一一。那点与FAT基本相同,固不在此赘述。

图片 15图片 16View Code

 1 protected List<UInt32> m_minifatSectors;
 2 
 3 private void ReadMiniFatSectors()
 4 {
 5     UInt32 sectorID = this.m_miniFatStartSectorID;
 6 
 7     while (true)
 8     {
 9         this.m_minifatSectors.Add(sectorID);
10         sectorID = this.GetNextSectorID(sectorID);
11 
12         if (sectorID == CompoundBinaryFile.EndOfChain)
13         {
14             break;
15         }
16     }
17 }

接下来大家去写八个新的GetEntryOffset去满意不相同的DirectoryEntry。

图片 17图片 18View Code

 1 protected Int64 GetEntryOffset(DirectoryEntry entry)
 2 {
 3     if (entry.Length >= this.m_miniCutoffSize)
 4     {
 5         return GetSectorOffset(entry.SectorID);
 6     }
 7     else
 8     {
 9         return GetMiniSectorOffset(entry.SectorID);
10     }
11 }
12 
13 protected Int64 GetSectorOffset(UInt32 sectorID)
14 {
15     return HeaderSize + this.m_sectorSize * sectorID;
16 }
17 
18 protected Int64 GetMiniSectorOffset(UInt32 miniSectorID)
19 {
20     UInt32 sectorID = this.m_miniSectors[(Int32)((miniSectorID * this.m_miniSectorSize) / this.m_sectorSize)];
21     UInt32 offset = (UInt32)((miniSectorID * this.m_miniSectorSize) % this.m_sectorSize);
22 
23     return HeaderSize + this.m_sectorSize * sectorID + offset;
24 }

现行反革命再尝试,是还是不是有所的Office文书档案的DocumentSummary和Summary都能读取到了吗?

 

【③ 、PowerPoint Document的构造与分析】

跟Word不一致的是,WordDocument永远是Header后的率先个Sector,但是PowerPoint
Document就不肯定咯,然则PowerPoint不像Word那样,要想读取文字,还索要先读取WordDocument中的FIB以及TableStream中的数据才能读取文本,全体PowerPoint幻灯片的数据都存款和储蓄在PowerPoint
Document中。

简单来讲说,PowerPoint中贮存的始末是以Record为根基的,Record又包蕴Container
Record和Atom
Record两种,从名字其实就能够看看,前者是容器,后者是容器中的内容,那么实际上PowerPoint
Document中蕴藏的莫过于相当于树形结构。

对此每个Record,其组织如下:

  1. 从000H到001H的2字节UInt16,是Record的版本,当中低3个人是recVer(尤其的是,假如为0xF则一定为Container),高1多少人是recInstance。
  2. 从002H到003H的2字节UInt16,是Record的类型recType。
  3. 从004H到007H的4字节UInt32,是Record内容的尺寸recLen。
  4. 此后recLen字节是Record的具体内容。

接下去常见的recType的档次:

  1. 如果为0x03E8(1000),则为DocumentContainer。
  2. 如果为0x0FF0(4080),则为MasterListWithTextContainer或SlideListWithTextContainer或NotesListWithTextContainer。
  3. 如果为0x03F3(1011),则为MasterPersistAtom或SlidePersistAtom或NotesPersistAtom。
  4. 如果为0x0F9F(3999),则为TextHeaderAtom。
  5. 如果为0x03EA(1002),则为EndDocumentAtom。
  6. 如果为0x03F8(1016),则为MainMasterContainer。
  7. 如果为0x040C(1036),则为DrawingContainer。
  8. 如果为0x03EE(1006),则为SlideContainer。
  9. 如果为0x0FD9(4057),则为SlideHeadersFootersContainer或NotesHeadersFootersContainer。
  10. 如果为0x03EF(1007),则为SlideAtom。
  11. 如果为0x03F0(1008),则为NotesContainer。
  12. 如果为0x0FA0(4000),则为TextCharsAtom。
  13. 如果为0x0FA8(4008),则为TextBytesAtom。
  14. 即便为0x0FBA(4026),则为CString,储存很多文字的Atom。

是因为PowerPoint支持广大种Record,那里只列举可能用到的部分Record,别的的就不一一列举了,详细内容能够参照微软文书档案“[MS-PPT].pdf”的2.13.24节(http://msdn.microsoft.com/en-us/library/dd945336)。

为了更好地询问Record和PowerPoint Document,大家创制三个Record类

图片 19图片 20View Code

  1 public enum RecordType : uint
  2 {
  3     Unknown = 0,
  4     DocumentContainer = 0x03E8,
  5     ListWithTextContainer = 0x0FF0,
  6     PersistAtom = 0x03F3,
  7     TextHeaderAtom = 0x0F9F,
  8     EndDocumentAtom = 0x03EA,
  9     MainMasterContainer = 0x03F8,
 10     DrawingContainer = 0x040C,
 11     SlideContainer = 0x03EE,
 12     HeadersFootersContainer = 0x0FD9,
 13     SlideAtom = 0x03EF,
 14     NotesContainer = 0x03F0,
 15     TextCharsAtom = 0x0FA0,
 16     TextBytesAtom = 0x0FA8,
 17     CString = 0x0FBA
 18 }
 19 
 20 public class Record
 21 {
 22     #region 字段
 23     private UInt16 m_recVer;
 24     private UInt16 m_recInstance;
 25     private RecordType m_recType;
 26     private UInt32 m_recLen;
 27     private Int64 m_offset;
 28 
 29     private Int32 m_deepth;
 30     private Record m_parent;
 31     private List<Record> m_children;
 32     #endregion
 33 
 34     #region 属性
 35     /// <summary>
 36     /// 获取RecordVersion
 37     /// </summary>
 38     public UInt16 RecordVersion
 39     {
 40         get { return this.m_recVer; }
 41     }
 42 
 43     /// <summary>
 44     /// 获取RecordInstance
 45     /// </summary>
 46     public UInt16 RecordInstance
 47     {
 48         get { return this.m_recInstance; }
 49     }
 50 
 51     /// <summary>
 52     /// 获取Record类型
 53     /// </summary>
 54     public RecordType RecordType
 55     {
 56         get { return this.m_recType; }
 57     }
 58 
 59     /// <summary>
 60     /// 获取Record内容大小
 61     /// </summary>
 62     public UInt32 RecordLength
 63     {
 64         get { return this.m_recLen; }
 65     }
 66     
 67     /// <summary>
 68     /// 获取Record相对PowerPoint Document偏移
 69     /// </summary>
 70     public Int64 Offset
 71     {
 72         get { return this.m_offset; }
 73     }
 74 
 75     /// <summary>
 76     /// 获取Record深度
 77     /// </summary>
 78     public Int32 Deepth
 79     {
 80         get { return this.m_deepth; }
 81     }
 82 
 83     /// <summary>
 84     /// 获取Record的父节点
 85     /// </summary>
 86     public Record Parent
 87     {
 88         get { return this.m_parent; }
 89     }
 90 
 91     /// <summary>
 92     /// 获取Record的子节点
 93     /// </summary>
 94     public List<Record> Children
 95     {
 96         get { return this.m_children; }
 97     }
 98     #endregion
 99 
100     #region 构造函数
101     /// <summary>
102     /// 初始化新的Record
103     /// </summary>
104     /// <param name="parent">父节点</param>
105     /// <param name="version">RecordVersion和Instance</param>
106     /// <param name="type">Record类型</param>
107     /// <param name="length">Record内容大小</param>
108     /// <param name="offset">Record相对PowerPoint Document偏移</param>
109     public Record(Record parent, UInt16 version, UInt16 type, UInt32 length, Int64 offset)
110     {
111         this.m_recVer = (UInt16)(version & 0xF);
112         this.m_recInstance = (UInt16)(version & 0xFFF0);
113         this.m_recType = (RecordType)type;
114         this.m_recLen = length;
115         this.m_offset = offset;
116         this.m_deepth = (parent == null ? 0 : parent.m_deepth + 1);
117         this.m_parent = parent;
118 
119         if (m_recVer == 0xF)
120         {
121             this.m_children = new List<Record>();
122         }
123     }
124     #endregion
125 
126     #region 方法
127     public void AddChild(Record entry)
128     {
129         if (this.m_children == null)
130         {
131             this.m_children = new List<Record>();
132         }
133 
134         this.m_children.Add(entry);
135     }
136     #endregion
137 }

接下来大家遍历全部节点读取Record的树形结构

图片 21图片 22View Code

 1 private StringBuilder m_recordTree;
 2 
 3 /// <summary>
 4 /// 获取PowerPoint中Record的树形结构
 5 /// </summary>
 6 public String RecordTree
 7 {
 8     get { return this.m_recordTree.ToString(); }
 9 }
10 
11 protected override void ReadContent()
12 {
13     DirectoryEntry entry = this.m_dirRootEntry.GetChild("PowerPoint Document");
14 
15     if (entry == null)
16     {
17         return;
18     }
19 
20     Int64 entryStart = this.GetEntryOffset(entry);
21     this.m_stream.Seek(entryStart, SeekOrigin.Begin);
22 
23     this.m_recordTree = new StringBuilder();
24     this.m_records = new List<Record>();
25     Record record = null;
26 
27     while (this.m_stream.Position < this.m_stream.Length)
28     {
29         record = this.ReadRecord(null);
30 
31         if (record == null || record.RecordType == 0)
32         {
33             break;
34         }
35     }
36 }
37 
38 private Record ReadRecord(Record parent)
39 {
40     Record record = GetRecord(parent);
41 
42     if (record == null)
43     {
44         return null;
45     }
46     else
47     {
48         this.m_recordTree.Append('-', record.Deepth * 2);
49         this.m_recordTree.AppendFormat("[{0}]-[{1}]-[Len:{2}]", record.RecordType, record.Deepth, record.RecordLength);
50         this.m_recordTree.AppendLine();
51     }
52 
53     if (parent == null)
54     {
55         this.m_records.Add(record);
56     }
57     else
58     {
59         parent.AddChild(record);
60     }
61 
62     if (record.RecordVersion == 0xF)
63     {
64         while (this.m_stream.Position < record.Offset + record.RecordLength)
65         {
66             this.ReadRecord(record);
67         }
68     }
69     else
70     {
71         this.m_stream.Seek(record.RecordLength, SeekOrigin.Current);
72     }
73 
74     return record;
75 }
76 
77 private Record GetRecord(Record parent)
78 {
79     if (this.m_stream.Position >= this.m_stream.Length)
80     {
81         return null;
82     }
83 
84     UInt16 version = this.m_reader.ReadUInt16();
85     UInt16 type = this.m_reader.ReadUInt16();
86     UInt32 length = this.m_reader.ReadUInt32();
87 
88     return new Record(parent, version, type, length, this.m_stream.Position);
89 }

结果类似于如下图所示

图片 23

骨子里假使要读取PowerPoint中存有的公文,那么只须求读取全部的TextCharsAtom、TextBytesAtom和CString就能够,供给表明的是,TextBytesAtom是以Ansi单字节实行仓库储存的,而除此以外多个则是以Unicode格局储存的。上节大家早已读取过Word,那么接下去就不讨厌了啊。

咱俩实际上只要把读取到Atom时跳过内容的那句话“this.m_stream.Seek(record.RecordLength,
SeekOrigin.Current);”替换为如下代码就足以了。

图片 24图片 25View Code

 1 if (record.RecordType == RecordType.TextCharsAtom || record.RecordType == RecordType.CString)//找到Unicode双字节文字内容
 2 {
 3     Byte[] data = this.m_reader.ReadBytes((Int32)record.RecordLength);
 4     this.m_allText.Append(StringHelper.GetString(true, data));
 5     this.m_allText.AppendLine();
 6     
 7 }
 8 else if (record.RecordType == RecordType.TextBytesAtom)//找到Unicode<256单字节文字内容
 9 {
10     Byte[] data = this.m_reader.ReadBytes((Int32)record.RecordLength);
11     this.m_allText.Append(StringHelper.GetString(false, data));
12     this.m_allText.AppendLine();
13 }
14 else
15 {
16     this.m_stream.Seek(record.RecordLength, SeekOrigin.Current);
17 }

但是尽管那样读取的话,也会把母版页及其余情节读取进来,比如下图:

图片 26

由此大家得以经过判断文字父Record的档次来控制是或不是读取那段文字。经常存放文字的Record有“ListWithTextContainer和HeadersFootersContainer”,大家仅须求判定文字Record的父Record是或不是是那俩就能够的。但是有一些,在用PowerPoint
二〇一二囤积的ppt文件,假若只判定那俩是读取不到内容的,还索要看清Type值为0xF00D的Record,但是那个RecordType在脚下新型的文书档案中并从未注脚。

此间把全部的代码贴出来:

图片 27图片 28View Code

  1 protected override void ReadContent()
  2 {
  3     DirectoryEntry entry = this.m_dirRootEntry.GetChild("PowerPoint Document");
  4 
  5     if (entry == null)
  6     {
  7         return;
  8     }
  9 
 10     Int64 entryStart = this.GetEntryOffset(entry);
 11     this.m_stream.Seek(entryStart, SeekOrigin.Begin);
 12 
 13     #region 测试方法
 14     this.m_recordTree = new StringBuilder();
 15     #endregion
 16 
 17     this.m_allText = new StringBuilder();
 18     this.m_records = new List<Record>();
 19     Record record = null;
 20 
 21     while (this.m_stream.Position < this.m_stream.Length)
 22     {
 23         record = this.ReadRecord(null);
 24 
 25         if (record == null || record.RecordType == 0)
 26         {
 27             break;
 28         }
 29     }
 30 
 31     this.m_allText = new StringBuilder(StringHelper.ReplaceString(this.m_allText.ToString()));
 32 }
 33 
 34 private Record ReadRecord(Record parent)
 35 {
 36     Record record = GetRecord(parent);
 37 
 38     if (record == null)
 39     {
 40         return null;
 41     }
 42     #region 测试方法
 43     else
 44     {
 45         this.m_recordTree.Append('-', record.Deepth * 2);
 46         this.m_recordTree.AppendFormat("[{0}]-[{1}]-[Len:{2}]", record.RecordType, record.Deepth, record.RecordLength);
 47         this.m_recordTree.AppendLine();
 48     }
 49     #endregion
 50 
 51     if (parent == null)
 52     {
 53         this.m_records.Add(record);
 54     }
 55     else
 56     {
 57         parent.AddChild(record);
 58     }
 59 
 60     if (record.RecordVersion == 0xF)
 61     {
 62         while (this.m_stream.Position < record.Offset + record.RecordLength)
 63         {
 64             this.ReadRecord(record);
 65         }
 66     }
 67     else
 68     {
 69         if (record.Parent != null && (
 70             record.Parent.RecordType == RecordType.ListWithTextContainer ||
 71             record.Parent.RecordType == RecordType.HeadersFootersContainer ||
 72             (UInt32)record.Parent.RecordType == 0xF00D))
 73         {
 74             if (record.RecordType == RecordType.TextCharsAtom || record.RecordType == RecordType.CString)//找到Unicode双字节文字内容
 75             {
 76                 Byte[] data = this.m_reader.ReadBytes((Int32)record.RecordLength);
 77                 this.m_allText.Append(StringHelper.GetString(true, data));
 78                 this.m_allText.AppendLine();
 79 
 80             }
 81             else if (record.RecordType == RecordType.TextBytesAtom)//找到Unicode<256单字节文字内容
 82             {
 83                 Byte[] data = this.m_reader.ReadBytes((Int32)record.RecordLength);
 84                 this.m_allText.Append(StringHelper.GetString(false, data));
 85                 this.m_allText.AppendLine();
 86             }
 87             else
 88             {
 89                 this.m_stream.Seek(record.RecordLength, SeekOrigin.Current);
 90             }
 91         }
 92         else
 93         {
 94             this.m_stream.Seek(record.RecordLength, SeekOrigin.Current);
 95         }
 96     }
 97 
 98     return record;
 99 }
100 
101 private Record GetRecord(Record parent)
102 {
103     if (this.m_stream.Position >= this.m_stream.Length)
104     {
105         return null;
106     }
107 
108     UInt16 version = this.m_reader.ReadUInt16();
109     UInt16 type = this.m_reader.ReadUInt16();
110     UInt32 length = this.m_reader.ReadUInt32();
111 
112     return new Record(parent, version, type, length, this.m_stream.Position);
113 }

末段附上那三篇小说全体的代码下载地址: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*

 

【后记】

本想尽量简洁尽量少地去写测试用的代码,结果没悟出好多少个类的代码写到第叁篇照旧写了很多。到那边境海关于Office二进制文书档案文字的抽取就终止了,下篇简要介绍下OOXML(Office
贰零零柒开端的格式)文字抽取的情势。其余,假诺你认为文章对你有用,一定要点个推荐啊;假设小说对您起到了扶助,评论一下又不会怀孕,还是能给自家支持,多好的事。hiahiahia~~

相关文章

网站地图xml地图