65.9K
CodeProject 正在变化。 阅读更多。
Home

Searcharoo 2007 (中等信任和 Office 2007 索引)

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.95/5 (9投票s)

2007 年 4 月 29 日

CPOL

10分钟阅读

viewsIcon

216193

downloadIcon

865

移除二进制序列化以解决中等信任问题;为 ASP.NET/C# 免费搜索引擎索引 OpenXML 文档格式

背景

本文接续之前的四篇 Searcharoo 示例

Searcharoo 1 介绍了构建一个简单的搜索引擎,该搜索引擎可以抓取文件系统。开发了一个基本的设计和对象模型来支持简单的单字搜索,结果在一个基础的查询/结果页面中显示。

Searcharoo 2 侧重于添加一个“爬虫”来查找要索引的数据,通过跟踪Web 链接(通过 HTTP 下载文件并解析 HTML)。还讨论了如何将多个搜索词的结果组合成一组“匹配”。

Searcharoo 3 为目录实现了“保存到磁盘”功能,以便在 IIS 应用程序重启后可以重新加载,而无需每次都生成。它还抓取了 FRAMESETs 并为索引器添加了停用词、常用词和词干提取。

Searcharoo 4非文本文件类型(例如 Word、PDF 和 PowerPoint)添加了 IFilter 支持,改进了 robots.txt 支持,添加了一个远程索引控制台应用程序,并进行了大量代码清理(重构!)。

版本 5 简介

本文比大多数文章要短,只涵盖两个主题

  1. 允许 Searcharoo 在 ASP.NET 应用程序仅限于中等信任的网站上运行。v4 中的远程索引控制台应用程序旨在解决此问题 - 但仅仅远程构建目录是不够的,因为在中等信任下无法反序列化文件。与其建议人们尝试更改服务器上的信任级别或进行自定义(这很困难!),不如更改了文件格式(改为 XML),使其可以在中等信任下工作。
  2. 扩展 v4 中引入的 Document 对象层次结构,以索引 Office 2007(OpenXML)文件类型。我最近从同事那里收到一个*.docx 文件,由于我暂时不打算升级到 Office 2007,因此研究如何在不安装应用程序/IFilter 的情况下索引/搜索该文件似乎是个好主意。

ASP.NET 有“信任问题”

Searcharoo v4 在中等信任下运行时,您会遇到以下错误之一:

如果 Search.aspx 找不到目录文件并触发 SearchSpider.aspx,则会发生WebPermission 拒绝(在中等信任下默认不允许访问网站或 Web 服务)。

[SecurityException: Request for the permission of type 'System.Net.WebPermission, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089' failed.]
   System.Security.CodeAccessSecurityEngine.Check(Object demand, StackCrawlMark& stackMark, Boolean isPermSet) +0
   System.Security.CodeAccessPermission.Demand() +59
   System.Net.HttpWebRequest..ctor(Uri uri, ServicePoint servicePoint) +166
   System.Net.HttpRequestCreator.Create(Uri Uri) +26
   System.Net.WebRequest.Create(Uri requestUri, Boolean useUriBase) +373
   System.Net.WebRequest.Create(String requestUriString) +81
   Searcharoo.Indexer.RobotsTxt..ctor(Uri startPageUri, String userAgent) +250
   Searcharoo.Indexer.Spider.BuildCatalog(Uri startPageUri) +116

如果 Search.aspx 找到一个二进制序列化的目录文件并尝试反序列化它,则会发生SecurityPermission 拒绝(在中等信任下不允许二进制序列化)。

[SecurityException: Request for the permission of type 'System.Security.Permissions.SecurityPermission, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089' failed.]
   System.Runtime.Serialization.Formatters.Binary.ObjectReader.CheckSecurity(ParseRecord pr) +1644388
   System.Runtime.Serialization.Formatters.Binary.ObjectReader.ParseObject(ParseRecord pr) +363
   System.Runtime.Serialization.Formatters.Binary.ObjectReader.Parse(ParseRecord pr) +64
   System.Runtime.Serialization.Formatters.Binary.__BinaryParser.ReadObjectWithMapTyped(BinaryObjectWithMapTyped record) +1050
   System.Runtime.Serialization.Formatters.Binary.__BinaryParser.ReadObjectWithMapTyped(BinaryHeaderEnum binaryHeaderEnum) +62
   System.Runtime.Serialization.Formatters.Binary.__BinaryParser.Run() +144
   System.Runtime.Serialization.Formatters.Binary.ObjectReader.Deserialize(HeaderHandler handler, __BinaryParser serParser, Boolean fCheck, Boolean isCrossAppDomain, IMethodCallMessage methodCallMessage) +183
   System.Runtime.Serialization.Formatters.Binary.BinaryFormatter.Deserialize(Stream serializationStream, HeaderHandler handler, Boolean fCheck, Boolean isCrossAppDomain, IMethodCallMessage methodCallMessage) +190
   System.Runtime.Serialization.Formatters.Binary.BinaryFormatter.Deserialize(Stream serializationStream) +12
   Searcharoo.Common.Catalog.Load() +461

