Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
org.apache.tika.parser.pdf.AbstractPDF2XHTML Maven / Gradle / Ivy
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.tika.parser.pdf;
import static org.apache.tika.parser.pdf.PDFParserConfig.OCR_STRATEGY.NO_OCR;
import javax.xml.stream.XMLStreamException;
import java.awt.image.BufferedImage;
import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
import java.util.ListIterator;
import java.util.Locale;
import java.util.Map;
import java.util.TreeMap;
import org.apache.commons.io.IOExceptionWithCause;
import org.apache.commons.io.IOUtils;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDDocumentCatalog;
import org.apache.pdfbox.pdmodel.PDDocumentNameDictionary;
import org.apache.pdfbox.pdmodel.PDEmbeddedFilesNameTreeNode;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.common.PDDestinationOrAction;
import org.apache.pdfbox.pdmodel.common.PDNameTreeNode;
import org.apache.pdfbox.pdmodel.common.filespecification.PDComplexFileSpecification;
import org.apache.pdfbox.pdmodel.common.filespecification.PDEmbeddedFile;
import org.apache.pdfbox.pdmodel.common.filespecification.PDFileSpecification;
import org.apache.pdfbox.pdmodel.common.filespecification.PDSimpleFileSpecification;
import org.apache.pdfbox.pdmodel.interactive.action.PDAction;
import org.apache.pdfbox.pdmodel.interactive.action.PDActionImportData;
import org.apache.pdfbox.pdmodel.interactive.action.PDActionJavaScript;
import org.apache.pdfbox.pdmodel.interactive.action.PDActionLaunch;
import org.apache.pdfbox.pdmodel.interactive.action.PDActionRemoteGoTo;
import org.apache.pdfbox.pdmodel.interactive.action.PDActionURI;
import org.apache.pdfbox.pdmodel.interactive.action.PDAnnotationAdditionalActions;
import org.apache.pdfbox.pdmodel.interactive.action.PDDocumentCatalogAdditionalActions;
import org.apache.pdfbox.pdmodel.interactive.action.PDFormFieldAdditionalActions;
import org.apache.pdfbox.pdmodel.interactive.action.PDPageAdditionalActions;
import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotation;
import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationFileAttachment;
import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationMarkup;
import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationWidget;
import org.apache.pdfbox.pdmodel.interactive.digitalsignature.PDSignature;
import org.apache.pdfbox.pdmodel.interactive.documentnavigation.outline.PDDocumentOutline;
import org.apache.pdfbox.pdmodel.interactive.documentnavigation.outline.PDOutlineItem;
import org.apache.pdfbox.pdmodel.interactive.documentnavigation.outline.PDOutlineNode;
import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm;
import org.apache.pdfbox.pdmodel.interactive.form.PDField;
import org.apache.pdfbox.pdmodel.interactive.form.PDNonTerminalField;
import org.apache.pdfbox.pdmodel.interactive.form.PDSignatureField;
import org.apache.pdfbox.pdmodel.interactive.form.PDXFAResource;
import org.apache.pdfbox.rendering.PDFRenderer;
import org.apache.pdfbox.text.PDFTextStripper;
import org.apache.pdfbox.tools.imageio.ImageIOUtil;
import org.apache.tika.exception.TikaException;
import org.apache.tika.extractor.EmbeddedDocumentExtractor;
import org.apache.tika.extractor.EmbeddedDocumentUtil;
import org.apache.tika.io.TemporaryResources;
import org.apache.tika.io.TikaInputStream;
import org.apache.tika.metadata.Metadata;
import org.apache.tika.metadata.PDF;
import org.apache.tika.metadata.TikaCoreProperties;
import org.apache.tika.parser.ParseContext;
import org.apache.tika.parser.ocr.TesseractOCRConfig;
import org.apache.tika.parser.ocr.TesseractOCRParser;
import org.apache.tika.sax.EmbeddedContentHandler;
import org.apache.tika.sax.XHTMLContentHandler;
import org.xml.sax.ContentHandler;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.AttributesImpl;
class AbstractPDF2XHTML extends PDFTextStripper {
enum ActionTrigger {
AFTER_DOCUMENT_PRINT,
AFTER_DOCUMENT_SAVE,
ANNOTATION_CURSOR_ENTERS,
ANNOTATION_CURSOR_EXIT,
ANNOTATION_LOSE_INPUT_FOCUS,
ANNOTATION_MOUSE_CLICK,
ANNOTATION_MOUSE_RELEASED,
ANNOTATION_PAGE_CLOSED,
ANNOTATION_PAGE_NO_LONGER_VISIBLE,
ANNOTATION_PAGE_OPENED,
ANNOTATION_PAGE_VISIBLE,
ANNOTATION_RECEIVES_FOCUS,
ANNOTATION_WIDGET,
BEFORE_DOCUMENT_CLOSE,
BEFORE_DOCUMENT_PRINT,
BEFORE_DOCUMENT_SAVE,
DOCUMENT_OPEN,
FORM_FIELD,
FORM_FIELD_FORMATTED,
FORM_FIELD_KEYSTROKE,
FORM_FIELD_RECALCULATE,
FORM_FIELD_VALUE_CHANGE,
PAGE_CLOSE,
PAGE_OPEN, BOOKMARK,
};
/**
* Maximum recursive depth during AcroForm processing.
* Prevents theoretical AcroForm recursion bomb.
*/
private final static int MAX_ACROFORM_RECURSIONS = 10;
private final static TesseractOCRConfig DEFAULT_TESSERACT_CONFIG = new TesseractOCRConfig();
/**
* Format used for signature dates
* TODO Make this thread-safe
*/
private final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ", Locale.ROOT);
final List exceptions = new ArrayList<>();
final PDDocument pdDocument;
final XHTMLContentHandler xhtml;
private final ParseContext context;
final Metadata metadata;
final EmbeddedDocumentExtractor embeddedDocumentExtractor;
final PDFParserConfig config;
private int pageIndex = 0;
AbstractPDF2XHTML(PDDocument pdDocument, ContentHandler handler, ParseContext context, Metadata metadata,
PDFParserConfig config) throws IOException {
this.pdDocument = pdDocument;
this.xhtml = new XHTMLContentHandler(handler, metadata);
this.context = context;
this.metadata = metadata;
this.config = config;
embeddedDocumentExtractor = EmbeddedDocumentUtil.getEmbeddedDocumentExtractor(context);
}
@Override
protected void startPage(PDPage page) throws IOException {
try {
xhtml.startElement("div", "class", "page");
} catch (SAXException e) {
throw new IOExceptionWithCause("Unable to start a page", e);
}
writeParagraphStart();
}
private void extractEmbeddedDocuments(PDDocument document)
throws IOException, SAXException, TikaException {
PDDocumentNameDictionary namesDictionary =
new PDDocumentNameDictionary(document.getDocumentCatalog());
PDEmbeddedFilesNameTreeNode efTree = namesDictionary.getEmbeddedFiles();
if (efTree == null) {
return;
}
Map embeddedFileNames = efTree.getNames();
//For now, try to get the embeddedFileNames out of embeddedFiles or its kids.
//This code follows: pdfbox/examples/pdmodel/ExtractEmbeddedFiles.java
//If there is a need we could add a fully recursive search to find a non-null
//Map that contains the doc info.
if (embeddedFileNames != null) {
processEmbeddedDocNames(embeddedFileNames);
} else {
List> kids = efTree.getKids();
if (kids == null) {
return;
}
for (PDNameTreeNode node : kids) {
embeddedFileNames = node.getNames();
if (embeddedFileNames != null) {
processEmbeddedDocNames(embeddedFileNames);
}
}
}
}
private void processDoc(String name, PDFileSpecification spec, AttributesImpl attributes) throws TikaException, SAXException, IOException {
if (spec instanceof PDSimpleFileSpecification) {
attributes.addAttribute("", "class", "class", "CDATA", "linked");
attributes.addAttribute("", "id", "id", "CDATA", spec.getFile());
xhtml.startElement("div", attributes);
xhtml.endElement("div");
} else if (spec instanceof PDComplexFileSpecification){
if (attributes.getIndex("source") < 0) {
attributes.addAttribute("", "source", "source", "CDATA", "attachment");
}
extractMultiOSPDEmbeddedFiles(name, (PDComplexFileSpecification)spec, attributes);
}
}
private void processEmbeddedDocNames(Map embeddedFileNames)
throws IOException, SAXException, TikaException {
if (embeddedFileNames == null || embeddedFileNames.isEmpty()) {
return;
}
for (Map.Entry ent : embeddedFileNames.entrySet()) {
processDoc(ent.getKey(), ent.getValue(), new AttributesImpl());
}
}
private void extractMultiOSPDEmbeddedFiles(String displayName,
PDComplexFileSpecification spec, AttributesImpl attributes) throws IOException,
SAXException, TikaException {
if (spec == null) {
return;
}
//current strategy is to pull all, not just first non-null
extractPDEmbeddedFile(displayName, spec.getFileUnicode(),
spec.getFile(), spec.getEmbeddedFile(), attributes);
extractPDEmbeddedFile(displayName, spec.getFileUnicode(),
spec.getFileMac(), spec.getEmbeddedFileMac(), attributes);
extractPDEmbeddedFile(displayName, spec.getFileUnicode(),
spec.getFileDos(), spec.getEmbeddedFileDos(), attributes);
extractPDEmbeddedFile(displayName, spec.getFileUnicode(),
spec.getFileUnix(), spec.getEmbeddedFileUnix(), attributes);
}
private void extractPDEmbeddedFile(String displayName, String unicodeFileName,
String fileName, PDEmbeddedFile file, AttributesImpl attributes)
throws SAXException, IOException, TikaException {
if (file == null) {
//skip silently
return;
}
fileName = (fileName == null || "".equals(fileName.trim())) ? unicodeFileName : fileName;
fileName = (fileName == null || "".equals(fileName.trim())) ? displayName : fileName;
// TODO: other metadata?
Metadata embeddedMetadata = new Metadata();
embeddedMetadata.set(Metadata.RESOURCE_NAME_KEY, fileName);
embeddedMetadata.set(Metadata.CONTENT_TYPE, file.getSubtype());
embeddedMetadata.set(Metadata.CONTENT_LENGTH, Long.toString(file.getSize()));
embeddedMetadata.set(TikaCoreProperties.EMBEDDED_RESOURCE_TYPE,
TikaCoreProperties.EmbeddedResourceType.ATTACHMENT.toString());
embeddedMetadata.set(TikaCoreProperties.ORIGINAL_RESOURCE_NAME, fileName);
if (!embeddedDocumentExtractor.shouldParseEmbedded(embeddedMetadata)) {
return;
}
TikaInputStream stream = null;
try {
stream = TikaInputStream.get(file.createInputStream());
} catch (IOException e) {
//store this exception in the parent's metadata
EmbeddedDocumentUtil.recordEmbeddedStreamException(e, metadata);
return;
}
try {
embeddedDocumentExtractor.parseEmbedded(
stream,
new EmbeddedContentHandler(xhtml),
embeddedMetadata, false);
attributes.addAttribute("", "class", "class", "CDATA", "embedded");
attributes.addAttribute("", "id", "id", "CDATA", fileName);
xhtml.startElement("div", attributes);
xhtml.endElement("div");
} finally {
IOUtils.closeQuietly(stream);
}
}
void handleCatchableIOE(IOException e) throws IOException {
if (config.isCatchIntermediateIOExceptions()) {
if (e.getCause() instanceof SAXException && e.getCause().getMessage() != null &&
e.getCause().getMessage().contains("Your document contained more than")) {
//TODO -- is there a cleaner way of checking for:
// WriteOutContentHandler.WriteLimitReachedException?
throw e;
}
String msg = e.getMessage();
if (msg == null) {
msg = "IOException, no message";
}
metadata.add(TikaCoreProperties.TIKA_META_EXCEPTION_WARNING, msg);
exceptions.add(e);
} else {
throw e;
}
}
void doOCROnCurrentPage() throws IOException, TikaException, SAXException {
if (config.getOcrStrategy().equals(NO_OCR)) {
return;
}
TesseractOCRConfig tesseractConfig =
context.get(TesseractOCRConfig.class, DEFAULT_TESSERACT_CONFIG);
TesseractOCRParser tesseractOCRParser = new TesseractOCRParser();
if (! tesseractOCRParser.hasTesseract(tesseractConfig)) {
throw new TikaException("Tesseract is not available. "+
"Please set the OCR_STRATEGY to NO_OCR or configure Tesseract correctly");
}
PDFRenderer renderer = new PDFRenderer(pdDocument);
TemporaryResources tmp = new TemporaryResources();
try {
BufferedImage image = renderer.renderImage(pageIndex, config.getOcrImageScale(), config.getOcrImageType());
Path tmpFile = tmp.createTempFile();
try (OutputStream os = Files.newOutputStream(tmpFile)) {
//TODO: get output format from TesseractConfig
ImageIOUtil.writeImage(image, config.getOcrImageFormatName(),
os, config.getOcrDPI(), config.getOcrImageQuality());
}
try (InputStream is = TikaInputStream.get(tmpFile)) {
tesseractOCRParser.parseInline(is, xhtml, tesseractConfig);
}
} catch (IOException e) {
handleCatchableIOE(e);
} catch (SAXException e) {
throw new IOExceptionWithCause("error writing OCR content from PDF", e);
} finally {
tmp.dispose();
}
}
@Override
protected void endPage(PDPage page) throws IOException {
try {
for (PDAnnotation annotation : page.getAnnotations()) {
if (annotation instanceof PDAnnotationFileAttachment) {
PDAnnotationFileAttachment fann = (PDAnnotationFileAttachment) annotation;
PDComplexFileSpecification fileSpec = (PDComplexFileSpecification) fann.getFile();
try {
AttributesImpl attributes = new AttributesImpl();
attributes.addAttribute("", "source", "source", "CDATA", "annotation");
extractMultiOSPDEmbeddedFiles(fann.getAttachmentName(), fileSpec, attributes);
} catch (SAXException e) {
throw new IOExceptionWithCause("file embedded in annotation sax exception", e);
} catch (TikaException e) {
throw new IOExceptionWithCause("file embedded in annotation tika exception", e);
} catch (IOException e) {
handleCatchableIOE(e);
}
} else if (annotation instanceof PDAnnotationWidget) {
handleWidget((PDAnnotationWidget)annotation);
}
// TODO: remove once PDFBOX-1143 is fixed:
if (config.getExtractAnnotationText()) {
PDActionURI uri = getActionURI(annotation);
if (uri != null) {
String link = uri.getURI();
if (link != null && link.trim().length() > 0) {
xhtml.startElement("div", "class", "annotation");
xhtml.startElement("a", "href", link);
xhtml.characters(link);
xhtml.endElement("a");
xhtml.endElement("div");
}
}
if (annotation instanceof PDAnnotationMarkup) {
PDAnnotationMarkup annotationMarkup = (PDAnnotationMarkup) annotation;
String title = annotationMarkup.getTitlePopup();
String subject = annotationMarkup.getSubject();
String contents = annotationMarkup.getContents();
// TODO: maybe also annotationMarkup.getRichContents()?
if (title != null || subject != null || contents != null) {
xhtml.startElement("div", "class", "annotation");
if (title != null) {
xhtml.startElement("div", "class", "annotationTitle");
xhtml.characters(title);
xhtml.endElement("div");
}
if (subject != null) {
xhtml.startElement("div", "class", "annotationSubject");
xhtml.characters(subject);
xhtml.endElement("div");
}
if (contents != null) {
xhtml.startElement("div", "class", "annotationContents");
xhtml.characters(contents);
xhtml.endElement("div");
}
xhtml.endElement("div");
}
}
}
}
if (config.getOcrStrategy().equals(PDFParserConfig.OCR_STRATEGY.OCR_AND_TEXT_EXTRACTION)) {
doOCROnCurrentPage();
}
PDPageAdditionalActions pageActions = page.getActions();
if (pageActions != null) {
handleDestinationOrAction(pageActions.getC(), ActionTrigger.PAGE_CLOSE);
handleDestinationOrAction(pageActions.getO(), ActionTrigger.PAGE_OPEN);
}
xhtml.endElement("div");
} catch (SAXException|TikaException e) {
throw new IOExceptionWithCause("Unable to end a page", e);
} catch (IOException e) {
exceptions.add(e);
} finally {
pageIndex++;
}
}
private void handleWidget(PDAnnotationWidget widget) throws TikaException, SAXException, IOException {
if (widget == null) {
return;
}
handleDestinationOrAction(widget.getAction(), ActionTrigger.ANNOTATION_WIDGET);
PDAnnotationAdditionalActions annotationActions = widget.getActions();
if (annotationActions != null) {
handleDestinationOrAction(annotationActions.getBl(), ActionTrigger.ANNOTATION_LOSE_INPUT_FOCUS);
handleDestinationOrAction(annotationActions.getD(), ActionTrigger.ANNOTATION_MOUSE_CLICK);
handleDestinationOrAction(annotationActions.getE(), ActionTrigger.ANNOTATION_CURSOR_ENTERS);
handleDestinationOrAction(annotationActions.getFo(), ActionTrigger.ANNOTATION_RECEIVES_FOCUS);
handleDestinationOrAction(annotationActions.getPC(), ActionTrigger.ANNOTATION_PAGE_CLOSED);
handleDestinationOrAction(annotationActions.getPI(), ActionTrigger.ANNOTATION_PAGE_NO_LONGER_VISIBLE);
handleDestinationOrAction(annotationActions.getPO(), ActionTrigger.ANNOTATION_PAGE_OPENED);
handleDestinationOrAction(annotationActions.getPV(), ActionTrigger.ANNOTATION_PAGE_VISIBLE);
handleDestinationOrAction(annotationActions.getU(), ActionTrigger.ANNOTATION_MOUSE_RELEASED);
handleDestinationOrAction(annotationActions.getX(), ActionTrigger.ANNOTATION_CURSOR_EXIT);
}
}
@Override
protected void startDocument(PDDocument pdf) throws IOException {
try {
xhtml.startDocument();
try {
handleDestinationOrAction(pdf.getDocumentCatalog().getOpenAction(), ActionTrigger.DOCUMENT_OPEN);
} catch (IOException e) {
//See PDFBOX-3773
//swallow -- no need to report this
}
} catch (TikaException|SAXException e) {
throw new IOExceptionWithCause("Unable to start a document", e);
}
}
private void handleDestinationOrAction(PDDestinationOrAction action,
ActionTrigger actionTrigger) throws IOException, SAXException, TikaException {
if (action == null || ! config.getExtractActions()) {
return;
}
AttributesImpl attributes = new AttributesImpl();
String actionOrDestString = (action instanceof PDAction) ? "action" : "destination";
addNonNullAttribute("class", actionOrDestString, attributes);
addNonNullAttribute("type", action.getClass().getSimpleName(), attributes);
addNonNullAttribute("trigger", actionTrigger.name(), attributes);
if (action instanceof PDActionImportData) {
processDoc("", ((PDActionImportData)action).getFile(), attributes);
} else if (action instanceof PDActionLaunch) {
PDActionLaunch pdActionLaunch = (PDActionLaunch)action;
addNonNullAttribute("id", pdActionLaunch.getF(), attributes);
addNonNullAttribute("defaultDirectory", pdActionLaunch.getD(), attributes);
addNonNullAttribute("operation", pdActionLaunch.getO(), attributes);
addNonNullAttribute("parameters", pdActionLaunch.getP(), attributes);
processDoc(pdActionLaunch.getF(), pdActionLaunch.getFile(), attributes);
} else if (action instanceof PDActionRemoteGoTo) {
PDActionRemoteGoTo remoteGoTo = (PDActionRemoteGoTo)action;
processDoc("", remoteGoTo.getFile(), attributes);
} else if (action instanceof PDActionJavaScript) {
PDActionJavaScript jsAction = (PDActionJavaScript)action;
Metadata m = new Metadata();
m.set(Metadata.CONTENT_TYPE, "application/javascript");
m.set(Metadata.CONTENT_ENCODING, StandardCharsets.UTF_8.toString());
m.set(PDF.ACTION_TRIGGER, actionTrigger.toString());
m.set(TikaCoreProperties.EMBEDDED_RESOURCE_TYPE, TikaCoreProperties.EmbeddedResourceType.MACRO.name());
String js = jsAction.getAction();
js = (js == null) ? "" : js;
if (embeddedDocumentExtractor.shouldParseEmbedded(m)) {
try (InputStream is = TikaInputStream.get(js.getBytes(StandardCharsets.UTF_8))) {
embeddedDocumentExtractor.parseEmbedded(is, xhtml, m, false);
}
}
addNonNullAttribute("class", "javascript", attributes);
addNonNullAttribute("type", jsAction.getType(), attributes);
addNonNullAttribute("subtype", jsAction.getSubType(), attributes);
xhtml.startElement("div", attributes);
xhtml.endElement("div");
} else {
xhtml.startElement("div", attributes);
xhtml.endElement("div");
}
}
private static void addNonNullAttribute(String name, String value, AttributesImpl attributes) {
if (name == null || value == null) {
return;
}
attributes.addAttribute("", name, name, "CDATA", value);
}
@Override
protected void endDocument(PDDocument pdf) throws IOException {
try {
// Extract text for any bookmarks:
if(config.getExtractBookmarksText()) {
extractBookmarkText();
}
try {
extractEmbeddedDocuments(pdf);
} catch (IOException e) {
handleCatchableIOE(e);
}
//extract acroform data at end of doc
if (config.getExtractAcroFormContent() == true) {
try {
extractAcroForm(pdf);
} catch (IOException e) {
handleCatchableIOE(e);
}
}
PDDocumentCatalogAdditionalActions additionalActions = pdf.getDocumentCatalog().getActions();
handleDestinationOrAction(additionalActions.getDP(), ActionTrigger.AFTER_DOCUMENT_PRINT);
handleDestinationOrAction(additionalActions.getDS(), ActionTrigger.AFTER_DOCUMENT_SAVE);
handleDestinationOrAction(additionalActions.getWC(), ActionTrigger.BEFORE_DOCUMENT_CLOSE);
handleDestinationOrAction(additionalActions.getWP(), ActionTrigger.BEFORE_DOCUMENT_PRINT);
handleDestinationOrAction(additionalActions.getWS(), ActionTrigger.BEFORE_DOCUMENT_SAVE);
xhtml.endDocument();
} catch (TikaException e) {
throw new IOExceptionWithCause("Unable to end a document", e);
} catch (SAXException e) {
throw new IOExceptionWithCause("Unable to end a document", e);
}
}
void extractBookmarkText() throws SAXException, IOException, TikaException {
PDDocumentOutline outline = document.getDocumentCatalog().getDocumentOutline();
if (outline != null) {
extractBookmarkText(outline);
}
}
void extractBookmarkText(PDOutlineNode bookmark) throws SAXException, IOException, TikaException {
PDOutlineItem current = bookmark.getFirstChild();
if (current != null) {
xhtml.startElement("ul");
while (current != null) {
xhtml.startElement("li");
xhtml.characters(current.getTitle());
xhtml.endElement("li");
handleDestinationOrAction(current.getAction(), ActionTrigger.BOOKMARK);
// Recurse:
extractBookmarkText(current);
current = current.getNextSibling();
}
xhtml.endElement("ul");
}
}
void extractAcroForm(PDDocument pdf) throws IOException,
SAXException, TikaException {
//Thank you, Ben Litchfield, for org.apache.pdfbox.examples.fdf.PrintFields
//this code derives from Ben's code
PDDocumentCatalog catalog = pdf.getDocumentCatalog();
if (catalog == null)
return;
PDAcroForm form = catalog.getAcroForm();
if (form == null)
return;
//if it has xfa, try that.
//if it doesn't exist or there's an exception,
//go with traditional AcroForm
PDXFAResource pdxfa = form.getXFA();
if (pdxfa != null) {
//if successful, return
XFAExtractor xfaExtractor = new XFAExtractor();
InputStream is = null;
try {
is = new BufferedInputStream(
new ByteArrayInputStream(pdxfa.getBytes()));
} catch (IOException e) {
EmbeddedDocumentUtil.recordEmbeddedStreamException(e, metadata);
}
if (is != null) {
try {
xfaExtractor.extract(is, xhtml, metadata, context);
return;
} catch (XMLStreamException e) {
//if there was an xml parse exception in xfa, try the AcroForm
EmbeddedDocumentUtil.recordException(e, metadata);
} finally {
IOUtils.closeQuietly(is);
}
}
}
@SuppressWarnings("rawtypes")
List fields = form.getFields();
if (fields == null)
return;
@SuppressWarnings("rawtypes")
ListIterator itr = fields.listIterator();
if (itr == null)
return;
xhtml.startElement("div", "class", "acroform");
xhtml.startElement("ol");
while (itr.hasNext()) {
Object obj = itr.next();
if (obj != null && obj instanceof PDField) {
processAcroField((PDField) obj, 0);
}
}
xhtml.endElement("ol");
xhtml.endElement("div");
}
private void processAcroField(PDField field, final int currentRecursiveDepth)
throws SAXException, IOException, TikaException {
if (currentRecursiveDepth >= MAX_ACROFORM_RECURSIONS) {
return;
}
PDFormFieldAdditionalActions pdFormFieldAdditionalActions = field.getActions();
if (pdFormFieldAdditionalActions != null) {
handleDestinationOrAction(pdFormFieldAdditionalActions.getC(), ActionTrigger.FORM_FIELD_RECALCULATE);
handleDestinationOrAction(pdFormFieldAdditionalActions.getF(), ActionTrigger.FORM_FIELD_FORMATTED);
handleDestinationOrAction(pdFormFieldAdditionalActions.getK(), ActionTrigger.FORM_FIELD_KEYSTROKE);
handleDestinationOrAction(pdFormFieldAdditionalActions.getV(), ActionTrigger.FORM_FIELD_VALUE_CHANGE);
}
if (field.getWidgets() != null) {
for (PDAnnotationWidget widget : field.getWidgets()) {
handleWidget(widget);
}
}
addFieldString(field);
if (field instanceof PDNonTerminalField) {
int r = currentRecursiveDepth + 1;
xhtml.startElement("ol");
for (PDField child : ((PDNonTerminalField)field).getChildren()) {
processAcroField(child, r);
}
xhtml.endElement("ol");
}
}
private void addFieldString(PDField field) throws SAXException {
//Pick partial name to present in content and altName for attribute
//Ignoring FullyQualifiedName for now
String partName = field.getPartialName();
String altName = field.getAlternateFieldName();
StringBuilder sb = new StringBuilder();
AttributesImpl attrs = new AttributesImpl();
if (partName != null) {
sb.append(partName).append(": ");
}
if (altName != null) {
attrs.addAttribute("", "altName", "altName", "CDATA", altName);
}
//return early if PDSignature field
if (field instanceof PDSignatureField) {
handleSignature(attrs, (PDSignatureField) field);
return;
}
String value = field.getValueAsString();
if (value != null && !value.equals("null")) {
sb.append(value);
}
if (attrs.getLength() > 0 || sb.length() > 0) {
xhtml.startElement("li", attrs);
xhtml.characters(sb.toString());
xhtml.endElement("li");
}
}
private void handleSignature(AttributesImpl parentAttributes, PDSignatureField sigField)
throws SAXException {
PDSignature sig = sigField.getSignature();
if (sig == null) {
return;
}
Map vals = new TreeMap<>();
vals.put("name", sig.getName());
vals.put("contactInfo", sig.getContactInfo());
vals.put("location", sig.getLocation());
vals.put("reason", sig.getReason());
Calendar cal = sig.getSignDate();
if (cal != null) {
dateFormat.setTimeZone(cal.getTimeZone());
vals.put("date", dateFormat.format(cal.getTime()));
}
//see if there is any data
int nonNull = 0;
for (String val : vals.keySet()) {
if (val != null && !val.equals("")) {
nonNull++;
}
}
//if there is, process it
if (nonNull > 0) {
xhtml.startElement("li", parentAttributes);
AttributesImpl attrs = new AttributesImpl();
attrs.addAttribute("", "type", "type", "CDATA", "signaturedata");
xhtml.startElement("ol", attrs);
for (Map.Entry e : vals.entrySet()) {
if (e.getValue() == null || e.getValue().equals("")) {
continue;
}
attrs = new AttributesImpl();
attrs.addAttribute("", "signdata", "signdata", "CDATA", e.getKey());
xhtml.startElement("li", attrs);
xhtml.characters(e.getValue());
xhtml.endElement("li");
}
xhtml.endElement("ol");
xhtml.endElement("li");
}
}
private static PDActionURI getActionURI(PDAnnotation annot) {
//copied and pasted from PDFBox's PrintURLs
// use reflection to catch all annotation types that have getAction()
// If you can't use reflection, then check for classes
// PDAnnotationLink and PDAnnotationWidget, and call getAction() and check for a
// PDActionURI result type
try {
Method actionMethod = annot.getClass().getDeclaredMethod("getAction");
if (actionMethod.getReturnType().equals(PDAction.class)) {
PDAction action = (PDAction) actionMethod.invoke(annot);
if (action instanceof PDActionURI) {
return (PDActionURI) action;
}
}
}
catch (NoSuchMethodException|IllegalAccessException|InvocationTargetException e) {
}
return null;
}
}