如何构建 HTML org.w3c.dom.Document?

2022-09-04 21:38:23

文档界面的文档将界面描述为:

文档接口表示整个 HTML 或 XML 文档。

javax.xml.parsers.DocumentBuilder构建 XML。但是,我无法找到一种方法来构建一个HTML!DocumentDocumentDocument

我想要一个HTML,因为我正在尝试构建一个文档,然后将其传递给一个需要HTML的库。此库以不区分大小写的方式使用,这对于 HTML 很好,但对于 XML 则不然。DocumentDocumentDocument#getElementsByTagName(String tagname)

我环顾四周,什么也没找到。像如何将网页的Html源代码转换为java中的org.w3c.dom.Document之类的项目实际上没有答案。


答案 1

您似乎有两个明确的要求:

  1. 您需要将 HTML 表示为 .org.w3c.dom.Document
  2. 您需要以不区分大小写的方式进行操作。Document#getElementsByTagName(String tagname)

如果你正在尝试使用 org.w3c.dom.Document 使用 HTML,那么我假设你正在使用某种风格的 XHTML。因为 XML API(如 DOM)需要格式正确的 XML。HTML不一定是格式正确的XML,但XHTML是格式正确的XML。即使您使用的是 HTML,在尝试通过 XML 解析器运行它之前,也必须进行一些预处理以确保它是格式正确的 XML。首先使用HTML解析器(例如jsoup)解析HTML,然后通过遍历HTML解析器的生成树(在jsoup的情况下)来构建HTML可能更容易。org.w3c.dom.Documentorg.jsoup.nodes.Document


有一个 org.w3c.dom.html.HTMLDocument 接口,它扩展了 .我发现的唯一实现是在Xerces-j(2.11.0)中,形式为org.apache.html.dom.HTMLDocumentImpl。乍一看,这似乎是有希望的,但经过仔细研究,我们发现存在一些问题。org.w3c.dom.Document

1. 没有一个明确的、“干净”的方法来获取实现 org.w3c.dom..html.HTMLDocument 接口的对象实例。

使用 Xerces,我们通常会通过以下方式使用 a 获取对象:DocumentDocumentBuilder

DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
Document doc = builder.newDocument();
//or doc = builder.parse(xmlFile) if parsing from a file

或使用各种:DOMImplementation

DOMImplementationRegistry registry = DOMImplementationRegistry.newInstance();
DOMImplementationLS impl = (DOMImplementationLS)registry.getDOMImplementation("LS");
LSParser lsParser = impl.createLSParser(DOMImplementationLS.MODE_SYNCHRONOUS, null);
Document document = lsParser.parseURI("myFile.xml");

在这两种情况下,我们纯粹使用接口来获取对象。org.w3c.dom.*Document

我发现最接近的等价物是这样的:HTMLDocument

HTMLDOMImplementation htmlDocImpl = HTMLDOMImplementationImpl.getHTMLDOMImplementation();
HTMLDocument htmlDoc = htmlDocImpl.createHTMLDocument("My Title");

这要求我们直接实例化内部实现类,使我们的实现依赖于Xerces。

