返回

PDF提取:格式化文本转HTML实战

Android

PDF 内容提取:格式化与 HTML 输出

PDF 文件因其跨平台兼容性和固定布局而成为文档共享的标准。不过,当需要提取 PDF 中的文本内容,并保留其原有的格式或者转化为 HTML 格式时,问题就来了。 这往往成为开发中的一个挑战。纯文本提取会丢失许多关键的信息,例如段落结构、列表、图片、以及标题等元素,无法满足我们后续的操作。下面分析几种实现 PDF 文本格式化提取的方案。

方案一: 基于 PDFBox 的定制文本提取器

Apache PDFBox 提供了解析 PDF 文件结构的基础能力,但是默认的 PDFTextStripper 只提取纯文本。我们需要继承这个类,重写其方法来定制输出。 这样做能够使我们有机会处理每一个文本元素及其属性,实现一定的格式化能力。

工作原理:

这种方法依赖于 PDFBox 提供的底层文本提取逻辑。通过重写 writeString()processTextPosition() 方法,我们可以捕获每个字符和其所在的位置。根据位置和字体等信息生成 HTML 标签。
这涉及到一些逻辑,诸如检测换行、段落开始与结束,粗体,斜体等样式。这是一种更加控制粒度的方式,但开发起来也会稍微复杂一点。

代码示例与步骤:

在前面的示例中,你尝试使用了 PDFToHTMLConverterNullWriter 类,但是没有得到正确的 Arabic 文本格式化输出。主要问题是代码将文本简单地翻转,且没有正确的考虑 HTML 结构,对阿拉伯文本的处理也是不恰当的。可以改进这个类。需要创建一个能保留换行、段落和简单样式处理的版本, 比如粗体、斜体,处理阿拉伯字符的展示顺序和方向。
需要确保 pdfbox-android 库已添加到你的 Android 项目中。

改进后的 PDFToHTMLConverter

import com.tom_roush.pdfbox.pdmodel.PDDocument;
import com.tom_roush.pdfbox.text.PDFTextStripper;
import com.tom_roush.pdfbox.text.TextPosition;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;


public class PDFToHTMLConverter extends PDFTextStripper {

    private StringBuilder html;
    private float lastY = -1f;
    private float lineStart;

     public PDFToHTMLConverter() throws IOException {
        super();
         this.html = new StringBuilder();
        lineStart = -1;

    }

    @Override
    protected void startDocument(PDDocument document) {
        html.append("<html><body>");
    }


    @Override
    protected void endDocument(PDDocument document) {
        if (lineStart != -1) {
          html.append("</p>");
      }
      html.append("</body></html>");
    }
    @Override
     protected void writeString(String text, List<TextPosition> textPositions) throws IOException {
        if (textPositions.isEmpty()) {
            return;
        }
        TextPosition firstPos = textPositions.get(0);
          if(Math.abs(firstPos.getY() - lastY)>0.05){
            if (lineStart != -1) {
               html.append("</p>");
            }
               html.append("<p>");
             lastY = firstPos.getY();
        }


          html.append(escapeHTML(text));

    }

        // 处理特殊字符
       private String escapeHTML(String text) {
           return text.replace("&", "&amp;")
                .replace("<", "&lt;")
               .replace(">", "&gt;")
                .replace("\"", "&quot;")
                .replace("'", "&#39;");
        }



