Cómo leer o analizar archivos MHTML (.mht) en Java

Necesito extraer el contenido de la mayoría de los archivos documentales conocidos, como:

  1. pdf
  2. html
  3. doc / docx etc.

Para la mayoría de estos formatos de archivo, estoy planeando usar:

http://tika.apache.org/

Pero a partir de ahora Tika no admite archivos MHTML (* .mht) .. ( http://en.wikipedia.org/wiki/MHTML ) Hay algunos ejemplos en C # ( http://www.codeproject.com/KB /files/MhtBuilder.aspx ) pero no encontré ninguno en Java.

Intenté abrir el archivo * .mht en 7Zip y falló … Aunque WinZip fue capaz de descomprimir el archivo en imágenes y texto (CSS, HTML, Script) como archivos de texto y binarios …

Según la página de MSDN ( http://msdn.microsoft.com/en-us/library/aa767785%28VS.85%29.aspx#compress_content ) y la página de code project mencioné anteriormente … los archivos mht usan compresión GZip. …

Intentar descomprimir en java da como resultado las siguientes excepciones: con java.uti.zip.GZIPInputStream

 java.io.IOException: Not in GZIP format at java.util.zip.GZIPInputStream.readHeader(Unknown Source) at java.util.zip.GZIPInputStream.(Unknown Source) at java.util.zip.GZIPInputStream.(Unknown Source) at GZipTest.main(GZipTest.java:16) 

Y con java.util.zip.ZipFile

  java.util.zip.ZipException: error in opening zip file at java.util.zip.ZipFile.open(Native Method) at java.util.zip.ZipFile.(Unknown Source) at java.util.zip.ZipFile.(Unknown Source) at GZipTest.main(GZipTest.java:21) 

Amablemente sugiera cómo descomprimirlo …

Gracias….

Francamente, no esperaba una solución en un futuro cercano y estaba a punto de darme por vencida, pero de alguna forma me encontré con esta página:

http://en.wikipedia.org/wiki/MIME#Multipart_messages

http://msdn.microsoft.com/en-us/library/ms527355%28EXCHG.10%29.aspx

Aunque no es muy pegadizo a primera vista. Pero si miras con cuidado obtendrás una pista. Después de leer esto, *.mht mi IE y al azar comencé a guardar páginas como archivo *.mht . Déjame ir línea por línea …

Pero permítanme explicar de antemano que mi objective final era separar / extraer el contenido html y analizarlo … la solución no es completa en sí misma, ya que depende del character set o la encoding que elijo al guardar. Pero a pesar de que extraerá los archivos individuales con pequeños enganches …

Espero que esto sea útil para cualquiera que intente analizar / descomprimir *.mht/MHTML 🙂

======= Explicación ======== ** Tomado de un archivo mht **

 From: "Saved by Windows Internet Explorer 7" 

Es el software utilizado para guardar el archivo

 Subject: Google Date: Tue, 13 Jul 2010 21:23:03 +0530 MIME-Version: 1.0 

Sujeto, fecha y versión de mimo … muy parecido al formato de correo

  Content-Type: multipart/related; type="text/html"; 

Esta es la parte que nos dice que es un documento de multipart . Un documento multiparte tiene uno o más conjuntos de datos diferentes combinados en un solo cuerpo, un campo de Tipo de contenido multipart debe aparecer en el encabezado de la entidad. Aquí, también podemos ver el tipo como "text/html" .

 boundary="----=_NextPart_000_0007_01CB22D1.93BBD1A0" 

De todo esto es la parte más importante. Este es el delimitador único que divide dos partes diferentes (html, images, css, script, etc.). Una vez que se hace con esto, todo se vuelve fácil … Ahora, solo tengo que recorrer el documento y encontrar diferentes secciones y guardarlas según su Content-Transfer-Encoding (base64, quoted-printable, etc.) … . . .

MUESTRA

  ------=_NextPart_000_0007_01CB22D1.93BBD1A0 Content-Type: text/html; charset="utf-8" Content-Transfer-Encoding: quoted-printable Content-Location: http://www.google.com/webhp?sourceid=navclient&ie=UTF-8 < !DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" = . . . 

** CÓDIGO JAVA **

Una interfaz para definir constantes.

 public interface IConstants { public String BOUNDARY = "boundary"; public String CHAR_SET = "charset"; public String CONTENT_TYPE = "Content-Type"; public String CONTENT_TRANSFER_ENCODING = "Content-Transfer-Encoding"; public String CONTENT_LOCATION = "Content-Location"; public String UTF8_BOM = "=EF=BB=BF"; public String UTF16_BOM1 = "=FF=FE"; public String UTF16_BOM2 = "=FE=FF"; } 

La clase de analizador principal ...

 /** * This program and the accompanying materials are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html */ package com.test.mht.core; import java.io.BufferedOutputStream; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.File; import java.io.FileOutputStream; import java.io.FileReader; import java.io.OutputStreamWriter; import java.util.regex.Matcher; import java.util.regex.Pattern; import sun.misc.BASE64Decoder; /** * File to parse and decompose *.mts file in its constituting parts. * @author Manish Shukla */ public class MHTParser implements IConstants { private File mhtFile; private File outputFolder; public MHTParser(File mhtFile, File outputFolder) { this.mhtFile = mhtFile; this.outputFolder = outputFolder; } /** * @throws Exception */ public void decompress() throws Exception { BufferedReader reader = null; String type = ""; String encoding = ""; String location = ""; String filename = ""; String charset = "utf-8"; StringBuilder buffer = null; try { reader = new BufferedReader(new FileReader(mhtFile)); final String boundary = getBoundary(reader); if(boundary == null) throw new Exception("Failed to find document 'boundary'... Aborting"); String line = null; int i = 1; while((line = reader.readLine()) != null) { String temp = line.trim(); if(temp.contains(boundary)) { if(buffer != null) { writeBufferContentToFile(buffer,encoding,filename,charset); buffer = null; } buffer = new StringBuilder(); }else if(temp.startsWith(CONTENT_TYPE)) { type = getType(temp); }else if(temp.startsWith(CHAR_SET)) { charset = getCharSet(temp); }else if(temp.startsWith(CONTENT_TRANSFER_ENCODING)) { encoding = getEncoding(temp); }else if(temp.startsWith(CONTENT_LOCATION)) { location = temp.substring(temp.indexOf(":")+1).trim(); i++; filename = getFileName(location,type); }else { if(buffer != null) { buffer.append(line + "\n"); } } } }finally { if(null != reader) reader.close(); } } private String getCharSet(String temp) { String t = temp.split("=")[1].trim(); return t.substring(1, t.length()-1); } /** * Save the file as per character set and encoding */ private void writeBufferContentToFile(StringBuilder buffer,String encoding, String filename, String charset) throws Exception { if(!outputFolder.exists()) outputFolder.mkdirs(); byte[] content = null; boolean text = true; if(encoding.equalsIgnoreCase("base64")){ content = getBase64EncodedString(buffer); text = false; }else if(encoding.equalsIgnoreCase("quoted-printable")) { content = getQuotedPrintableString(buffer); } else content = buffer.toString().getBytes(); if(!text) { BufferedOutputStream bos = null; try { bos = new BufferedOutputStream(new FileOutputStream(filename)); bos.write(content); bos.flush(); }finally { bos.close(); } }else { BufferedWriter bw = null; try { bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(filename), charset)); bw.write(new String(content)); bw.flush(); }finally { bw.close(); } } } /** * When the save the *.mts file with 'utf-8' encoding then it appends '=EF=BB=BF' * @see http://en.wikipedia.org/wiki/Byte_order_mark */ private byte[] getQuotedPrintableString(StringBuilder buffer) { //Set uniqueHex = new HashSet(); //final Pattern p = Pattern.compile("(=\\p{XDigit}{2})*"); String temp = buffer.toString().replaceAll(UTF8_BOM, "").replaceAll("=\n", ""); //Matcher m = p.matcher(temp); //while(m.find()) { // uniqueHex.add(m.group()); //} //System.out.println(uniqueHex); //for (String hex : uniqueHex) { //temp = temp.replaceAll(hex, getASCIIValue(hex.substring(1))); //} return temp.getBytes(); } /*private String getASCIIValue(String hex) { return ""+(char)Integer.parseInt(hex, 16); }*/ /** * Although system dependent..it works well */ private byte[] getBase64EncodedString(StringBuilder buffer) throws Exception { return new BASE64Decoder().decodeBuffer(buffer.toString()); } /** * Tries to get a qualified file name. If the name is not apparent it tries to guess it from the URL. * Otherwise it returns 'unknown.' */ private String getFileName(String location, String type) { final Pattern p = Pattern.compile("(\\w|_|-)+\\.\\w+"); String ext = ""; String name = ""; if(type.toLowerCase().endsWith("jpeg")) ext = "jpg"; else ext = type.split("/")[1]; if(location.endsWith("/")) { name = "main"; }else { name = location.substring(location.lastIndexOf("/") + 1); Matcher m = p.matcher(name); String fname = ""; while(m.find()) { fname = m.group(); } if(fname.trim().length() == 0) name = "unknown"; else return getUniqueName(fname.substring(0,fname.indexOf(".")), fname.substring(fname.indexOf(".") + 1, fname.length())); } return getUniqueName(name,ext); } /** * Returns a qualified unique output file path for the parsed path. * In case the file already exist it appends a numarical value a continues */ private String getUniqueName(String name,String ext) { int i = 1; File file = new File(outputFolder,name + "." + ext); if(file.exists()) { while(true) { file = new File(outputFolder, name + i + "." + ext); if(!file.exists()) return file.getAbsolutePath(); i++; } } return file.getAbsolutePath(); } private String getType(String line) { return splitUsingColonSpace(line); } private String getEncoding(String line){ return splitUsingColonSpace(line); } private String splitUsingColonSpace(String line) { return line.split(":\\s*")[1].replaceAll(";", ""); } /** * Gives you the boundary string */ private String getBoundary(BufferedReader reader) throws Exception { String line = null; while((line = reader.readLine()) != null) { line = line.trim(); if(line.startsWith(BOUNDARY)) { return line.substring(line.indexOf("\"") + 1, line.lastIndexOf("\"")); } } return null; } } 

Saludos,

No tienes que hacerlo por tu cuenta.

Con dependencia

  org.apache.james apache-mime4j 0.7.2  

Rodar tu archivo

 public static void main(String[] args) { MessageTree.main(new String[]{"YOU MHT FILE PATH"}); } 

MessageTree hará

 /** * Displays a parsed Message in a window. The window will be divided into * two panels. The left panel displays the Message tree. Clicking on a * node in the tree shows information on that node in the right panel. * * Some of this code have been copied from the Java tutorial's JTree section. */ 

Entonces puedes mirarlo.

😉

U puede probar http://www.chilkatsoft.com/mht-features.asp , puede empaquetar / descomprimir y puede manejarlo como archivos normales. El enlace de descarga es: http://www.chilkatsoft.com/java.asp

fui utilizado http://jtidy.sourceforge.net para analizar / leer / indexar archivos mht (pero como archivos normales, no comprimidos)

Tarde a la fiesta, pero expandiendo la respuesta de @wener para cualquier persona que tropiece con esto.

La biblioteca Apache Mime4J parece tener la solución más accesible para el procesamiento EML o MHTML , ¡mucho más fácil que hacerlo por sí mismo!

La función ‘ parseMhtToFile ‘ de mi prototipo a continuación parseMhtToFile archivos html y otros artefactos de un archivo ‘mht’ de informe activo de Cognos, pero podría adaptarse a otros fines.

Esto está escrito en Groovy y requiere Apache Mime4J ‘core’ y ‘dom’ jars (actualmente 0.7.2).

 import org.apache.james.mime4j.dom.Message import org.apache.james.mime4j.dom.Multipart import org.apache.james.mime4j.dom.field.ContentTypeField import org.apache.james.mime4j.message.DefaultMessageBuilder import org.apache.james.mime4j.stream.MimeConfig /** * Use Mime4J MessageBuilder to parse an mhtml file (assumes multipart) into * separate html files. * Files will be written to outDir (or parent) as baseName + partIdx + ext. */ void parseMhtToFile(File mhtFile, File outDir = null) { if (!outDir) {outDir = mhtFile.parentFile } // File baseName will be used in generating new filenames def mhtBaseName = mhtFile.name.replaceFirst(~/\.[^\.]+$/, '') // -- Set up Mime parser, using Default Message Builder MimeConfig parserConfig = new MimeConfig(); parserConfig.setMaxHeaderLen(-1); // The default is a mere 10k parserConfig.setMaxLineLen(-1); // The default is only 1000 characters. parserConfig.setMaxHeaderCount(-1); // Disable the check for header count. DefaultMessageBuilder builder = new DefaultMessageBuilder(); builder.setMimeEntityConfig(parserConfig); // -- Parse the MHT stream data into a Message object println "Parsing ${mhtFile}..."; InputStream mhtStream = mhtFile.newInputStream() Message message = builder.parseMessage(mhtStream); // -- Process the resulting body parts, writing to file assert message.getBody() instanceof Multipart Multipart multipart = (Multipart) message.getBody(); def parts = multipart.getBodyParts(); parts.eachWithIndex { p, i -> ContentTypeField cType = p.header.getField('content-type') println "${p.class.simpleName}\t${i}\t${cType.mimeType}" // Assume mime sub-type is a "good enough" file-name extension // eg text/html = html, image/png = png, application/json = json String partFileName = "${mhtBaseName}_${i}.${cType.subType}" File partFile = new File(outDir, partFileName) // Write part body stream to file println "Writing ${partFile}..."; if (partFile.exists()) partFile.delete(); InputStream partStream = p.body.inputStream; partFile.append(partStream); } } 

El uso es simple:

 File mhtFile = new File('', 'Report-en-au.mht') parseMhtToFile(mhtFile) println 'Done.' 

La salida es:

 Parsing \Report-en-au.mht... BodyPart 0 text/html Writing \Report-en-au_0.html... BodyPart 1 image/png Writing \Report-en-au_1.png... Done. 

Pensamientos sobre otras mejoras:

  • Para las partes de mime de “texto” , puede acceder a un Reader lugar de a un Stream que podría ser más apropiado para la minería de textos como lo solicitó el OP.

  • Para las extensiones de nombre de archivo generadas, usaría otra biblioteca para buscar la extensión apropiada, no asumir que el subtipo de mimo es adecuado.

  • Manejar archivos de mhtml Multiparte de cuerpo único (no multiparte) y recursivo y otras complejidades. Estos pueden requerir un MimeStreamParser con implementación personalizada de Content Handler .