org.apache.tika.gui.TikaGUI 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.gui;
import javax.swing.Box;
import javax.swing.JDialog;
import javax.swing.JEditorPane;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextPane;
import javax.swing.ProgressMonitorInputStream;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.event.HyperlinkEvent;
import javax.swing.event.HyperlinkEvent.EventType;
import javax.swing.event.HyperlinkListener;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.sax.SAXTransformerFactory;
import javax.xml.transform.sax.TransformerHandler;
import javax.xml.transform.stream.StreamResult;
import java.awt.CardLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.WindowEvent;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.io.Writer;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import org.apache.commons.io.IOUtils;
import org.apache.tika.config.TikaConfig;
import org.apache.tika.exception.TikaException;
import org.apache.tika.extractor.DocumentSelector;
import org.apache.tika.io.TikaInputStream;
import org.apache.tika.metadata.Metadata;
import org.apache.tika.metadata.serialization.JsonMetadataList;
import org.apache.tika.mime.MediaType;
import org.apache.tika.parser.AbstractParser;
import org.apache.tika.parser.AutoDetectParser;
import org.apache.tika.parser.DigestingParser;
import org.apache.tika.parser.ParseContext;
import org.apache.tika.parser.Parser;
import org.apache.tika.parser.RecursiveParserWrapper;
import org.apache.tika.parser.html.BoilerpipeContentHandler;
import org.apache.tika.parser.utils.CommonsDigester;
import org.apache.tika.sax.BasicContentHandlerFactory;
import org.apache.tika.sax.BodyContentHandler;
import org.apache.tika.sax.ContentHandlerDecorator;
import org.apache.tika.sax.TeeContentHandler;
import org.apache.tika.sax.XHTMLContentHandler;
import org.xml.sax.Attributes;
import org.xml.sax.ContentHandler;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.AttributesImpl;
import static java.nio.charset.StandardCharsets.UTF_8;
/**
* Simple Swing GUI for Apache Tika. You can drag and drop files on top
* of the window to have them parsed.
*/
public class TikaGUI extends JFrame
implements ActionListener, HyperlinkListener {
//maximum length to allow for mark for reparse to get JSON
private static final int MAX_MARK = 20*1024*1024;//20MB
/**
* Serial version UID.
*/
private static final long serialVersionUID = 5883906936187059495L;
/**
* Main method. Sets the Swing look and feel to the operating system
* settings, and starts the Tika GUI with an {@link AutoDetectParser}
* instance as the default parser.
*
* @param args ignored
* @throws Exception if an error occurs
*/
public static void main(String[] args) throws Exception {
TikaConfig config = TikaConfig.getDefaultConfig();
if (args.length > 0) {
File configFile = new File(args[0]);
config = new TikaConfig(configFile);
}
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
final TikaConfig finalConfig = config;
SwingUtilities.invokeLater(new Runnable() {
public void run() {
new TikaGUI(new DigestingParser(
new AutoDetectParser(finalConfig),
new CommonsDigester(MAX_MARK,
CommonsDigester.DigestAlgorithm.MD5,
CommonsDigester.DigestAlgorithm.SHA256)
)).setVisible(true);
}
});
}
/**
* Parsing context.
*/
private final ParseContext context;
/**
* Configured parser instance.
*/
private final Parser parser;
/**
* Captures requested embedded images
*/
private final ImageSavingParser imageParser;
/**
* The card layout for switching between different views.
*/
private final CardLayout layout = new CardLayout();
/**
* Container for the editor cards.
*/
private final JPanel cards;
/**
* Formatted XHTML output.
*/
private final JEditorPane html;
/**
* Plain text output.
*/
private final JEditorPane text;
/**
* Main content output.
*/
private final JEditorPane textMain;
/**
* Raw XHTML source.
*/
private final JEditorPane xml;
/**
* Raw JSON source.
*/
private final JEditorPane json;
/**
* Document metadata.
*/
private final JEditorPane metadata;
/**
* File chooser.
*/
private final JFileChooser chooser = new JFileChooser();
public TikaGUI(Parser parser) {
super("Apache Tika");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
addMenuBar();
cards = new JPanel(layout);
addWelcomeCard(cards, "welcome");
metadata = addCard(cards, "text/plain", "metadata");
html = addCard(cards, "text/html", "html");
text = addCard(cards, "text/plain", "text");
textMain = addCard(cards, "text/plain", "main");
xml = addCard(cards, "text/plain", "xhtml");
json = addCard(cards, "text/plain", "json");
add(cards);
layout.show(cards, "welcome");
setPreferredSize(new Dimension(640, 480));
pack();
this.context = new ParseContext();
this.parser = parser;
this.imageParser = new ImageSavingParser(parser);
this.context.set(DocumentSelector.class, new ImageDocumentSelector());
this.context.set(Parser.class, imageParser);
}
private void addMenuBar() {
JMenuBar bar = new JMenuBar();
JMenu file = new JMenu("File");
file.setMnemonic(KeyEvent.VK_F);
addMenuItem(file, "Open...", "openfile", KeyEvent.VK_O);
addMenuItem(file, "Open URL...", "openurl", KeyEvent.VK_U);
file.addSeparator();
addMenuItem(file, "Exit", "exit", KeyEvent.VK_X);
bar.add(file);
JMenu view = new JMenu("View");
view.setMnemonic(KeyEvent.VK_V);
addMenuItem(view, "Metadata", "metadata", KeyEvent.VK_M);
addMenuItem(view, "Formatted text", "html", KeyEvent.VK_F);
addMenuItem(view, "Plain text", "text", KeyEvent.VK_P);
addMenuItem(view, "Main content", "main", KeyEvent.VK_C);
addMenuItem(view, "Structured text", "xhtml", KeyEvent.VK_S);
addMenuItem(view, "Recursive JSON", "json", KeyEvent.VK_J);
bar.add(view);
bar.add(Box.createHorizontalGlue());
JMenu help = new JMenu("Help");
help.setMnemonic(KeyEvent.VK_H);
addMenuItem(help, "About Tika", "about", KeyEvent.VK_A);
bar.add(help);
setJMenuBar(bar);
}
private void addMenuItem(
JMenu menu, String title, String command, int key) {
JMenuItem item = new JMenuItem(title, key);
item.setActionCommand(command);
item.addActionListener(this);
menu.add(item);
}
public void actionPerformed(ActionEvent e) {
String command = e.getActionCommand();
if ("openfile".equals(command)) {
int rv = chooser.showOpenDialog(this);
if (rv == JFileChooser.APPROVE_OPTION) {
openFile(chooser.getSelectedFile());
}
} else if ("openurl".equals(command)) {
Object rv = JOptionPane.showInputDialog(
this, "Enter the URL of the resource to be parsed:",
"Open URL", JOptionPane.PLAIN_MESSAGE,
null, null, "");
if (rv != null && rv.toString().length() > 0) {
try {
openURL(new URL(rv.toString().trim()));
} catch (MalformedURLException exception) {
JOptionPane.showMessageDialog(
this, "The given string is not a valid URL",
"Invalid URL", JOptionPane.ERROR_MESSAGE);
}
}
} else if ("html".equals(command)) {
layout.show(cards, command);
} else if ("text".equals(command)) {
layout.show(cards, command);
} else if ("main".equals(command)) {
layout.show(cards, command);
} else if ("xhtml".equals(command)) {
layout.show(cards, command);
} else if ("metadata".equals(command)) {
layout.show(cards, command);
} else if ("json".equals(command)) {
layout.show(cards, command);
} else if ("about".equals(command)) {
textDialog(
"About Apache Tika",
TikaGUI.class.getResource("about.html"));
} else if ("exit".equals(command)) {
Toolkit.getDefaultToolkit().getSystemEventQueue().postEvent(
new WindowEvent(this, WindowEvent.WINDOW_CLOSING));
}
}
public void openFile(File file) {
try {
Metadata metadata = new Metadata();
try (TikaInputStream stream = TikaInputStream.get(file, metadata)) {
handleStream(stream, metadata);
}
} catch (Throwable t) {
handleError(file.getPath(), t);
}
}
public void openURL(URL url) {
try {
Metadata metadata = new Metadata();
try (TikaInputStream stream = TikaInputStream.get(url, metadata)) {
handleStream(stream, metadata);
}
} catch (Throwable t) {
handleError(url.toString(), t);
}
}
private void handleStream(InputStream input, Metadata md)
throws Exception {
StringWriter htmlBuffer = new StringWriter();
StringWriter textBuffer = new StringWriter();
StringWriter textMainBuffer = new StringWriter();
StringWriter xmlBuffer = new StringWriter();
StringBuilder metadataBuffer = new StringBuilder();
ContentHandler handler = new TeeContentHandler(
getHtmlHandler(htmlBuffer),
getTextContentHandler(textBuffer),
getTextMainContentHandler(textMainBuffer),
getXmlContentHandler(xmlBuffer));
context.set(DocumentSelector.class, new ImageDocumentSelector());
input = TikaInputStream.get(new ProgressMonitorInputStream(
this, "Parsing stream", input));
if (input.markSupported()) {
int mark = -1;
if (input instanceof TikaInputStream) {
if (((TikaInputStream)input).hasFile()) {
mark = (int)((TikaInputStream)input).getLength();
}
}
if (mark == -1) {
mark = MAX_MARK;
}
input.mark(mark);
}
parser.parse(input, handler, md, context);
String[] names = md.names();
Arrays.sort(names);
for (String name : names) {
for (String val : md.getValues(name)) {
metadataBuffer.append(name);
metadataBuffer.append(": ");
metadataBuffer.append(val);
metadataBuffer.append("\n");
}
}
String name = md.get(Metadata.RESOURCE_NAME_KEY);
if (name != null && name.length() > 0) {
setTitle("Apache Tika: " + name);
} else {
setTitle("Apache Tika: unnamed document");
}
setText(metadata, metadataBuffer.toString());
setText(xml, xmlBuffer.toString());
setText(text, textBuffer.toString());
setText(textMain, textMainBuffer.toString());
setText(html, htmlBuffer.toString());
if (!input.markSupported()) {
setText(json, "InputStream does not support mark/reset for Recursive Parsing");
layout.show(cards, "metadata");
return;
}
boolean isReset = false;
try {
input.reset();
isReset = true;
} catch (IOException e) {
setText(json, "Error during stream reset.\n"+
"There's a limit of "+MAX_MARK + " bytes for this type of processing in the GUI.\n"+
"Try the app with command line argument of -J."
);
}
if (isReset) {
RecursiveParserWrapper wrapper = new RecursiveParserWrapper(parser,
new BasicContentHandlerFactory(
BasicContentHandlerFactory.HANDLER_TYPE.BODY, -1));
wrapper.parse(input, null, new Metadata(), new ParseContext());
StringWriter jsonBuffer = new StringWriter();
JsonMetadataList.setPrettyPrinting(true);
JsonMetadataList.toJson(wrapper.getMetadata(), jsonBuffer);
setText(json, jsonBuffer.toString());
}
layout.show(cards, "metadata");
}
private void handleError(String name, Throwable t) {
StringWriter writer = new StringWriter();
writer.append("Apache Tika was unable to parse the document\n");
writer.append("at " + name + ".\n\n");
writer.append("The full exception stack trace is included below:\n\n");
t.printStackTrace(new PrintWriter(writer));
JEditorPane editor =
new JEditorPane("text/plain", writer.toString());
editor.setEditable(false);
editor.setBackground(Color.WHITE);
editor.setCaretPosition(0);
editor.setPreferredSize(new Dimension(600, 400));
JDialog dialog = new JDialog(this, "Apache Tika error");
dialog.add(new JScrollPane(editor));
dialog.pack();
dialog.setVisible(true);
}
private void addWelcomeCard(JPanel panel, String name) {
try {
JEditorPane editor =
new JEditorPane(TikaGUI.class.getResource("welcome.html"));
editor.setContentType("text/html");
editor.setEditable(false);
editor.setBackground(Color.WHITE);
editor.setTransferHandler(new ParsingTransferHandler(
editor.getTransferHandler(), this));
panel.add(new JScrollPane(editor), name);
} catch (IOException e) {
e.printStackTrace();
}
}
private JEditorPane addCard(JPanel panel, String type, String name) {
JEditorPane editor = new JTextPane();
editor.setBackground(Color.WHITE);
editor.setContentType(type);
editor.setTransferHandler(new ParsingTransferHandler(
editor.getTransferHandler(), this));
panel.add(new JScrollPane(editor), name);
return editor;
}
private void textDialog(String title, URL resource) {
try {
JDialog dialog = new JDialog(this, title);
JEditorPane editor = new JEditorPane(resource);
editor.setContentType("text/html");
editor.setEditable(false);
editor.setBackground(Color.WHITE);
editor.setPreferredSize(new Dimension(400, 250));
editor.addHyperlinkListener(this);
dialog.add(editor);
dialog.pack();
dialog.setVisible(true);
} catch (IOException e) {
e.printStackTrace();
}
}
public void hyperlinkUpdate(HyperlinkEvent e) {
if (e.getEventType() == EventType.ACTIVATED) {
try {
URL url = e.getURL();
try (InputStream stream = url.openStream()) {
JEditorPane editor =
new JEditorPane("text/plain", IOUtils.toString(stream, UTF_8));
editor.setEditable(false);
editor.setBackground(Color.WHITE);
editor.setCaretPosition(0);
editor.setPreferredSize(new Dimension(600, 400));
String name = url.toString();
name = name.substring(name.lastIndexOf('/') + 1);
JDialog dialog = new JDialog(this, "Apache Tika: " + name);
dialog.add(new JScrollPane(editor));
dialog.pack();
dialog.setVisible(true);
}
} catch (IOException exception) {
exception.printStackTrace();
}
}
}
private void setText(JEditorPane editor, String text) {
editor.setText(text);
editor.setCaretPosition(0);
}
/**
* Creates and returns a content handler that turns XHTML input to
* simplified HTML output that can be correctly parsed and displayed
* by {@link JEditorPane}.
*
* The returned content handler is set to output html
* to the given writer. The XHTML namespace is removed from the output
* to prevent the serializer from using the <tag/> empty element
* syntax that causes extra ">" characters to be displayed.
* The <head> tags are dropped to prevent the serializer from
* generating a <META> content type tag that makes
* {@link JEditorPane} fail thinking that the document character set
* is inconsistent.
*
* Additionally, it will use ImageSavingParser to re-write embedded:(image)
* image links to be file:///(temporary file) so that they can be loaded.
*
* @param writer output writer
* @return HTML content handler
* @throws TransformerConfigurationException if an error occurs
*/
private ContentHandler getHtmlHandler(Writer writer)
throws TransformerConfigurationException {
SAXTransformerFactory factory = (SAXTransformerFactory)
SAXTransformerFactory.newInstance();
TransformerHandler handler = factory.newTransformerHandler();
handler.getTransformer().setOutputProperty(OutputKeys.METHOD, "html");
handler.setResult(new StreamResult(writer));
return new ContentHandlerDecorator(handler) {
@Override
public void startElement(
String uri, String localName, String name, Attributes atts)
throws SAXException {
if (XHTMLContentHandler.XHTML.equals(uri)) {
uri = null;
}
if (!"head".equals(localName)) {
if("img".equals(localName)) {
AttributesImpl newAttrs;
if(atts instanceof AttributesImpl) {
newAttrs = (AttributesImpl)atts;
} else {
newAttrs = new AttributesImpl(atts);
}
for(int i=0; i wanted = new HashMap();
private Parser downstreamParser;
private File tmpDir;
private ImageSavingParser(Parser downstreamParser) {
this.downstreamParser = downstreamParser;
try {
File t = File.createTempFile("tika", ".test");
tmpDir = t.getParentFile();
} catch(IOException e) {}
}
public File requestSave(String embeddedName) throws IOException {
String suffix = ".tika";
int splitAt = embeddedName.lastIndexOf('.');
if (splitAt > 0) {
embeddedName.substring(splitAt);
}
File tmp = File.createTempFile("tika-embedded-", suffix);
wanted.put(embeddedName, tmp);
return tmp;
}
public Set getSupportedTypes(ParseContext context) {
// Never used in an auto setup
return null;
}
public void parse(InputStream stream, ContentHandler handler,
Metadata metadata, ParseContext context) throws IOException,
SAXException, TikaException {
String name = metadata.get(Metadata.RESOURCE_NAME_KEY);
if(name != null && wanted.containsKey(name)) {
FileOutputStream out = new FileOutputStream(wanted.get(name));
IOUtils.copy(stream, out);
out.close();
} else {
if(downstreamParser != null) {
downstreamParser.parse(stream, handler, metadata, context);
}
}
}
}
}