2010-07-12 66 views
7

tôi cần phải khai thác các nội dung của hầu hết các tập tin tài liệu tiếng như:Làm thế nào để đọc hoặc phân tích các file MHTML (.mht) trong java

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

Đối với hầu hết các định dạng tập tin tôi đang lên kế hoạch để sử dụng:

http://tika.apache.org/

Nhưng như của bây giờ Tika không hỗ trợ MHTML (* .mht) file .. (http://en.wikipedia.org/wiki/MHTML) Có vài ví dụ trong C# (http://www.codeproject.com/KB/files/MhtBuilder.aspx) nhưng tôi thấy không có gì trong Java.

Tôi đã thử mở tệp * .mht trong 7Zip và không thành công ... Mặc dù WinZip có thể giải nén tệp thành hình ảnh và văn bản (CSS, HTML, Tập lệnh) dưới dạng văn bản và tệp nhị phân ...

theo MSDN trang (http://msdn.microsoft.com/en-us/library/aa767785%28VS.85%29.aspx#compress_content) và code project trang tôi đã đề cập trước đó ... file MHT sử dụng Gzip nén ....

Cố gắng giải nén trong kết quả java trong trường hợp ngoại lệ sau: với 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) 

Và với 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) 

Vui lòng đề nghị làm thế nào để giải nén nó ....

Cảm ơn ....

Trả lời

11

Thẳng thắn mà nói, tôi đã không mong đợi một giải pháp trong thời gian tới và đã sắp bỏ cuộc, nhưng một số làm thế nào tôi vấp trên trang này:

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

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

Mặc dù, không phải là một rất hấp dẫn trong cái nhìn đầu tiên. Nhưng nếu bạn nhìn kỹ, bạn sẽ có được đầu mối. Sau khi đọc xong, tôi đã kích hoạt trình duyệt IE của mình và bắt đầu lưu các trang như là *.mht. Hãy để tôi đi từng dòng ...

Nhưng hãy để tôi giải thích trước rằng mục tiêu cuối cùng của tôi là tách/giải nén nội dung html và phân tích cú pháp ... giải pháp không hoàn chỉnh vì nó phụ thuộc vào character set hoặc encoding Tôi chọn trong khi lưu. Nhưng ngay cả khi nó sẽ trích xuất các tập tin cá nhân với những trở ngại nhỏ ...

Tôi hy vọng điều này sẽ hữu ích cho bất cứ ai đang cố gắng để phân tích/giải nén file *.mht/MHTML :)

Giải thích ======= ======== ** Taken từ một MHT tập tin **

From: "Saved by Windows Internet Explorer 7" 

Đây là phần mềm sử dụng để lưu các tập tin

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

Chủ đề, ngày tháng và mime-phiên bản ... giống như những định dạng email

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

Đây là phần cho chúng tôi biết đây là tài liệu multipart. Tài liệu nhiều phần có một hoặc nhiều tập hợp dữ liệu khác nhau được kết hợp trong một thân, một trường loại nội dung multipart phải xuất hiện trong tiêu đề của thực thể. Ở đây, chúng ta cũng có thể thấy kiểu là "text/html".

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

Trong số tất cả điều này là phần quan trọng nhất. Đây là dấu phân cách duy nhất phân chia hai phần khác nhau (html, hình ảnh, css, tập lệnh, v.v.). Sau khi bạn có được điều này, mọi thứ trở nên dễ dàng ... Bây giờ, tôi chỉ phải lặp qua tài liệu và tìm ra các phần khác nhau và lưu chúng theo Content-Transfer-Encoding (base64, có thể in được, vv) ... . . .

MẪU

------=_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 MÃ **

Một giao diện để xác định hằng số.

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"; 
} 

Lớp phân tích cú pháp chính ...

