1

我希望在我的程序中实现 apache-poi 的文本提取和稍后的摘要信息功能。也就是说,poi 对 .doc、docx、.xls 和 .xlsx 文件使用不同的库。

为了从调用 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);
    }

......还有更多的“如果”

这有效,并隐藏了实现,

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

但我讨厌所有的“如果”。我不禁认为必须有更好的,完全不同的方式来做到这一点,或者可能是一些多态的诡计......

这些私有变量是以前在本课程尝试中遗留下来的,因此请随意将它们扔到您建议的任何解决方案中。

谢谢

4

3 回答 3

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 文件,您都可以使用相同的属性和方法处理相同的 Workbook 对象。然而,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 方法进行简单更改即可。

于 2012-06-09T14:53:36.687 回答
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 还是许多其他受支持的格式之一,您都将获得纯文本内容。

于 2012-06-10T20:52:35.647 回答
1

您可以抽象 excel/word/whatever 文件和 getText 部分的加载。

为这两种方法创建一个公社接口,然后为每个 if 实现这两种方法。

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
    }
}

Doc Extractor,利用之前的实现

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();
    }
}

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

于 2012-06-08T07:18:44.943 回答