Tika解析非结构文档处理过程的简单分析
2011-05-31 10:01
501 查看
转自:http://flym.iteye.com/blog/723430
http://flym.iteye.com/blog/723558
前缀时间在使用Jackrabbit作非结构化内容的存取,当问到当存取一个word文档时,jackrabbit能不能对word文档里面的内容作全文检索呢。回去查了一下相关的文档,是可以的,而且用的是一个叫Tika的工具。
Tika原先是一个Lucene的子项目,即对内容作元数据抽取用。更多的时候,是对一个平时所用的文档类数据作信息的进一步读取,这些信息是隐藏在文档本身的。这就要求有一个统一的工具来做这些事情,而Tika就提供了一个统一的调用接口来完成这些工作。
Tika通过一个parser和一个contentHandler来进行文档分析和处理,其中parser负责解析具体的文档,当解析到需要进行处理的时候,调用contentHandler进行解析内容的处理。contentHandler(org.xml.sax)是一个用于处理sax解析的程序接口,当parser解析到某些内容时(如节点之间的信息,以及节点本身的信息)时,便会根据需要去调用contentHandler的相关处理方法。即一个负责解析,一个负责处理,两者相互协作,最终将信息通过一定的手段返回回来。
Parser是一个接口,对不同的文档进行处理时,需要寻找具体的解析器来实现具体的解析工作。Tika通过MimeType来实现对一个文档的具体识别工作,即识别出一个文档是什么样的类型,然后再根据类型寻找相应的parser。最后调用具体实现的parser来完成parse工作。
ContentHandler也是一个接口,对不同的信息进行处理时,需要自己调用具体的信息处理类。一般来说,如果只关心文档内的内容(即有信息的地方),可以使用WriteOutContentHandler。这个handler将所以被解析到的信息通过一个output或write输出到调用者提供的输出流中,这样最终可以读取这个writer中的数据。例如,ParseUtils中的getStringContent(工具方法),即通过WriterOutContentHandler进行信息的读取,最终返回这些信息。
除这些信息之外,还有一些信息需要在处理的过程中被解析和保存,比如一个文档的标题,作者以及contentType等。在tika中,这些信息被保存在一个叫Metadata的对象中。metadata保存了很多与具体文档相关的值,它以一个map的形式,保存相应的元数据信息值。如对于word,它会保存其中的Author,Keywords这些信息。这些信息对于用一些关键信息进行文档检索非常有用。
看整个处理流程,可以用下面的图来表示:
在具体实现中,用到了大量的装饰器,将一个parser包装在另一个parser中,同时也是将一个contentHandler包装在另一个handler中,所以在进行程序定位时很麻烦。但简单点来说,还是将底层进行的parser进行上层包装来进行高级的信息解析,最后返回相应的信息。
前面说了一个tika的大概处理流程,现在可以通过一个它自带的parserTest来看一下具体的走向。以下这段代码,读取某一个pdf文件的内容:
Java代码
File file = getResourceAsFile("/test-documents/testPDF.pdf");
String s1 = ParseUtils.getStringContent(file, tc);//方法1
直接使用ParseUtils.getStringContent来获取它里面的内容信息。直接进入到此方法:
Java代码
String mime = config.getMimeRepository().getMimeType(documentFile).getName();
return getStringContent(documentFile, config, mime);//方法2
第一句话,包含几个信息,首先从config中取得所有的mimeType注册器(1),再将待解析的文件传入以取得相应的 mimeType(2),最后取得mimeType的名称(3)。
1:config参数是传递过来的,即在parserTest中进行了声明,见原文,是通过TikaConfig.getDefaultConfig()方法读取了默认的mimeConfig信息。在这个方法中,会返回一个默认的TikaConfig对象(即new TikaConfig())。进入构造函数:
Java代码
ParseContext context = new ParseContext();
Iterator<Parser> iterator =
ServiceRegistry.lookupProviders(Parser.class, loader);
while (iterator.hasNext()) {
Parser parser = iterator.next();
for (MediaType type : parser.getSupportedTypes(context)) {
parsers.put(type, parser);
}
}
Types = MimeTypesFactory.create("tika-mimetypes.xml");
此代码主要做两件事情,第一个是读取在services中注册的parse的所有实现类,即读取在WETA-INF/services目录下的org.apache.tika.parser.Parser文件。此文件中以列表的方法列出了parser的实现类,即服务提供者。如下所示:
在上图中,列出了所有已经注册的Parser实现类,tika即加载这些类以用作具体的解析。
第二件事即是读取所有的mimeType信息,通过读取resource下的tika-mimeTypes.xml来解析其中每个mimeType以及对于每个mimeType中它应该拥有的信息格式,即根据一定的规则定义一个mimeType与匹配这个mimeType的规则。具体的实现可参考MimeTypesReader的readMimeType(Element element)类(此处略)。
2:第二个方法根据文件取得具体的mimeType,在这里的简单实现是根据文件的名称来取得mimeType(在实际中对于某些文件后缀名不正确的文件,应该根据文件中的数据来确定mimeType,此处简单用file.getName来进行判断)。
在做完以上工作之后,进入到下个方法:
Java代码
InputStream stream = new BufferedInputStream(new FileInputStream(documentFile));
eturn getStringContent(stream, config, mimeType);
再进入到以下方法:
Java代码
Parser parser = config.getParser(MediaType.parse(mimeType));
ContentHandler handler = new BodyContentHandler();//最初的handler
parser.parse(stream, handler, new Metadata());
return handler.toString();
这里最终会调用到具体处理各个文件的parser,这里处理pdf的类为pdfParser,进入到它的parse方法:
Java代码
PDDocument pdfDocument = PDDocument.load(stream);
metadata.set(Metadata.CONTENT_TYPE, "application/pdf");
extractMetadata(pdfDocument, metadata);
PDF2XHTML.process(pdfDocument, handler, metadata);
这里通过apache的pdfbox类来加载文件流,并设置相应的contentType,接下来将读取相应的元数据并保存在metaData中。最后调用pdf2xhtml的静态方法来处理这个pdf文档。
在extractMetadata中,tika通过PDDocumentInformation类将以下信息读取到metadata中:title,author,CREATOR,KEYWORDS,producer,SUBJECT,trapped,created,CREATION_DATE,LAST_MODIFIED以及其他通过getDictionary方法中未加入的其他信息。
PDF2XHTML类通过继承PDFTextStripper类来重写相应的方法以达到读取信息的目的,它通过调用getText(document)方法,并重写相应的扩展方法将处理的信息交由handler来处理,最终实现由handler来处理最终的信息。如下个方法所示:
Java代码
protected void writeString(String text) throws IOException {
handler.characters(text);
}
这里的handler即是最开始传入的bodyContentHandler,其默认不带参数的handler即this(new WriteOutContentHandler()[A]),最终将会调用到MatchingContentHandler[B]来处理,handler[B]处理完自己需要处理的信息(即过滤其中的某些信息)之后,将其他处理部分交由handler[A]来处理,如在characters中,writeOutContentHandler中的调用代码为:
Java代码
writer.write(ch, start, length);
writeCount += length;
即将其写入输出流中。
最后,调用handler的toString方法,输出相应信息,即调用writer.toString()返回输出的信息。
至此,程序主要调用逻辑完成。当然,在具体处理中,还会涉及到其他处理,如startElement,endElement等事件的处理,这里未主要涉及,待下来再研究。
http://flym.iteye.com/blog/723558
前缀时间在使用Jackrabbit作非结构化内容的存取,当问到当存取一个word文档时,jackrabbit能不能对word文档里面的内容作全文检索呢。回去查了一下相关的文档,是可以的,而且用的是一个叫Tika的工具。
Tika原先是一个Lucene的子项目,即对内容作元数据抽取用。更多的时候,是对一个平时所用的文档类数据作信息的进一步读取,这些信息是隐藏在文档本身的。这就要求有一个统一的工具来做这些事情,而Tika就提供了一个统一的调用接口来完成这些工作。
Tika通过一个parser和一个contentHandler来进行文档分析和处理,其中parser负责解析具体的文档,当解析到需要进行处理的时候,调用contentHandler进行解析内容的处理。contentHandler(org.xml.sax)是一个用于处理sax解析的程序接口,当parser解析到某些内容时(如节点之间的信息,以及节点本身的信息)时,便会根据需要去调用contentHandler的相关处理方法。即一个负责解析,一个负责处理,两者相互协作,最终将信息通过一定的手段返回回来。
Parser是一个接口,对不同的文档进行处理时,需要寻找具体的解析器来实现具体的解析工作。Tika通过MimeType来实现对一个文档的具体识别工作,即识别出一个文档是什么样的类型,然后再根据类型寻找相应的parser。最后调用具体实现的parser来完成parse工作。
ContentHandler也是一个接口,对不同的信息进行处理时,需要自己调用具体的信息处理类。一般来说,如果只关心文档内的内容(即有信息的地方),可以使用WriteOutContentHandler。这个handler将所以被解析到的信息通过一个output或write输出到调用者提供的输出流中,这样最终可以读取这个writer中的数据。例如,ParseUtils中的getStringContent(工具方法),即通过WriterOutContentHandler进行信息的读取,最终返回这些信息。
除这些信息之外,还有一些信息需要在处理的过程中被解析和保存,比如一个文档的标题,作者以及contentType等。在tika中,这些信息被保存在一个叫Metadata的对象中。metadata保存了很多与具体文档相关的值,它以一个map的形式,保存相应的元数据信息值。如对于word,它会保存其中的Author,Keywords这些信息。这些信息对于用一些关键信息进行文档检索非常有用。
看整个处理流程,可以用下面的图来表示:
在具体实现中,用到了大量的装饰器,将一个parser包装在另一个parser中,同时也是将一个contentHandler包装在另一个handler中,所以在进行程序定位时很麻烦。但简单点来说,还是将底层进行的parser进行上层包装来进行高级的信息解析,最后返回相应的信息。
前面说了一个tika的大概处理流程,现在可以通过一个它自带的parserTest来看一下具体的走向。以下这段代码,读取某一个pdf文件的内容:
Java代码
File file = getResourceAsFile("/test-documents/testPDF.pdf");
String s1 = ParseUtils.getStringContent(file, tc);//方法1
File file = getResourceAsFile("/test-documents/testPDF.pdf"); String s1 = ParseUtils.getStringContent(file, tc);//方法1
直接使用ParseUtils.getStringContent来获取它里面的内容信息。直接进入到此方法:
Java代码
String mime = config.getMimeRepository().getMimeType(documentFile).getName();
return getStringContent(documentFile, config, mime);//方法2
String mime = config.getMimeRepository().getMimeType(documentFile).getName(); return getStringContent(documentFile, config, mime);//方法2
第一句话,包含几个信息,首先从config中取得所有的mimeType注册器(1),再将待解析的文件传入以取得相应的 mimeType(2),最后取得mimeType的名称(3)。
1:config参数是传递过来的,即在parserTest中进行了声明,见原文,是通过TikaConfig.getDefaultConfig()方法读取了默认的mimeConfig信息。在这个方法中,会返回一个默认的TikaConfig对象(即new TikaConfig())。进入构造函数:
Java代码
ParseContext context = new ParseContext();
Iterator<Parser> iterator =
ServiceRegistry.lookupProviders(Parser.class, loader);
while (iterator.hasNext()) {
Parser parser = iterator.next();
for (MediaType type : parser.getSupportedTypes(context)) {
parsers.put(type, parser);
}
}
Types = MimeTypesFactory.create("tika-mimetypes.xml");
ParseContext context = new ParseContext(); Iterator<Parser> iterator = ServiceRegistry.lookupProviders(Parser.class, loader); while (iterator.hasNext()) { Parser parser = iterator.next(); for (MediaType type : parser.getSupportedTypes(context)) { parsers.put(type, parser); } } mimeTypes = MimeTypesFactory.create("tika-mimetypes.xml");
此代码主要做两件事情,第一个是读取在services中注册的parse的所有实现类,即读取在WETA-INF/services目录下的org.apache.tika.parser.Parser文件。此文件中以列表的方法列出了parser的实现类,即服务提供者。如下所示:
在上图中,列出了所有已经注册的Parser实现类,tika即加载这些类以用作具体的解析。
第二件事即是读取所有的mimeType信息,通过读取resource下的tika-mimeTypes.xml来解析其中每个mimeType以及对于每个mimeType中它应该拥有的信息格式,即根据一定的规则定义一个mimeType与匹配这个mimeType的规则。具体的实现可参考MimeTypesReader的readMimeType(Element element)类(此处略)。
2:第二个方法根据文件取得具体的mimeType,在这里的简单实现是根据文件的名称来取得mimeType(在实际中对于某些文件后缀名不正确的文件,应该根据文件中的数据来确定mimeType,此处简单用file.getName来进行判断)。
在做完以上工作之后,进入到下个方法:
Java代码
InputStream stream = new BufferedInputStream(new FileInputStream(documentFile));
eturn getStringContent(stream, config, mimeType);
InputStream stream = new BufferedInputStream(new FileInputStream(documentFile)); return getStringContent(stream, config, mimeType);
再进入到以下方法:
Java代码
Parser parser = config.getParser(MediaType.parse(mimeType));
ContentHandler handler = new BodyContentHandler();//最初的handler
parser.parse(stream, handler, new Metadata());
return handler.toString();
Parser parser = config.getParser(MediaType.parse(mimeType)); ContentHandler handler = new BodyContentHandler();//最初的handler parser.parse(stream, handler, new Metadata()); return handler.toString();
这里最终会调用到具体处理各个文件的parser,这里处理pdf的类为pdfParser,进入到它的parse方法:
Java代码
PDDocument pdfDocument = PDDocument.load(stream);
metadata.set(Metadata.CONTENT_TYPE, "application/pdf");
extractMetadata(pdfDocument, metadata);
PDF2XHTML.process(pdfDocument, handler, metadata);
PDDocument pdfDocument = PDDocument.load(stream); metadata.set(Metadata.CONTENT_TYPE, "application/pdf"); extractMetadata(pdfDocument, metadata); PDF2XHTML.process(pdfDocument, handler, metadata);
这里通过apache的pdfbox类来加载文件流,并设置相应的contentType,接下来将读取相应的元数据并保存在metaData中。最后调用pdf2xhtml的静态方法来处理这个pdf文档。
在extractMetadata中,tika通过PDDocumentInformation类将以下信息读取到metadata中:title,author,CREATOR,KEYWORDS,producer,SUBJECT,trapped,created,CREATION_DATE,LAST_MODIFIED以及其他通过getDictionary方法中未加入的其他信息。
PDF2XHTML类通过继承PDFTextStripper类来重写相应的方法以达到读取信息的目的,它通过调用getText(document)方法,并重写相应的扩展方法将处理的信息交由handler来处理,最终实现由handler来处理最终的信息。如下个方法所示:
Java代码
protected void writeString(String text) throws IOException {
handler.characters(text);
}
protected void writeString(String text) throws IOException { handler.characters(text); }
这里的handler即是最开始传入的bodyContentHandler,其默认不带参数的handler即this(new WriteOutContentHandler()[A]),最终将会调用到MatchingContentHandler[B]来处理,handler[B]处理完自己需要处理的信息(即过滤其中的某些信息)之后,将其他处理部分交由handler[A]来处理,如在characters中,writeOutContentHandler中的调用代码为:
Java代码
writer.write(ch, start, length);
writeCount += length;
writer.write(ch, start, length); writeCount += length;
即将其写入输出流中。
最后,调用handler的toString方法,输出相应信息,即调用writer.toString()返回输出的信息。
至此,程序主要调用逻辑完成。当然,在具体处理中,还会涉及到其他处理,如startElement,endElement等事件的处理,这里未主要涉及,待下来再研究。
相关文章推荐
- Tika解析非结构文档处理过程的简单分析
- Tika解析非结构文档处理过程的简单分析
- JSP程序运行原理、文档结构及简单输入输出实例分析
- 从LayoutInflater分析XML布局解析成View的树形结构的过程
- I/O模型和原理,以及简单解析客户端请求WEB服务器内部处理过程,MPM三种模
- jemalloc源码结构分析(一):内存申请处理过程
- Ffmpeg解析media容器过程/ ffmpeg 源代码简单分析 : av_read_frame()
- 视图之UINavigationController结构解析(侧重于文档分析)
- MTK平台图片文件解析过程简单分析
- PostgreSQL服务过程中的那些事二:Pg服务进程处理简单查询四:分析重写成querytree
- JSP程序运行原理、文档结构及简单输入输出实例分析
- 从LayoutInflater分析XML布局解析成View的树形结构的过程
- 变态的libDispatch结构分析-全局队列同步任务处理过程
- 简单分析system_call中断处理过程
- mokid 工程代码下载与结构分析与两种机理的简单解析
- 从LayoutInflater分析XML布局解析成View的树形结构的过程
- 视图之UINavigationController结构解析(侧重于文档分析)
- 从LayoutInflater分析XML布局解析成View的树形结构的过程
- 结合syslog-ng分析syslog标准协议的结构和处理过程
- 解析XML文档过程中对于未知类型的值的处理