本文共 10802 字,大约阅读时间需要 36 分钟。
tika读取rtf表格
我一直在为一个客户进行数据抓取项目,并在进行一些实验后向我证明使用可以很好地完成从PDF文件中提取文本的工作。 本周,我遇到了一个的新数据源,事实证明Tika也可以处理该数据源。
受到这种愉快无缝体验的鼓舞,我决定是时候进一步了解Tika了。 我发现自己在尝试了解如何处理解码后的信息时跌跌撞撞。 Tika网站或《 》一书都没有涵盖我所需要的内容,也找不到我最喜欢的搜索引擎来解决我所要解决的问题的任何内容。
因为Tika是开源的,所以我下载了源代码以获取一些可用的示例并开始进行实验。 这是我最终完成我需要做的事情的方式-希望有人觉得它有用。
在开始之前,我应该提到Tika是用编写的,而我的示例是用编写的。
Tika的体系结构由几个组件组成。 特别有趣的是 (用于输入数据并将其转换为用于XML的简单API( )事件的序列)和接口,该接口将SAX信息传递给程序员。
从我的角度来看(作为拥有大量各种格式数据的人),Tika是那些出色的开源“平台”项目之一,该项目其他开源项目的参与者做出贡献(在这种情况下,使用解析器来解码信息),促进其共享和使用。 而且Tika处理了大量的源格式(请参阅 )。
在我的代码中使用Tika使得解码表格文件格式(例如DBF或变得容易。 但是,我找不到满足我需求的内容处理程序。 Tika提供的似乎可以分为两大类:第一类是逐字段或以文本行集的形式传递转换后的输入,其中各字段由制表符分隔。 第二个给了我一个XML / HTML文档。 这些都不符合我的需求。
例如,此LibreOffice电子表格:
保存为XLS文件,然后由处理,看起来像这样:
Sheet1 → Name → Location → Jones, Ann → London → Smith, Bob → San Francisco → Espinoza, Mercedes → Buenos Aires → Khan, Imran → Mumbai & C & "Times New Roman,Regular" & 12 & A & C & "Times New Roman,Regular" & 12Page & P
其中→表示制表符。
当它由ToXMLContentHandler处理时,其主体看起来像这样:
< body >< div class = "page" >< h1 > Sheet1 < table >< tbody >< tr > < td > Name < td > Location < tr > < td > Jones, Ann < td > London < tr > < td > Smith, Bob < td > San Francisco < tr > < td > Espinoza, Mercedes < td > Buenos Aires < tr > < td > Khan, Imran < td > Mumbai < div class = "outside" >& amp;C & amp; "Times New Roman,Regular" & amp; 12 & amp;A < div class = "outside" >& amp;C & amp; "Times New Roman,Regular" & amp;12Page & amp;P
这些都不是理想的。 我不喜欢将制表符用作分隔符(如果我的一个字段包含制表符怎么办?),也找不到将该字符设置为其他任何字符的方法。 至于XML / HTML,在文本上调用XML解析器只是为了将其分开似乎很奇怪。
当我考虑这个问题并寻找定制内容处理程序的示例时,我注意到了两个关键的事情:首先,特别是内容处理程序和SAX通常与Tika分开,因此我开始在其他地方寻找有关它们的信息; 其次,Tika为我提供了一个完美的自定义起点(对我来说太夸张了): 。 正如ContentHandlerDecorator API页面所指出的,“子类可以通过重写一个或多个SAX事件方法来提供额外的装饰。” (有关装饰器的更多信息,请阅读Wikipedia上 。)
可以使用ContentHandlerDecorator给我一个很好的方法,将我的SAX事件序列转换为所需的东西,而又不需要很多多余的假装吗?
基本上,我想要一种生成哈希表列表的方法,其中每个哈希表对应于表格数据中的一行,其键设置为列名,值设置为适当的行和列中的单元格内容。 在Groovy中,以这种方式呈现的电子表格将声明为:
def data = [ [ Name: 'Jones, Ann' , Location: 'London' ] , [ Name: 'Smith, Bob' , Location: 'San Francisco' ] , [ Name: 'Espinoza, Mercedes' , Location: 'Buenos Aires' ] , [ Name: 'Khan, Imran' , Location: 'Mumbai' ] ]
我阅读了Tika各种内容处理程序的源代码,并了解到可以通过重写ContentHandlerDecorator中的三个方法来实现我的目标:
我需要实现这些方法以:
简单! 让我们写一些代码!
首先,在Groovy中,我创建了一个ContentHandlerDecorator ,如下所示:
def handler = new ContentHandlerDecorator ( ) { // we know we need a row counter and a list of row maps int rowCount = 0 def rowMapList = [ ] // we also know we need a list of column names and row values def columnNameList = [ ] def rowValueList // we know we want to give the user access to rowMapList [ ] public def getRowMapList ( ) { rowMapList } // we may as well offer a String version of rowMapList [ ] @ Override public String toString ( ) { rowMapList.toString ( ) } // rest of the code goes in here }
接下来,我需要保存感兴趣的文本:
boolean inDataElement = false StringBuffer dataElement @ Override public void characters ( char [ ] ch, int start, int length ) { if ( inDataElement ) dataElement.append ( ch, start, length ) }
我使用了StringBuffer ,它本质上是一个可变字符串。 它似乎与character()的参数非常匹配。 该代码遵循上一个代码块中的注释“其余的代码在这里”。
接下来,我需要处理数据元素和行的结尾:
@ Override void endElement ( String uri, String localName, String name ) { switch ( name ) { case 'td' : inDataElement = false if ( rowCount == 0 ) columnNameList.add ( dataElement.toString ( ) ) else rowValueList.add ( dataElement.toString ( ) ) break case 'tr' : if ( rowCount > 0 ) rowMapList. add ( [ columnNameList, rowValueList ] . transpose ( ) . collectEntries { e - > [ ( e [ 0 ] ) : ( e [ 1 ] .trim ( ) ) ] } ) rowCount++ break default: break } }
该代码段在上一个代码段之后。 它包含了很好的课程,以及一些不错的Groovy知识,因此值得详细介绍一下:
最后,我将其设置为处理数据元素和行的开头:
@ Override void startElement ( String uri, String localName, String name, org.xml.sax.Attributes atts ) { switch ( name ) { case 'tr' : if ( rowCount > 0 ) rowValueList = [ ] break case 'td' : inDataElement = true dataElement = new StringBuffer ( ) break default: break } }
这会寻找<TR>来(重新)初始化行值列表,并且寻找<TD>注意它正在处理有用的数据元素,是时候将传递给帮助器的字符保存在新的字符串缓冲区中了。 再一次,此代码紧跟先前的代码。
而已! 要使用此代码,请运行以下命令:
import org.apache.tika. * import org.apache.tika.parser. * import org.apache.tika.metadata. * import org.apache.tika.sax. * // define the file we’re reading and get an input stream // based on that file def file = new File ( 'test.xls' ) def fis = new FileInputStream ( file ) // define the metadata and parser def metadata = new Metadata ( ) def parser = new AutoDetectParser ( ) // define the content handler ( copy the code created above ) def handler = new ContentHandlerDecorator ( ) { // … } ; // parse the input file parser.parse ( fis, handler, metadata ) // here we visit all the elements in the row map list // and print out each row map, one per line handler.rowMapList.each { map - > println map } fis.close ( )
获得此输出:
[ Name:Jones, Ann, Location:London ] [ Name:Smith, Bob, Location:San Francisco ] [ Name:Espinoza, Mercedes, Location:Buenos Aires ] [ Name:Khan, Imran, Location:Mumbai ]
正是医生命令的!
因此,现在可以代替handler.rowMapList.each {map->…}闭包来研究每个单元格的值。 例如:
if ( map.Name == ‘Smith, Bob’ ) map.Location = ‘Nairobi’
当我想将鲍勃·史密斯从伦敦搬到内罗毕时,或
new Employee ( name: map.Name, location: map.Location ) .save ( )
当我想将数据插入我的Grails数据库中时。
正如我所发现的,Tika Parser非常适合将事情分开,但是ContentHandler接口是以有用的方式将其重新组合的关键。
翻译自:
tika读取rtf表格
转载地址:http://zfnzd.baihongyu.com/