     public String getHTMLText(File pdfFile) throws IOException {
        try (PDDocument document = PDDocument.load(pdfFile)) {
            this.writeText(document, new NullWriter());
        }
         String output =  html.toString();
          // 移除所有结尾的空行
            output = output.replaceAll("(<p></p>)+
import com.tom_roush.pdfbox.pdmodel.PDDocument;
import com.tom_roush.pdfbox.text.PDFTextStripper;
import com.tom_roush.pdfbox.text.TextPosition;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;


public class PDFToHTMLConverter extends PDFTextStripper {

    private StringBuilder html;
    private float lastY = -1f;
    private float lineStart;

     public PDFToHTMLConverter() throws IOException {
        super();
         this.html = new StringBuilder();
        lineStart = -1;

    }

    @Override
    protected void startDocument(PDDocument document) {
        html.append("<html><body>");
    }


    @Override
    protected void endDocument(PDDocument document) {
        if (lineStart != -1) {
          html.append("</p>");
      }
      html.append("</body></html>");
    }
    @Override
     protected void writeString(String text, List<TextPosition> textPositions) throws IOException {
        if (textPositions.isEmpty()) {
            return;
        }
        TextPosition firstPos = textPositions.get(0);
          if(Math.abs(firstPos.getY() - lastY)>0.05){
            if (lineStart != -1) {
               html.append("</p>");
            }
               html.append("<p>");
             lastY = firstPos.getY();
        }


          html.append(escapeHTML(text));

    }

        // 处理特殊字符
       private String escapeHTML(String text) {
           return text.replace("&", "&amp;")
                .replace("<", "&lt;")
               .replace(">", "&gt;")
                .replace("\"", "&quot;")
                .replace("'", "&#39;");
        }



     public String getHTMLText(File pdfFile) throws IOException {
        try (PDDocument document = PDDocument.load(pdfFile)) {
            this.writeText(document, new NullWriter());
        }
         String output =  html.toString();
          // 移除所有结尾的空行
            output = output.replaceAll("(<p></p>)+$", "");

             output = output.trim(); // 移除首尾的空白字符
       return output;
     }


}


quot;
, ""); output = output.trim(); // 移除首尾的空白字符 return output; } }

需要创建一个NullWriter

import java.io.Writer;
import java.io.IOException;

public class NullWriter extends Writer {
    @Override
    public void write(char[] cbuf, int off, int len) throws IOException {
        // Do nothing
    }

    @Override
    public void flush() throws IOException {
        // Do nothing
    }

    @Override
    public void close() throws IOException {
        // Do nothing
    }
}

使用这个类:

       try {
            PDFToHTMLConverter converter = new PDFToHTMLConverter();
            File pdfFile = new File("path/to/your/pdf.pdf"); // 替换为你的PDF文件路径
             String htmlContent = converter.getHTMLText(pdfFile);
            // 使用 htmlContent 处理你想要的操作, 例如设置 webview 或者进一步解析
          Log.d("HTML output", htmlContent);
       } catch (IOException e) {
           e.printStackTrace();
       }

注意,这里的改进版本添加了对换行的基本判断,将同一行内容放置在一个 <p> 标签中,简单的HTML 转义。处理阿拉伯文本需要更深入的支持,需要解析字符的方向和组合。 这仍然只是一个基础实现,如果要完全呈现复杂 PDF 文档,比如处理各种复杂样式、图片,则需要在文本分析的逻辑上,下更多的功夫,才能生成尽可能符合要求的HTML代码。

安全性: 在解析 PDF 内容时,注意文件来源。从不可信来源加载PDF 文件可能会带来安全风险,要小心处理。在写入数据或执行与 PDF 数据相关操作前,执行有效的验证,或者使用可信任的库解析内容。

方案二: 使用第三方 HTML 生成库

许多第三方库提供了更高级的 PDF 到 HTML 的转换功能,例如 PdfPig, PDFTron SDK。它们通常能够更好地处理复杂的布局,保留图片,提供更高的准确性和输出质量。

工作原理:

这些库使用不同的算法解析 PDF 文件结构,能够生成更加精准的 HTML 文件,包含更丰富的格式和布局信息。一些库还允许你自定义转换过程和样式。

代码示例与步骤:

由于不同的第三方库差异比较大,这里提供使用 pdfTron SDK的步骤(你需要拥有授权和环境):

  1. 添加 PDFTron SDK 到项目中: 你需要从他们的官网下载 Android 版本的 SDK,添加到你的 Android 项目中。

  2. 代码:

   import com.pdftron.pdf.Convert;
    import com.pdftron.pdf.PDFDoc;
     import java.io.File;

      public class PdfTronHelper {

           public static String convertPDFtoHTML(String inputPath, String outputPath) {
          try (PDFDoc doc = new PDFDoc(inputPath)) {

              Convert.ConversionResult result = Convert.toHtml(doc, outputPath);

                 if (result == Convert.ConversionResult.e_success) {
                       return outputPath; // 返回输出 html 文件的路径

                  } else {
                          // 输出失败 log 信息
                         return ""; //返回失败标志,具体原因查看日志。
               }


          } catch (Exception ex) {
           // 处理异常,包括异常的错误输出等。
             ex.printStackTrace();

            return "";

          }
          }

           // 调用该方法转换
             String htmlPath = PdfTronHelper.convertPDFtoHTML("/path/to/input.pdf", "/path/to/output.html");

         }
   ```

你需要注册 pdftron SDK ,并通过 api 文档去了解更高级的功能使用,这会比较耗时。需要依据项目的情况,选择不同的 SDK 产品。

### 总结

不同的方法各有优缺点。如果只需要基本的文本提取和简单的格式保留,可以考虑定制的PDFBox 解析。如果要处理复杂的布局、高质量的转换结果,并且对输出质量要求较高,推荐使用成熟的商业库或者第三方库。 对于复杂的项目,结合两者的优势,或者根据需求选择合适的工具,才能更好地完成 PDF 内容提取和处理。

希望这些方案对你有帮助。