(Arrays.asList(elements)));
+ }
+
+ /**
+ * Metadata associated with the document. Used to fill in the
+ * <head/> section.
+ */
+ private final ThunderbirdMetadata metadata;
+
+ /**
+ * Flags to indicate whether the document head element has been started/ended.
+ */
+ private boolean headStarted = false;
+ private boolean headEnded = false;
+ private boolean useFrameset = false;
+
+ public ThunderbirdXHTMLContentHandler(ContentHandler handler, ThunderbirdMetadata metadata) {
+ super(handler);
+ this.metadata = metadata;
+ }
+
+ /**
+ * Starts an XHTML document by setting up the namespace mappings.
+ * The standard XHTML prefix is generated lazily when the first
+ * element is started.
+ */
+ @Override
+ public void startDocument() throws SAXException {
+ super.startDocument();
+ startPrefixMapping("", XHTML);
+ }
+
+ /**
+ * Generates the following XHTML prefix when called for the first time:
+ *
+ * <html>
+ * <head>
+ * <title>...</title>
+ * </head>
+ * <body>
+ *
+ */
+ private void lazyStartHead() throws SAXException {
+ if (!headStarted) {
+ headStarted = true;
+
+ // Call directly, so we don't go through our startElement(), which will
+ // ignore these elements.
+ super.startElement(XHTML, "html", "html", EMPTY_ATTRIBUTES);
+ newline();
+ super.startElement(XHTML, "head", "head", EMPTY_ATTRIBUTES);
+ newline();
+ }
+ }
+
+ /**
+ * Generates the following XHTML prefix when called for the first time:
+ *
+ * <html>
+ * <head>
+ * <title>...</title>
+ * </head>
+ * <body> (or <frameset>
+ *
+ */
+ private void lazyEndHead(boolean isFrameset) throws SAXException {
+ lazyStartHead();
+
+ if (!headEnded) {
+ headEnded = true;
+ useFrameset = isFrameset;
+
+ // TIKA-478: Emit all metadata values (other than title). We have to call
+ // startElement() and characters() directly to avoid recursive problems.
+ for (String name : metadata.names()) {
+ if (name.equals("title")) {
+ continue;
+ }
+
+ for (String value : metadata.getValues(name)) {
+ // Putting null values into attributes causes problems, but is
+ // allowed by Metadata, so guard against that.
+ if (value != null) {
+ AttributesImpl attributes = new AttributesImpl();
+ attributes.addAttribute("", "name", "name", "CDATA", name);
+ attributes.addAttribute("", "content", "content", "CDATA", value);
+ super.startElement(XHTML, "meta", "meta", attributes);
+ super.endElement(XHTML, "meta", "meta");
+ newline();
+ }
+ }
+ }
+
+ super.startElement(XHTML, "title", "title", EMPTY_ATTRIBUTES);
+ String title = metadata.get(Metadata.TITLE);
+ if (title != null && title.length() > 0) {
+ char[] titleChars = title.toCharArray();
+ super.characters(titleChars, 0, titleChars.length);
+ } else {
+ // TIKA-725: Prefer over
+ super.characters(new char[0], 0, 0);
+ }
+ super.endElement(XHTML, "title", "title");
+ newline();
+
+ super.endElement(XHTML, "head", "head");
+ newline();
+
+ if (useFrameset) {
+ super.startElement(XHTML, "frameset", "frameset", EMPTY_ATTRIBUTES);
+ } else {
+ super.startElement(XHTML, "body", "body", EMPTY_ATTRIBUTES);
+ }
+ }
+ }
+
+ /**
+ * Ends the XHTML document by writing the following footer and
+ * clearing the namespace mappings:
+ *
+ * </body>
+ * </html>
+ *
+ */
+ @Override
+ public void endDocument() throws SAXException {
+ lazyEndHead(useFrameset);
+
+ if (useFrameset) {
+ super.endElement(XHTML, "frameset", "frameset");
+ } else {
+ super.endElement(XHTML, "body", "body");
+ }
+
+ super.endElement(XHTML, "html", "html");
+
+ endPrefixMapping("");
+ super.endDocument();
+ }
+
+ /**
+ * Starts the given element. Table cells and list items are automatically
+ * indented by emitting a tab character as ignorable whitespace.
+ */
+ @Override
+ public void startElement(
+ String uri, String local, String name, Attributes attributes)
+ throws SAXException {
+
+ if (name.equals("frameset")) {
+ lazyEndHead(true);
+ } else if (!AUTO.contains(name)) {
+ if (HEAD.contains(name)) {
+ lazyStartHead();
+ } else {
+ lazyEndHead(false);
+ }
+
+ if (XHTML.equals(uri) && INDENT.contains(name)) {
+ ignorableWhitespace(TAB, 0, TAB.length);
+ }
+
+ super.startElement(uri, local, name, attributes);
+ }
+ }
+
+ /**
+ * Ends the given element. Block elements are automatically followed
+ * by a newline character.
+ */
+ @Override
+ public void endElement(String uri, String local, String name) throws SAXException {
+ if (!AUTO.contains(name)) {
+ super.endElement(uri, local, name);
+ if (XHTML.equals(uri) && ENDLINE.contains(name)) {
+ newline();
+ }
+ }
+ }
+
+ /**
+ * @see TIKA-210
+ */
+ @Override
+ public void characters(char[] ch, int start, int length) throws SAXException {
+ lazyEndHead(useFrameset);
+ super.characters(ch, start, length);
+ }
+
+ //------------------------------------------< public convenience methods >
+
+ public void startElement(String name) throws SAXException {
+ startElement(XHTML, name, name, EMPTY_ATTRIBUTES);
+ }
+
+ public void startElement(String name, String attribute, String value)
+ throws SAXException {
+ AttributesImpl attributes = new AttributesImpl();
+ attributes.addAttribute("", attribute, attribute, "CDATA", value);
+ startElement(XHTML, name, name, attributes);
+ }
+
+ public void startElement(String name, AttributesImpl attributes)
+ throws SAXException {
+ startElement(XHTML, name, name, attributes);
+ }
+
+ public void endElement(String name) throws SAXException {
+ endElement(XHTML, name, name);
+ }
+
+ public void characters(String characters) throws SAXException {
+ if (characters != null && characters.length() > 0) {
+ characters(characters.toCharArray(), 0, characters.length());
+ }
+ }
+
+ public void newline() throws SAXException {
+ ignorableWhitespace(NL, 0, NL.length);
+ }
+
+ /**
+ * Emits an XHTML element with the given text content. If the given
+ * text value is null or empty, then the element is not written.
+ *
+ * @param name XHTML element name
+ * @param value element value, possibly null
+ * @throws SAXException if the content element could not be written
+ */
+ public void element(String name, String value) throws SAXException {
+ if (value != null && value.length() > 0) {
+ startElement(name);
+ characters(value);
+ endElement(name);
+ }
+ }
+
+}
diff --git a/ThunderbirdMboxEmailModule/src/org/sleuthkit/autopsy/thunderbirdparser/layer.xml b/ThunderbirdMboxEmailModule/src/org/sleuthkit/autopsy/thunderbirdparser/layer.xml
new file mode 100644
index 0000000000..6d26e34fd2
--- /dev/null
+++ b/ThunderbirdMboxEmailModule/src/org/sleuthkit/autopsy/thunderbirdparser/layer.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
+