博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
tika读取rtf表格_使用Tika在Groovy中管理表格数据
阅读量:2527 次
发布时间:2019-05-11

本文共 10802 字,大约阅读时间需要 36 分钟。

tika读取rtf表格

我一直在为一个客户进行数据抓取项目,并在进行一些实验后向我证明使用可以很好地完成从PDF文件中提取文本的工作。 本周,我遇到了一个的新数据源,事实证明Tika也可以处理该数据源。

受到这种愉快无缝体验的鼓舞,我决定是时候进一步了解Tika了。 我发现自己在尝试了解如何处理解码后的信息时跌跌撞撞。 Tika网站或《 》一书都没有涵盖我所需要的内容,也找不到我最喜欢的搜索引擎来解决我所要解决的问题的任何内容。

因为Tika是开源的,所以我下载了源代码以获取一些可用的示例并开始进行实验。 这是我最终完成我需要做的事情的方式-希望有人觉得它有用。

在开始之前,我应该提到Tika是用编写的,而我的示例是用编写的。

问题

Tika的体系结构由几个组件组成。 特别有趣的是 (用于输入数据并将其转换为用于XML的简单API( )事件的序列)和接口,该接口将SAX信息传递给程序员。

从我的角度来看(作为拥有大量各种格式数据的人),Tika是那些出色的开源“平台”项目之一,该项目其他开源项目的参与者做出贡献(在这种情况下,使用解析器来解码信息),促进其共享和使用。 而且Tika处理了大量的源格式(请参阅 )。

在我的代码中使用Tika使得解码表格文件格式(例如DBF或变得容易。 但是,我找不到满足我需求的内容处理程序。 Tika提供的似乎可以分为两大类:第一类是逐字段或以文本行集的形式传递转换后的输入,其中各字段由制表符分隔。 第二个给了我一个XML / HTML文档。 这些都不符合我的需求。

例如,此LibreOffice电子表格:

Spreadsheet with names and locations

保存为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中的三个方法来实现我的目标:

  • 对于每个HTML元素(例如<TABLE><TR><TD>) ,将在每个文档元素的开始处调用startElement()方法;
  • 所述的endElement()方法将被称为对于每个HTML元素,例如</ TABLE> </ TR>,</ TD>,在这些文档中的元素中的每一个的端部; 和
  • 每当需要写出文本时,就会调用character()方法。

我需要实现这些方法以:

  • 决定何时保存发送到character()的文本;
  • 使用startElement()监视<TD>并打开文本保存;
  • 使用endElement()监视</ TD>并关闭文本保存;
  • 使用startElement()监视<TR>并创建一个空列表以保存行数据;
  • 使用endElement()监视</ TR>并将行数据列表转换为映射并将该映射添加到行映射列表; 而且,哦,是的,
  • 使用endElement()将第一行视为标题而不是数据,并将其保存到标题列表而不是行数据。

简单! 让我们写一些代码!

首先,在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知识,因此值得详细介绍一下:

  • 在测试SAX事件序列的工作方式时,我确定我只需要查看start和end元素的name参数。
  • 我正在使用Groovy的功能来在字符串值上切换(){…}以拾取</ TR></ TD>元素。
  • 当我遇到</ TD>时 ,我知道刚刚处理的数据元素如果在第一条记录上,将进入列名列表,否则将进入行值列表。
  • 遇到</ TR>时 ,我知道如果我位于第二行或后续行中,则需要将列名列表和行值列表转换为行映射。
    • 我使用transpose()在列表中插入列名/行值对。
    • 然后,我使用collectEntries()将成对的列名和行值转换为映射条目。
  • 而且我记得要更新行数!

最后,我将其设置为处理数据元素和行的开头:

         
@ 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/

你可能感兴趣的文章
阶段3 3.SpringMVC·_03.SpringMVC常用注解_1 RequestParam注解
查看>>
阶段3 3.SpringMVC·_02.参数绑定及自定义类型转换_7 获取Servlet原生的API
查看>>
阶段3 3.SpringMVC·_03.SpringMVC常用注解_2 RequestBody注解
查看>>
阶段3 3.SpringMVC·_03.SpringMVC常用注解_3 PathVariable注解
查看>>
阶段3 3.SpringMVC·_03.SpringMVC常用注解_4 HiddentHttpMethodFilter过滤器
查看>>
阶段3 3.SpringMVC·_03.SpringMVC常用注解_6 CookieValue注解
查看>>
阶段3 3.SpringMVC·_03.SpringMVC常用注解_5 RequestHeader注解
查看>>
阶段3 3.SpringMVC·_03.SpringMVC常用注解_7 ModelAttribute注解
查看>>
阶段3 3.SpringMVC·_04.SpringMVC返回值类型及响应数据类型_1 搭建环境
查看>>
阶段3 3.SpringMVC·_03.SpringMVC常用注解_8 SessionAttributes注解
查看>>
阶段3 3.SpringMVC·_04.SpringMVC返回值类型及响应数据类型_3 响应之返回值是void类型...
查看>>
阶段3 3.SpringMVC·_04.SpringMVC返回值类型及响应数据类型_2 响应之返回值是String类型...
查看>>
阶段3 3.SpringMVC·_04.SpringMVC返回值类型及响应数据类型_4 响应之返回值是ModelAndView类型...
查看>>
阶段3 3.SpringMVC·_01.SpringMVC概述及入门案例_01.SpringMVC概述及入门案例
查看>>
阶段3 3.SpringMVC·_04.SpringMVC返回值类型及响应数据类型_6 响应json数据之过滤静态资源...
查看>>
阶段3 3.SpringMVC·_04.SpringMVC返回值类型及响应数据类型_5 响应之使用forward和redirect进行页面跳转...
查看>>
阶段3 3.SpringMVC·_04.SpringMVC返回值类型及响应数据类型_8 响应json数据之响应json格式数据...
查看>>
阶段3 3.SpringMVC·_04.SpringMVC返回值类型及响应数据类型_7 响应json数据之发送ajax的请求...
查看>>
阶段3 3.SpringMVC·_05.文件上传_2 文件上传之传统方式上传代码回顾
查看>>
阶段3 3.SpringMVC·_05.文件上传_1 文件上传之上传原理分析和搭建环境
查看>>