/** 
* 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; 
    } 
} 

Kính trọng,

+0

Cảm ơn bạn đã chia sẻ điều này. Tôi có một câu hỏi mặc dù: Làm thế nào để bạn xác định kết thúc của một phần HEADER? Tôi có nghĩa là, phần bắt đầu với ranh giới được xác định ở đâu đó ở trên, sau đó có tiêu đề như kiểu/mã hóa/vị trí vv, và sau này dường như với tôi rằng mỗi tiêu đề được giới hạn bởi ASCII 0D0A0D0A (hai lần CR + LF). Tôi có đúng về điều này, hoặc có bất kỳ dấu phân cách nào khác được bảo đảm không? –

+0

@favonius: Mã của bạn có chuyển đổi tệp .mht thành html không? –

+0

@UmeshKumar: Mã được trích xuất dữ liệu. Nhưng tôi nghĩ rằng nó nên được đơn giản để chuyển đổi mht sang html. Google xung quanh có thể là một thư viện đã tồn tại. – Favonius

0

U có thể thử http://www.chilkatsoft.com/mht-features.asp, nó có thể đóng gói/giải nén và bạn có thể xử lý nó sau các tệp bình thường. Liên kết tải xuống là: http://www.chilkatsoft.com/java.asp

+0

Cảm ơn Roki .. Tôi đã thấy các dịch vụ chilkatsoft nhưng không thể sử dụng nó vì: 1. Nó đang đến như là một thực hiện bản địa 'chilkat.dll' .. Tôi yêu cầu cái gì đó được viết bằng java tinh khiết . 2. Nó là một sản phẩm thương mại .. và trong dự án của tôi tôi đang sử dụng các sản phẩm nguồn mở ...: o Dù sao cảm ơn :) – Favonius

0

tôi đã được sử dụng để phân tích http://jtidy.sourceforge.net/đọc file/index MHT (nhưng tệp như bình thường, không phải file nén)

+0

Cảm ơn Wajdy Essam ... Hầu hết các tệp * .mht được lưu từ IE bằng 'lưu dưới dạng' tùy chọn ... và theo mặc định chúng được nén ... Nhưng tôi vẫn sẽ thử' jtidy' ... – Favonius

1

Bạn không cần phải làm điều đó trên bạn sở hữu.

Với sự phụ thuộc

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

cuộn bạn MHT nộp

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

MessageTree sẽ

/** 
* 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. 
*/ 

Sau đó, bạn có thể nhìn vào nó.

;-)

0

muộn để đảng, nhưng mở rộng về câu trả lời @wener 's cho bất cứ ai khác vấp ngã trên này.

Thư viện Apache Mime4J dường như có giải pháp dễ tiếp cận nhất để xử lý EML or MHTML, dễ dàng hơn nhiều so với cách bạn tự tạo!

Chức năng 'parseMhtToFile' nguyên mẫu của tôi dưới đây tách các tệp html và các tạo tác khác ra khỏi tệp báo cáo hoạt động 'mht' Cognos, nhưng có thể được điều chỉnh cho các mục đích khác.

Điều này được viết bằng Groovy và yêu cầu Apache Mime4J 'core' and 'dom' jars (hiện tại là 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); 
    } 
} 

Cách sử dụng đơn giản chỉ là:

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

Output là:

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. 

suy nghĩ về cải tiến khác:

  • Đối 'text' mime parts, bạn có thể truy cập vào một Reader ins tead của một Stream có thể thích hợp hơn cho khai thác văn bản như OP được yêu cầu.

  • Đối với phần mở rộng tên tệp được tạo, tôi sẽ sử dụng một thư viện khác để tra cứu tiện ích thích hợp, không giả định loại phụ mime là đủ.

  • Xử lý các tệp mhtml nhiều phần (không phải Multipart) và các tệp mhtml Multipart đệ quy và các phức tạp khác. Đây có thể yêu cầu một MimeStreamParser với tùy chỉnh Content Handler triển khai.

Các vấn đề liên quan