这两个错误的组合 - 无法创建新目录,也无法加载现有目录文件(即使它是在其他地方生成的)- 意味着 Searcharoo v4 在中等信任下无法工作。有两种选择可以解决这个问题:

  1. 使用自定义代码访问安全策略更新您的服务器,以允许 Searcharoo 代码执行这些功能。如果您的网站是共享托管,并且您需要说服 ISP 为您进行更改,这可能会非常困难。
  2. 修改 Searcharoo,使其至少不会出现其中一个错误。

我们将选择 #2,因为它更容易!在 v4 中,我们曾就二进制序列化为何是好主意,而 XML 序列化为何是坏主意进行了长时间的讨论:在本文中,我们将通过修复 XML 输出中的问题来扭转这一局面,以便我们可以使用 Indexer Console Application 远程构建它,然后将其上传到中等信任网站。XML 序列化数据即使在中等信任下也可以反序列化,因此可以加载和搜索。

关于选项 #2:XML 重制

原始(v4)XML 目录格式
早在 v4 中,XML 序列化的 Catalog 对象就被认为臃肿、效率低下且(如实现的那样)无法序列化。它看起来像这样:

Unusable Xml output from version 4

请记住,每个 Word 对象都包含一个 File 对象集合(Hashtable),指示该 Word 出现在哪个/哪些 File 中。这在内存中是有效的,因为 Word._FileCollection 中的 File 对象是引用 - 每个索引文件只有一个 File “对象”。

由此产生的 XML 问题在于,File 对象引用被“展平”(每次引用时都重复)。从上面的片段中可以看到,文档 https://:3359/content/Kilimanjaro.pdf 被表示了两次。对于每个 File 中的EVERY WORD,都会发生这种重复,在 Catalog 文件中产生大量冗余数据。

我们需要一种更简洁的方式来表示 WordFile 之间的关系:用数据库术语来说,就是一个“外键”。

新的(v5)XML 格式
这个“外键”将由一个新对象 CatalogWordFile 来表示,它将充当 Word 对象(我们不再序列化它们)的“代理”。Word 对象将继续作为 Catalog 的基础,但当我们通过 XML 序列化加载和保存它时,我们将使用属性来忽略 Word,并将 FileCatalogWordFile 视为两个由“外键”连接的“数据库表”:FileId

Catalog object's relationship with Word and File classes

现在 File 对象被序列化一次,并且它们的 FileId 是它们在序列化 XML 中的隐式顺序(当然,从零开始)。上面提到的内容 - https://:3359/content/Kilimanjaro.pdf - 在新的 XML 中仅出现一次,显示为 FileId=2(如下)。

File objects - serialized
New WordFile objects - serialized
在同一个 XML 文件中,各个 CatalogWordFile 对象仅引用 FileId,从而使 XML 比使用 Word 对象时小得多。

重复原始(v4)XML Catalog 示例,您可以看到上面框出的两个单词再次显示在这里,只有 FileId 而不是整个序列化的 File 对象。

请注意,显示的标记仍然包含一些完整的元素名称;在实际代码中,元素名称被覆盖以使用属性进一步减小 XML 文件大小:[XmlElement("w")][XmlElement("f")](见右侧)。

开发过程中使用的测试数据在二进制序列化时创建了一个178 KB的文件。这相当于旧格式的1.1 MB XML 文件。

使用新的、改进的 XML 格式,文件缩小到194 KB;在应用 XmlElement 属性缩短元素名称后,文件进一步缩小到97 KB - 实际上比二进制版本还要小。

XML 序列化幕后

所以这就是我们需要的 XML 格式 - 我们如何获得它?不幸的是,仅仅用 CatalogWordFile[] 替换 Word[] 并不是我们让它工作的全部;FileId 需要在 CatalogWordFileFile 数组之间保持“同步”,但我们并不知道 XmlSerializer 会以何种顺序访问属性(也不知道它们是否会被多次访问)。为了避免不必要地填充内部 CatalogWordFile 集合,我们在属性访问器中使用 pre/post 方法按需创建它。

两个属性声明如下(下方);PrepareForSerialization() 完成了将 Word 对象组成的 _Index Hashtable “展平”为带有 FileIdCatalogWordFile 代理的工作,它在两个 get 访问器中都被调用,以确保它们返回“同步”集合。

PostDeserialization() 方法会等待两个 FileWordFile set 访问器都被调用(因为我们需要两个集合才能通过我们的“外键”重建原始 _Index),然后遍历数据并调用 Add() 方法,就像 Spider 在索引时构建 Catalog 一样。

Deserialize build catalog

如果您查看 Catalog.Load() 代码,您还会注意到 XmlSerialization 使用了 Kelvin 通用序列化助手(另一个 CodeProject 文章)。

Catalog c1 = Kelvin<Catalog>.FromXmlFile(xmlFileName);

最后一点:我们并没有移除二进制序列化功能,而是保留了这两种方法,通过新的 web.config/app.config 设置(针对您的网站和 Indexer Console 应用程序)进行控制。

