2012-06-08 37 views
1

我期待实现Apache的POI的文本提取,后来,在我的计划概要信息的功能。也就是说,poi为.doc,docx,.xls和.xlsx文件使用不同的库。如何抽象POI功能

婉婷隐藏这从调用POI功能的复杂性,我做了下面的类和方法,我可以打电话从任何文档4种提取文本:

public class DocExtractor { 
private WordExtractor w = null; 
private XWPFWordExtractor wx = null; 
private ExcelExtractor x = null; 
private XSSFExcelExtractor xx = null; 

public DocExtractor(File f){ 
     String fileExtension = FilenameUtils.getExtension(f.toString()); 
     if (fileExtension.equals("doc")){ 
      try{ 
       FileInputStream is = new FileInputStream(f.getAbsolutePath()); 
       HWPFDocument doc = new HWPFDocument(is); 
       w = new WordExtractor(doc); 
      } 
      catch (Exception e){e.printStackTrace();} 
     } 

... 3个 'IFS' 在构造函数中

和方法:

public String getText(){ 
    String text =""; 
    if(this.w != null){ 
     String[] texted = w.getParagraphText(); //for .doc 
     text = this.joiner(texted); 
    } 

...但更多的 'IFS'

这工作,并隐藏了实现,

DocExtractor dm = new DocExtractor(doFile); 
    text = dm.getText(); 

但我讨厌所有的 'IFS'。 我不禁想到必须有一个更好的,完全不同的方式来做到这一点,或者可能是一些多态的欺骗......

这些私有变量是从这个类的以前的尝试遗留下来的,所以感觉自由把它们扔在你建议的任何东西中。

感谢

+0

为什么不使用[Apache Tika](http://tika.apache.org/)? Tika使用POI从办公室文件中提取文本,但是以一种常用的方式完成所有文本 – Gagravarr

回答

2

这是一个非常常见的Java编程问题的例子。它通常使用所谓的Java工厂设计模式来解决。下面的链接对工厂模式有一个很好的简单解释 - http://www.allapplabs.com/java_design_patterns/factory_pattern.htm

还有很多其他的设计模式,您可能会发现它很有用。阅读这些内容可以让您深入了解大量Java程序员如何解决常见问题。同一作者在http://www.allapplabs.com/java_design_patterns/java_design_patterns.htm

解释了大部分常见设计模式,现在就您的具体问题。首先,POI作者使用工厂设计模式。例如,看一下下面的代码:

Workbook wb1 = WorkbookFactory.create(new FileInputStream("myXlsFile.xls")); 
Workbook wb2 = WorkbookFactory.create(new FileInputStream("myXlsxFile.xlsx")); 
// this prints "wb1 class = org.apache.poi.xssf.usermodel.XSSFWorkbook" 
System.out.println("wb1 class = " + wb1.getClass().getName()); 
// this prints "wb2 class = org.apache.poi.hssf.usermodel.HSSFWorkbook" 
System.out.println("wb2 class = " + wb2.getClass().getName()); 

所以,作为POI的用户,你处理具有相同的属性和方法相同的工作簿对象无论你正在处理XLS文件或XLSX文件。然而,根据文件类型的不同,POI的作者显然需要两种截然不同的实现。

他们是如何做到这一点,而没有太多的if语句,比如你的代码中有什么?我会重做你的榜样,告诉你如何完成同样的事情。

你会做的第一件事是定义一个类DocExtractor如下:

public abstract class DocExtractor { 

    // constructor 
    public DocExtractor(File f) { 
     poiFile = f; 
    } 

    // the getText method must be defined by all derived classes 
    public abstract String getText(); 

    // this protected field is visible to all classes which extend DocExtractor 
    protected File poiFile; 

} 

的原因,我建议你做DocExtractor抽象的原因是你不想代码能够创建一个DocExtractor类。你使getText方法抽象的原因是你想确保扩展DocExtactor的类将定义它们自己的getText版本。希望这个推理在您阅读时会变得清晰。

您现在可以定义DocExtractor的派生类(它们“扩展”DocExtractor)。在这个例子中,我将定义两个类,一个用于doc文件,一个用于xls文件。

// this handles doc files 
public class DocExtractorDoc extends DocExtractor { 

    // constructor 
    public class DocExtractorDoc(File f) { 
     // this calls the DocExtractor constructor which has common code for all constructors 
     super(f); 
     // put code specific to the DocExtractorDoc constructor here 
    } 

    // concrete implementation of the getText method specific to doc files 
    public String getText() { 
     // getText code for doc files goes here 
    } 
} 

// this handles xls files 
public class DocExtractorXls extends DocExtractor { 

    // constructor 
    public class DocExtractorXls(File f) { 
     // this calls the DocExtractor constructor which has common code for all constructors 
     super(f); 
     // put code specific to the DocExtractorXls constructor here 
    } 

    // concrete implementation of the getText method specific to xls files 
    public String getText() { 
     // getText code for xls files goes here 
    } 
} 

现在定义有一个静态一个DocExtractorFactory类中创建方法:

public class DocExtractorFactory { 

    public static DocExtractor create(File f) { 
     // create the appropriate DocExtractor derived class based on the file extension 
     String extension = FilenameUtils.getExtension(f.getName()); 
     if (extension.equals("doc") { 
      return new DocExtractorDoc(f); 
     } else if (extension.equals("xls") { 
      return new DocExtractorXls(f); 
     } else { 
      // error handling code here -- perhaps throw an exception 
     } 
    } 
} 

最后,这里是它使用上述类

// this actually creates a DocExtractorDoc object (but you don't care) 
DocExtractor de1 = DocExtractorFactory.create(new File("myDocFile.doc")); 
// this actually uses DocExtractorDoc.getText (but again you don't care) 
String s1 = de1.getText(); 
// this actually creates a DocExtractorXls object 
DocExtractor de2 = DocExtractorFactory.create(new File("myDocFile.xls")); 
// this actually uses DocExtractorXls.getText 
String s2 = de2.getText(); 

所以一些代码,我们有什么基本完成的只有一个地方有if语句,工厂创建方法。您可以根据需要创建尽可能多的DocExtractor派生类,只需编写该类的代码并对create方法进行简单更改即可。

+0

非常清晰且易于遵循的答案。我也会研究这些链接。非常感谢。 – grooble

+0

所以,看起来Apache的设置与他们的ExtractorFactory: (http://poi.apache.org/apidocs/org/apache/poi/extractor/ExtractorFactory。html) ......这正是我想要的。尽管如此,这是一次很好的学习经历。 – grooble

1

你可以抽象的Excel /字/任何文件和gettext的部件的负荷。

创造了这两种方法公社界面,然后执行为每个IFS的这两种方法。

interface Extractor { 
    public void setInputStream(FileInputStream fis); 
    public String getText(); 
} 

实施的Word

class ConcreteWordExtractor implements Extractor { 
    private WordExtractor w; 

    public void setInputStream(FileInputStream fis) { 
     HWPFDocument doc = new HWPFDocument(fis); 
     this.w = new WordExtractor(doc); 
    } 

    public String getText() { 
     String[] texted = this.w.getParagraphText(); 
     // rest of your logic for word 
    } 
} 

实施为Excel

class ConcreteExcelExtractor implements Extractor { 
    private ExcelExtractor x; 

    public void setInputStream(FileInputStream fis) { 
     // load the Excel workbook from input stream 
     this.x = new ExcelExtractor(...); 
    } 

    public String getText() { 
     // your logic for Excel 
    } 
} 

的文件提取,利用先前实施的

public class DocExtractor { 
    private final Extractor extractor; 
    // you could use spring or any injector to create this and avoit it being in your code 
    private final Map<String, Extractor> extractors = new HashMap<String, Extractor>() {{ 
      put("doc", new ConcreteWordExtractor()); 
      put("xls", new ConcreteExcelExtractor()); 
    }}; 

    public DocExtractor(File f) { 
     String extension = FilenameUtils.getExtension(f.getName()); 
     if (!this.extractors.containsKey(extension)) 
      throw new IllegalArgumentException("No such extractor for extension `" + extension + "`."); 
     this.extractor = this.extractors.get(extension); 
     try { 
      FileInputStream fis = new FileInputStream(f); 
      extractor.setInputStream(fis); 
     } catch (Exception e) { 
      // do what you want 
     } 
    } 

    public String getText() { 
     return extractor.getText(); 
    } 
} 

这样你抽象的第l为每种格式和文本检索部分提供文件,当您需要支持新格式时,您必须实现Extractor接口并将其添加到地图中,或者如评论中所建议的,您可以使用任何依赖注入库/框架(如Spring)从代码中提取。

2

如果您想支持各种Office文件格式的文本提取,那么最好不要在前面编写自己的包装,而应该使用Apache Tika。 Apache Tika是一个文本和元数据提取工具包/库/ thingy。

要从Microsoft Office文件中提取文本,Tika调用Apache POI以完成实际工作。但是,它可以在内部完成,并为您隐藏不同格式的复杂性。相反,你所做的只是把它交给一个文件,Tika计算出它是什么,要调用哪个库,执行文本提取,并将文本提供给你。

使用Apache Tika时,可以选择纯文本或HTML。假设你想纯文本(因为这是所有低级别POI提取提供),你想要的东西,如:

Tika tika = new Tika(); 
Metadata metadata = new Metadata(); 
metadata.set(Metadata.RESOURCE_NAME_KEY, "myfile.name"); 
String text = tika.parseToString(new File("myfile.name")); 

就是这样。无论您是否拥有.xls,.ppt或甚至是其中一个many other supported formats,都会收回纯文本内容。

+0

我喜欢上面的答案,但我最终可能会与Tika一起进行多功能性。感谢您的高举。 – grooble