2010-07-12 35 views
7

necesito para extraer el contenido de de la mayoría de los archivos de documentos conocidos como:leer ni analizar archivos MHTML (.mht) en Java

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

para la mayoría de estos formatos de archivo que estoy planeando utilizar:

http://tika.apache.org/

Pero a partir de ahora Tika no soporta MHTML (* .mht) archivos .. (http://en.wikipedia.org/wiki/MHTML) Hay pocos ejemplos en C# (http://www.codeproject.com/KB/files/MhtBuilder.aspx) y no lo hallé en Java.

he intentado abrir el archivo * .mht en 7Zip y fracasó ... Aunque el WinZip fue capaz de descomprimir el archivo en imágenes y texto (CSS, HTML, escritura) como el texto y archivos binarios ...

de acuerdo con la página de MSDN (http://msdn.microsoft.com/en-us/library/aa767785%28VS.85%29.aspx#compress_content) y la página code project he mencionado antes ... archivos MHT utilizar la compresión GZip ....

el intento de descomprimir en los resultados de java en 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.<init>(Unknown Source) 
at java.util.zip.GZIPInputStream.<init>(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.<init>(Unknown Source) 
at java.util.zip.ZipFile.<init>(Unknown Source) 
at GZipTest.main(GZipTest.java:21) 

favor sugerir cómo descomprimirlo ....

Gracias ....

Respuesta

11

Francamente, no esperaba una solución en un futuro cercano y estaba a punto de darse por vencido, pero de alguna manera 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 una muy pegadiza en la primera mirada. Pero si miras con cuidado obtendrás una pista. Después de leer esto, encendí 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 objetivo final era separar/extraer el contenido html y analizarlo ... la solución no es completa en sí misma, ya que depende de character set o encoding Elijo mientras guardo. Pero a pesar de que extraerá los archivos individuales con pequeños enganches ...

espero que esto será útil para cualquier persona que está tratando de analizar/descomprimir archivos *.mht/MHTML :)

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

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 

asunto, la fecha y el mimo-versión ... al igual que el formato de correo

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

Esta es la parte que nos dice que es un documento 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 te haces cargo de esto, todo se pone 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" = 
. 
. 
. 

** JAVA CÓDIGO **

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 principal analizador ...

/** 
* 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'</br> 
    * @see http://en.wikipedia.org/wiki/Byte_order_mark 
    */ 
    private byte[] getQuotedPrintableString(StringBuilder buffer) 
    { 
     //Set<String> uniqueHex = new HashSet<String>(); 
     //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.<type>' 
    */ 
    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.</br> 
    * 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,

+0

Gracias por compartir esto. Sin embargo, tengo una pregunta: ¿cómo se determina el final de una sección HEADER? Quiero decir, la sección comienza con el límite definido en algún lugar de arriba, luego aparecen los encabezados como tipo/codificación/ubicación, etc. y después de esto, me parece que cada encabezado está delimitado por ASCII 0D0A0D0A (dos veces CR + LF). ¿Estoy en lo cierto al respecto, o hay algún otro delimitador garantizado? –

+0

@favonius: ¿Su código convierte el archivo .mht a html? –

+0

@UmeshKumar: el código era para la extracción de datos. Pero creo que debería ser sencillo convertir mht a html. Google alrededor puede ser una biblioteca ya existe. – Favonius

0

U puede tratar http://www.chilkatsoft.com/mht-features.asp, se puede empaquetar/desempaquetar y se puede manejar después como archivos normales. El enlace de descarga es: http://www.chilkatsoft.com/java.asp

+0

Gracias Roki .. me han visto las ofertas chilkatsoft pero no podían utilizarlo porque: 1. Está llegando como una aplicación nativa 'chilkat.dll' .. requiero algo que está escrito en Java puro . 2. Es un producto comercial ... y en mi proyecto estoy usando productos de código abierto ...: o De todos modos, gracias :) – Favonius

0

que estaba acostumbrado a analizar http://jtidy.sourceforge.net/leer archivos/índice MHT (pero como archivos normales, no archivos comprimidos)

+0

Gracias Wajdy Essam ... La mayoría de los archivos * .mht se guardan de IE usando 'save as' option ... y de forma predeterminada están comprimidos ... Pero aún así intentaré' jtidy' ... – Favonius

1

Usted no tiene que hacerlo sobre su propia.

Con dependencia

<dependency> 
    <groupId>org.apache.james</groupId> 
    <artifactId>apache-mime4j</artifactId> 
    <version>0.7.2</version> 
</dependency> 

rollo que el archivo MHT

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

MessageTree se

/** 
* 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 usted puede mirar en ella.

;-)

0

tarde a la fiesta, pero en expansión en respuesta @wener 's para cualquier otra persona tropezarse con esto.

La biblioteca Apache Mime4J parece tener la solución más accesible para el proceso EML or MHTML, ¡mucho más fácil que enrollarla!

La función de mi prototipo 'parseMhtToFile' a continuación descomprime 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' and '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 
     // e.g. 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('<path>', 'Report-en-au.mht') 
parseMhtToFile(mhtFile) 
println 'Done.' 

de salida es:

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

Reflexiones sobre otras mejoras:

  • Para 'text' mime parts, puede acceder a un Reader ins Tead de un Stream que podría ser más apropiado para la minería de textos como el PO solicitado.

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

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

Cuestiones relacionadas