<appSettings>
  <add key="Searcharoo_InMediumTrust" value="True" />
</appSettings>
如果设置为 True,Catalog 将保存为 XML 文件;如果设置为 False,则保存为 *.dat 文件。不要忘记更新其他 .config 文件设置以匹配您的环境 - 包括将在本文剩余部分讨论的 DownloadDocument 类中使用的 Searcharoo_VirtualRoot, Searcharoo_CatalogFilepathSearcharoo_TempFilepath

关于信任与代码访问安全

Office 2007 文件格式

文章的其余部分讨论了索引新的 Office 2007 文件格式。

Microsoft Word Docx 文件“结构”
这篇关于 OpenXML 入门的博客讨论了如何使用Open XML 文件格式。它解释了 OpenXML 文档的基本结构:它们实际上是一系列相关的 XML(和其他)文件,'隐藏' 在一个单一的ZIP 文件中,该文件的扩展名为 Office 2007 文件扩展名,如docs, xlsx, pptx 等)。

Microsoft Word 2007 文件在 ZIP “内部”是这样的:
Docx folder/document structure
您可以阅读参考文献中关于格式的详细信息,但我们关心的关键文件是 document.xml 部分。为了搜索它,我们需要执行以下步骤:

  1. 从 Web 链接下载 OpenXML 文件/ZIP 存档
  2. 从 ZIP 存档中提取我们需要的文件
  3. 了解一些 XML 格式,以便我们可以提取我们想要索引的纯文本,并忽略所有格式和其他数据。

步骤 1:继承 Document 以共享下载代码

v4 文章介绍了 FilterDocument 如何需要下载文件以进行 IFilter 处理(而以前是将下载内容加载到 MemoryStream 中进行解析)。新的 Office 2007 类也需要相同的行为,因此 SaveDownloadedFile 方法被提升到一个它们都可以实现的基类。

DocxDocument and related classes

步骤 2:解压缩

.NET 3.0 中的System.IO.Packaging API 提供了访问 ZIP 存档的内置功能(有些人可能会说,这是专门为了促进 Office 2007/OpenXML 互操作性而设计的)。但是,为了保持 Searcharoo 的可访问性,我们暂时不升级到 3.0;幸运的是,.NET 2.0 中的 System.IO.Compression 命名空间包含了构建ZipFile 实现所需的构建块,该实现可以读取/写入 ZIP 文件(因此也可以读取/写入 OpenXML 文档)。

使用 ZipFile 访问数据流进行处理很简单:

using (ZipFile zip = ZipFile.Read(filename))
{
    using (MemoryStream streamroot = new MemoryStream())
    {   
        MemoryStream stream = new MemoryStream();
        zip.Extract(@"word/document.xml", streamroot);
        stream.Seek(0, SeekOrigin.Begin);   // important!
        // TODO: code here to process Xml from the stream
    }
}

步骤 3:提取文本

事实证明,Word 2007 的 OpenXML 格式在处理格式和内容方面非常类似于 HTML:document.xml 中的所有文档结构和格式都包含在 XML 属性中,而相关的纯文本位于每个元素的 InnerXml 中。就我们而言,我们将假设这些就是我们想要索引的所有文本(需要进一步研究以确定是否包含页眉/页脚/表格/引用,并且需要更多工作来检测和索引其他嵌入的 Office 文档)。

DocxDocument 的 3 个简单步骤

新的 Docx 文件索引器继承了大部分功能,这些功能来自抽象的 DocumentDownloadDocument 类。我们真正需要做的就是重写 GetResponse() 方法来提取文件内容,并设置 WordsOnly 属性,该属性用于生成 Catalog

DocxDocument code

这种模式可以轻松地应用于 PowerPoint 2007(.pptx 文件)和 Excel 2007(.xlsx 文件)- 请参阅 XlsxDocumentPptxDocument 代码,了解了获取这些文件类型中所有文本所需的额外工作(遍历工作表/幻灯片)。

最后,除非我们更新 DocumentFactory 以识别新的“处理”MIME 类型,以及哪个 MIME 类型/文件扩展名映射到哪个类,否则我们的新类将永远不会被实例化。

关于 Open XML Office 格式的更多信息

Erika 的博客Office 2003 和 2007:MSDN 技术文章、操作方法内容和代码示例的绝佳来源。其他链接包括:

总结

这些对 Searcharoo 的添加非常小,发布它们主要是为了帮助任何希望在中等信任下使用该代码的人。许多用户可能已安装 Office 2007(或服务器上相应的 IFilter),并且可能甚至不需要额外的 Document 子类 - 如果是这种情况,只需从 DocumentFactory 中删除新的 case 语句,然后让现有的 FilterDocument 来指导 Indexer。

历史

  • 2004-06-30:CodeProject 上的版本 1
  • 2004-07-03:CodeProject 上的版本 2
  • 2006-05-24:CodeProject 上的版本 3
  • 2007-03-18:CodeProject 上的版本 4
  • 2007-04-29:版本 5(本页)在 CodeProject 上
© . All rights reserved.