(注意:我还看到Xerces还有一个内部HTMLBuilder(它实现了已弃用的DocumentHandler),据说可以使用SAX解析器生成HTMLDocument,但我没有费心研究它。

2. org.w3c.dom.html.HTMLDocument 不能生成正确的 XHTML。

虽然,您可以使用不区分大小写的方式搜索树,但所有元素名称都保存在内部的所有大写字母中。但是 XHTML 元素和属性名称应该全部小写。(这可以通过遍历整个文档树并使用 的方法将所有元素的名称更改为小写来解决。HTMLDocumentgetElementsByTagName(String tagname)DocumentrenameNode()

此外,XHTML 文档应该具有正确的 DOCTYPE 声明XHTML 命名空间的 xmlns 声明。似乎没有一种直接的方法将它们设置在中(除非你对内部Xerces实现进行了一些摆弄)。HTMLDocument

3. org.w3c.dom.html.HTMLDocument 几乎没有文档,Xerces 接口的实现似乎不完整。

我没有搜索整个互联网,但我找到的唯一文档是以前链接的JavaDocs,以及Xerces内部实现源代码中的注释。在这些评论中,我还发现了界面的几个不同部分没有实现的注释。(旁注:我真的有这样的印象,org.w3c.dom.html.HTMLDocument接口本身并没有被任何人真正使用,也许它本身就不完整。HTMLDocument


出于这些原因,我认为最好避免并尽我们所能。我们能做些什么?org.w3c.dom.html.HTMLDocumentorg.w3c.dom.Document

好吧,一种方法是扩展(扩展哪个实现)。这种方法不需要太多的代码,但它仍然使我们的实现依赖于Xerces,因为我们正在扩展。在我们的 中,我们只是在元素创建和搜索时将所有标签名称转换为小写。这将允许以不区分大小写的方式使用 。org.apache.xerces.dom.DocumentImplorg.apache.xerces.dom.CoreDocumentImplorg.w3c.dom.DocumentDocumentImplMyHTMLDocumentImplDocument#getElementsByTagName(String tagname)

MyHTMLDocumentImpl:

import org.apache.xerces.dom.DocumentImpl;
import org.apache.xerces.dom.DocumentTypeImpl;
import org.w3c.dom.DOMException;
import org.w3c.dom.Document;
import org.w3c.dom.DocumentType;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

//a base class somewhere in the hierarchy implements org.w3c.dom.Document
public class MyHTMLDocumentImpl extends DocumentImpl {

    private static final long serialVersionUID = 1658286253541962623L;


    /**
     * Creates an Document with basic elements required to meet
     * the <a href="http://www.w3.org/TR/xhtml1/#strict">XHTML standards</a>.
     * <pre>
     * {@code
     * <?xml version="1.0" encoding="UTF-8"?>
     * <!DOCTYPE html 
     *     PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" 
     *     "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
     * <html xmlns="http://www.w3.org/1999/xhtml">
     *     <head>
     *         <title>My Title</title>
     *     </head>
     *     <body/>
     * </html>
     * }
     * </pre>
     * 
     * @param title desired text content for title tag. If null, no text will be added.
     * @return basic HTML Document. 
     */
    public static Document makeBasicHtmlDoc(String title) {
        Document htmlDoc = new MyHTMLDocumentImpl();
        DocumentType docType = new DocumentTypeImpl(null, "html",
                "-//W3C//DTD XHTML 1.0 Strict//EN",
                "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd");
        htmlDoc.appendChild(docType);
        Element htmlElement = htmlDoc.createElementNS("http://www.w3.org/1999/xhtml", "html");
        htmlDoc.appendChild(htmlElement);
        Element headElement = htmlDoc.createElement("head");
        htmlElement.appendChild(headElement);
        Element titleElement = htmlDoc.createElement("title");
        if(title != null)
            titleElement.setTextContent(title);
        headElement.appendChild(titleElement);
        Element bodyElement = htmlDoc.createElement("body");
        htmlElement.appendChild(bodyElement);

        return htmlDoc;
    }

    /**
     * This method will allow us to create a our
     * MyHTMLDocumentImpl from an existing Document.
     */
    public static Document createFrom(Document doc) {
        Document htmlDoc = new MyHTMLDocumentImpl();
        DocumentType originDocType = doc.getDoctype();
        if(originDocType != null) {
            DocumentType docType = new DocumentTypeImpl(null, originDocType.getName(),
                    originDocType.getPublicId(),
                    originDocType.getSystemId());
            htmlDoc.appendChild(docType);
        }
        Node docElement = doc.getDocumentElement();
        if(docElement != null) {
            Node copiedDocElement = docElement.cloneNode(true);
            htmlDoc.adoptNode(copiedDocElement);
            htmlDoc.appendChild(copiedDocElement);
        }
        return htmlDoc;
    }

    private MyHTMLDocumentImpl() {
        super();
    }

    @Override
    public Element createElement(String tagName) throws DOMException {
        return super.createElement(tagName.toLowerCase());
    }

    @Override
    public Element createElementNS(String namespaceURI, String qualifiedName) throws DOMException {
        return super.createElementNS(namespaceURI, qualifiedName.toLowerCase());
    }

    @Override
    public NodeList getElementsByTagName(String tagname) {
        return super.getElementsByTagName(tagname.toLowerCase());
    }

    @Override
    public NodeList getElementsByTagNameNS(String namespaceURI, String localName) {
        return super.getElementsByTagNameNS(namespaceURI, localName.toLowerCase());
    }

    @Override
    public Node renameNode(Node n, String namespaceURI, String qualifiedName) throws DOMException {
        return super.renameNode(n, namespaceURI, qualifiedName.toLowerCase());
    }
}

测试 仪:

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;

import org.w3c.dom.DOMConfiguration;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.w3c.dom.bootstrap.DOMImplementationRegistry;
import org.w3c.dom.ls.DOMImplementationLS;
import org.w3c.dom.ls.LSOutput;
import org.w3c.dom.ls.LSSerializer;


public class HTMLDocumentTest {

    private final static int P_ELEMENT_NUM = 3;

    public static void main(String[] args) //I'm throwing all my exceptions here to shorten the example, but obviously you should handle them appropriately.
            throws ClassNotFoundException, InstantiationException, IllegalAccessException, ClassCastException, IOException {

        Document htmlDoc = MyHTMLDocumentImpl.makeBasicHtmlDoc("My Title");

        //populate the html doc with some example content
        Element bodyElement = (Element) htmlDoc.getElementsByTagName("body").item(0);
        for(int i = 0; i < P_ELEMENT_NUM; ++i) {
            Element pElement = htmlDoc.createElement("p");
            String id = Integer.toString(i+1);
            pElement.setAttribute("id", "anId"+id);
            pElement.setTextContent("Here is some text"+id+".");
            bodyElement.appendChild(pElement);
        }

        //get the title element in a case insensitive manner.
        NodeList titleNodeList = htmlDoc.getElementsByTagName("tItLe");
        for(int i = 0; i < titleNodeList.getLength(); ++i)
            System.out.println(titleNodeList.item(i).getTextContent());

        System.out.println();

        {//get all p elements searching with lowercase
            NodeList pNodeList = htmlDoc.getElementsByTagName("p");
            for(int i = 0; i < pNodeList.getLength(); ++i) {
                System.out.println(pNodeList.item(i).getTextContent());
            }
        }

        System.out.println();

        {//get all p elements searching with uppercase
            NodeList pNodeList = htmlDoc.getElementsByTagName("P");
            for(int i = 0; i < pNodeList.getLength(); ++i) {
                System.out.println(pNodeList.item(i).getTextContent());
            }
        }

        System.out.println();

        //to serialize
        DOMImplementationRegistry registry = DOMImplementationRegistry.newInstance();
        DOMImplementationLS domImplLS = (DOMImplementationLS) registry.getDOMImplementation("LS");

        LSSerializer lsSerializer = domImplLS.createLSSerializer();
        DOMConfiguration domConfig = lsSerializer.getDomConfig();
        domConfig.setParameter("format-pretty-print", true);  //if you want it pretty and indented

        LSOutput lsOutput = domImplLS.createLSOutput();
        lsOutput.setEncoding("UTF-8");

        //to write to file
        try (OutputStream os = new FileOutputStream(new File("myFile.html"))) {
            lsOutput.setByteStream(os);
            lsSerializer.write(htmlDoc, lsOutput);
        }

        //to print to screen
        System.out.println(lsSerializer.writeToString(htmlDoc)); 
    }

}

输出:

My Title

Here is some text1.
Here is some text2.
Here is some text3.

Here is some text1.
Here is some text2.
Here is some text3.

<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
    <head>
        <title>My Title</title>
    </head>
    <body>
        <p id="anId1">Here is some text1.</p>
        <p id="anId2">Here is some text2.</p>
        <p id="anId3">Here is some text3.</p>
    </body>
</html>

与上述类似的另一种方法是创建一个包装器来包装对象并实现接口本身。这比“扩展”方法需要更多的代码,但这种方式是“更干净”的,因为我们不必关心特定的实现。这种方法的额外代码并不困难;为方法提供所有这些包装器实现有点乏味。我还没有完全解决这个问题,可能会有一些问题,但如果它有效,这是一般的想法:DocumentDocumentDocumentDocumentImplDocumentDocument

public class MyHTMLDocumentWrapper implements Document {

    private Document doc;

    public MyHTMLDocumentWrapper(Document doc) {
        //...
        this.doc = doc;
        //...
    }

    //...
}

无论是,我上面提到的方法之一,还是其他方法,也许这些建议将帮助您了解如何继续。org.w3c.dom.html.HTMLDocument


编辑:

在尝试解析以下 XHTML 文件时,在我的解析测试中,Xerces 会在实体管理类中挂起,尝试打开 http 连接。为什么我不知道?特别是因为我在一个没有实体的本地html文件上进行了测试。(也许与DOCTYPE或命名空间有关?这是文档:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC 
    "-//W3C//DTD XHTML 1.0 Strict//EN"
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
    <head>
        <title>My Title</title>
    </head>
    <body>
        <p id="anId1">Here is some text1.</p>
        <p id="anId2">Here is some text2.</p>
        <p id="anId3">Here is some text3.</p>
    </body>
</html>

答案 2