解析格式错误的XML文档(如HTML文件)

时间:2010-12-09 18:07:50

标签: java html xml xml-parsing

在解析之后,我想删除危险的代码并再次将其格式化。

目的是防止脚本通过电子邮件进入,但仍然允许大量错误的HTML工作(至少不会完全失败)。

有没有图书馆?有没有更好的方法让脚本远离浏览器?

重要的是该程序不会抛出Parse异常。该程序可能会做出最好的猜测,即使它是错误的,也可以接受。

编辑:如果您认为哪些解析器更好以及为什么会有任何意见,我将不胜感激。

4 个答案:

答案 0 :(得分:3)

对于灵活的解析,您可能需要查看JSoup。但白名单是这里的方式。如果你只是禁止一堆“危险”元素,有人可能会找到一种方法来解析你的解析器。相反,你应该只允许一小部分安全元素。

答案 1 :(得分:0)

使用一种将HTML转换为XHTML的可用工具。

例如

http://www.chilkatsoft.com/java-html.asp

http://java-source.net/open-source/html-parsers

http://htmlcleaner.sourceforge.net/

然后使用常规XML解析器。

答案 2 :(得分:0)

我为此目的使用Jericho HTML parser

他们的消毒剂示例的一些调整版本:

public class HtmlSanitizer {

private HtmlSanitizer() {
}

private static final Set<String> VALID_ELEMENTS = Sets.newHashSet(DIV, BR,
        P, B, I, OL, UL, LI, A, STRONG, SPAN, EM, TT, IMG);


private static final Set<String> VALID_ATTRIBUTES = Sets.newHashSet("id",
        "class", "href", "target", "title", "src");

private static final Object VALID_MARKER = new Object();

public static void sanitize(Reader r, Writer w) {
    try {
        sanitize(new Source(r)).writeTo(w);
        w.flush();
        r.close();
    } catch (IOException ioe) {
        throw new RuntimeException("error during sanitize", ioe);
    }
}

public static OutputDocument sanitize(Source source) {
    source.fullSequentialParse();
    OutputDocument doc = new OutputDocument(source);
    List<Tag> tags = source.getAllTags();
    int pos = 0;
    for (Tag tag : tags) {
        if (processTag(tag, doc))
            tag.setUserData(VALID_MARKER);
        else
            doc.remove(tag);
        reencodeTextSegment(source, doc, pos, tag.getBegin());
        pos = tag.getEnd();
    }
    reencodeTextSegment(source, doc, pos, source.getEnd());
    return doc;
}

private static boolean processTag(Tag tag, OutputDocument doc) {
    String elementName = tag.getName();
    if (!VALID_ELEMENTS.contains(elementName))
        return false;
    if (tag.getTagType() == StartTagType.NORMAL) {
        Element element = tag.getElement();
        if (HTMLElements.getEndTagRequiredElementNames().contains(
                elementName)) {
            if (element.getEndTag() == null)
                return false;
        } else if (HTMLElements.getEndTagOptionalElementNames().contains(
                elementName)) {
            if (elementName == HTMLElementName.LI && !isValidLITag(tag))
                return false;
            if (element.getEndTag() == null)
                doc.insert(element.getEnd(), getEndTagHTML(elementName));

        }
        doc.replace(tag, getStartTagHTML(element.getStartTag()));
    } else if (tag.getTagType() == EndTagType.NORMAL) {
        if (tag.getElement() == null)
            return false;
        if (elementName == HTMLElementName.LI && !isValidLITag(tag))
            return false;
        doc.replace(tag, getEndTagHTML(elementName));
    } else {
        return false;
    }
    return true;
}

private static boolean isValidLITag(Tag tag) {
    Element parentElement = tag.getElement().getParentElement();
    if (parentElement == null
            || parentElement.getStartTag().getUserData() != VALID_MARKER)
        return false;
    return parentElement.getName() == HTMLElementName.UL
            || parentElement.getName() == HTMLElementName.OL;
}

private static void reencodeTextSegment(Source source, OutputDocument doc,
        int begin, int end) {
    if (begin >= end)
        return;
    Segment textSegment = new Segment(source, begin, end);
    String encodedText = encode(decode(textSegment));
    doc.replace(textSegment, encodedText);
}

private static CharSequence getStartTagHTML(StartTag startTag) {
    StringBuilder sb = new StringBuilder();
    sb.append('<').append(startTag.getName());
    for (Attribute attribute : startTag.getAttributes()) {
        if (VALID_ATTRIBUTES.contains(attribute.getKey())) {
            sb.append(' ').append(attribute.getName());
            if (attribute.getValue() != null) {
                sb.append("=\"");
                sb.append(CharacterReference.encode(attribute.getValue()));
                sb.append('"');
            }
        }
    }
    if (startTag.getElement().getEndTag() == null
            && !HTMLElements.getEndTagOptionalElementNames().contains(
                    startTag.getName()))
        sb.append('/');
    sb.append('>');
    return sb;
}

private static String getEndTagHTML(String tagName) {
    return "</" + tagName + '>';
}

}

答案 3 :(得分:0)

查看http://nekohtml.sourceforge.net/它具有内置的标记平衡功能。 另请查看Nekohtml http://nekohtml.sourceforge.net/filters.html#filters.removing的自定义过滤器部分。这是一个非常好的HTML解析器。