如何在java中读取或解析MHTML(.mht)文件

时间:2010-07-12 16:35:04

标签: java parsing compression mhtml

我需要挖掘大多数已知文档文件的内容,例如:

  1. PDF
  2. HTML
  3. doc / docx等。
  4. 对于我计划使用的大多数文件格式:

    http://tika.apache.org/

    但截至目前Tika不支持MHTML(* .mht)文件..(http://en.wikipedia.org/wiki/MHTML) C#(http://www.codeproject.com/KB/files/MhtBuilder.aspx)中的例子很少,但我在Java中找不到。

    我尝试在7Zip中打开* .mht文件但失败了......虽然WinZip能够将文件解压缩为图像和文本(CSS,HTML,Script)作为文本和二进制文件...

    根据MSDN页面(http://msdn.microsoft.com/en-us/library/aa767785%28VS.85%29.aspx#compress_content)和前面提到的code project页面... mht文件使用GZip压缩....

    尝试在java中解压缩导致以下异常: 使用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)
    

    使用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)
    

    请建议如何解压缩....

    ...谢谢

6 个答案:

答案 0 :(得分:13)

坦率地说,我不期待在不久的将来找到解决方案并且即将放弃,但有些我偶然发现了这个页面:

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

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

虽然,第一眼看上去并不是很吸引人。但如果仔细观察,你会得到线索。阅读本文后,我启动了我的IE并随机开始将页面保存为*.mht文件。让我一行一行......

但是,让我先解释一下,我的最终目标是分离/提取html内容并解析它......解决方案本身并不完整,因为它取决于character set或{ {1}}我在保存时选择。但即使它会轻微地提取单个文件......

我希望这对尝试解析/解压缩encoding个文件的任何人都有用:)

=======说明======== **取自mht文件**

*.mht/MHTML

这是用于保存文件的软件

From: "Saved by Windows Internet Explorer 7"

主题,日期和哑剧版......很像邮件格式

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

这是告诉我们它是 Content-Type: multipart/related; type="text/html"; 文档的部分。多部分文档在一个主体中组合了一个或多个不同的数据集,multipart Content-Type字段必须出现在实体的标题中。在这里,我们还可以将类型视为multipart

"text/html"

最重要的是,这是最重要的部分。这是唯一的分隔符,它分为两个不同的部分(html,图像,css,脚本等)。 一旦你就掌握了这一切,一切都变得轻松......现在,我只需要遍历文档并查找不同的部分并按照boundary="----=_NextPart_000_0007_01CB22D1.93BBD1A0" 保存它们(base64,引用) - 打印等)...     。     。     

<强>样品

Content-Transfer-Encoding

** JAVA CODE **

用于定义常量的接口。

 ------=_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" =
.
.
.

主解析器类......

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

此致

答案 1 :(得分:1)

你不必自己动手。

依赖

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

滚动你的文件

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

MessageTree

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

然后你可以调查一下。

- )

答案 2 :(得分:0)

你可以尝试http://www.chilkatsoft.com/mht-features.asp,它可以打包/解压缩,你可以像普通文件一样处理它。下载链接为:http://www.chilkatsoft.com/java.asp

答案 3 :(得分:0)

我使用http://jtidy.sourceforge.net来解析/读取/索引mht文件(但是作为普通文件,而不是压缩文件)

答案 4 :(得分:0)

参加派对的时间已晚,但要在@wener的答案中扩大其他人的绊脚石。

Apache Mime4J库似乎拥有最便于EML or MHTML处理的解决方案,比滚动自己更容易!

我的原型&#39; parseMhtToFile&#39;下面的函数从Cognos活动报告中删除html文件和其他工件&#39; mht&#39;文件,但可以根据其他目的定制。

这是用Groovy编写的,需要Apache Mime4J 'core' and 'dom' jars(目前为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);
    }
}

用法很简单:

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

输出是:

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.

关于其他改进的想法:

  • 对于'text' mime parts,您可以访问Reader而不是Stream,这可能更适合OP要求的文本挖掘。

  • 对于生成的文件扩展名,我使用另一个库来查找适当的扩展名,而不是假设mime子类型已足够。

  • 处理单体(非Multipart)和递归Multipart mhtml文件及其他复杂性。这些可能需要MimeStreamParser自定义Content Handler实施。

答案 5 :(得分:0)

使用Java Mail API的更紧凑的代码

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.net.URL;
import java.util.Properties;

import javax.mail.BodyPart;
import javax.mail.Session;
import javax.mail.internet.MimeMessage;
import javax.mail.internet.MimeMultipart;

import org.apache.commons.io.IOUtils;

public class MhtParser {

    private File mhtFile;
    private File outputFolder;

    public MhtParser(File mhtFile, File outputFolder) {
        this.mhtFile = mhtFile;
        this.outputFolder = outputFolder;
    }

    public void decompress() throws Exception {
        MimeMessage message = 
            new MimeMessage(
                    Session.getDefaultInstance(new Properties(), null),
                    new FileInputStream(mhtFile));

        if (message.getContent() instanceof MimeMultipart) {
            outputFolder.mkdir();
            MimeMultipart mimeMultipart = (MimeMultipart) message.getContent();

            for (int i = 0; i < mimeMultipart.getCount(); i++) {
                BodyPart bodyPart = mimeMultipart.getBodyPart(i);
                String fileName = bodyPart.getFileName();

                if (fileName == null) {
                    String[] locationHeader = bodyPart.getHeader("Content-Location");
                    if (locationHeader != null && locationHeader.length > 0) {
                        fileName = 
                            new File(new URL(locationHeader[0]).getFile()).getName();
                    }
                }

                if (fileName != null) {
                    FileOutputStream out = 
                        new FileOutputStream(new File(outputFolder, fileName));

                    IOUtils.copy(bodyPart.getInputStream(), out);
                    out.flush();
                    out.close();
                }
            }
        }
    }
}