Java 中文官方教程 2022 版(三十八)(3)

简介: Java 中文官方教程 2022 版(三十八)

Java 中文官方教程 2022 版(三十八)(2)https://developer.aliyun.com/article/1488148

处理验证错误

根据 SAX 标准规定,对验证错误的默认响应是不执行任何操作。JAXP 标准要求抛出 SAX 异常,因此您使用与 SAX 应用程序相同的错误处理机制。特别是,您使用 DocumentBuilder 类的 setErrorHandler 方法来提供一个实现 SAX ErrorHandler 接口的对象。


注意 - DocumentBuilder 还有一个 setEntityResolver 方法可供使用。


以下代码配置文档构建器使用在处理错误中定义的错误处理程序。

DocumentBuilder db = dbf.newDocumentBuilder();
OutputStreamWriter errorWriter = new OutputStreamWriter(System.err,
                                         outputEncoding);
db.setErrorHandler(new MyErrorHandler (new PrintWriter(errorWriter, true)));
Document doc = db.parse(new File(filename));

到目前为止,您看到的代码已经设置了文档构建器,并配置它在请求时执行验证。错误处理也已就位。然而,DOMEcho 还没有做任何事情。在下一节中,您将看到如何显示 DOM 结构并开始探索它。例如,您将看到在 DOM 中实体引用和 CDATA 部分的样子。也许最重要的是,您将看到文本节点(包含实际数据)如何存在于 DOM 中的元素节点下。

显示 DOM 节点

要创建或操作 DOM,有一个清晰的关于 DOM 中节点结构的概念是很有帮助的。本教程的这一部分揭示了 DOM 的内部结构,这样你就可以看到它包含的内容。DOMEcho 示例通过回显 DOM 节点,然后在屏幕上打印出来,适当缩进以使节点层次结构明显可见。这些节点类型的规范可以在DOM Level 2 Core Specification中找到,在Node规范下。下面的表 3-1 是从该规范中调整过来的。

表 3-1 节点类型

Node 节点名称 节点值 属性
Attr 属性名称 属性值 null
CDATASection #cdata-section CDATA 部分的内容 null
Comment #comment 注释的内容 null
Document #document null null
DocumentFragment #documentFragment null null
DocumentType 文档类型名称 null null
Element 标签名称 null null
Entity 实体名称 null null
EntityReference 引用的实体名称 null null
Notation 符号名称 null null
ProcessingInstruction 目标 不包括目标的整个内容 null
Text #text 文本节点的内容 null

此表中的信息非常有用;在处理 DOM 时,你将需要它,因为所有这些类型都混合在 DOM 树中。

获取节点类型信息

通过调用org.w3c.dom.Node类的各种方法来获取 DOM 节点元素类型信息。DOMEcho暴露的节点属性由以下代码回显。

private void printlnCommon(Node n) {
    out.print(" nodeName=\"" + n.getNodeName() + "\"");
    String val = n.getNamespaceURI();
    if (val != null) {
        out.print(" uri=\"" + val + "\"");
    }
    val = n.getPrefix();
    if (val != null) {
        out.print(" pre=\"" + val + "\"");
    }
    val = n.getLocalName();
    if (val != null) {
        out.print(" local=\"" + val + "\"");
    }
    val = n.getNodeValue();
    if (val != null) {
        out.print(" nodeValue=");
        if (val.trim().equals("")) {
            // Whitespace
            out.print("[WS]");
        }
        else {
            out.print("\"" + n.getNodeValue() + "\"");
        }
    }
    out.println();
}

每个 DOM 节点至少有一个类型、一个名称和一个值,这个值可能为空也可能不为空。在上面的示例中,Node接口的getNamespaceURI()getPrefix()getLocalName()getNodeValue()方法返回并打印回显节点的命名空间 URI、命名空间前缀、本地限定名称和值。请注意,对getNodeValue()返回的值调用trim()方法,以确定节点的值是否为空白字符,并相应地打印消息。

要查看Node方法的完整列表以及它们返回的不同信息,请参阅Node的 API 文档。

接下来,定义一个方法来设置节点打印时的缩进,以便节点层次结构能够清晰可见。

private void outputIndentation() {
    for (int i = 0; i < indent; i++) {
        out.print(basicIndent);
    }
}

DOMEcho显示节点树层次结构时,使用的基本缩进单位由DOMEcho构造函数类中添加以下突出显示的行来定义basicIndent常量。

public class DOMEcho {
    static final String outputEncoding = "UTF-8";
    private PrintWriter out;
    private int indent = 0;
    private final String basicIndent = " ";
    DOMEcho(PrintWriter out) {
        this.out = out;
    }
}

就像在处理错误中定义的错误处理程序一样,DOMEcho 程序将创建其输出作为 PrintWriter 实例。

词法控制

词法信息是您需要重建 XML 文档原始语法的信息。在编辑应用程序中保留词法信息非常重要,因为您希望保存的文档是对原始文档的准确反映-包括注释、实体引用以及一开始可能包含的任何 CDATA 部分。

然而,大多数应用程序只关注 XML 结构的内容。它们可以忽略注释,并且不在乎数据是在 CDATA 部分中编码还是作为纯文本,或者是否包含实体引用。对于这类应用程序,最好保留最少的词法信息,因为这简化了应用程序必须准备检查的 DOM 节点的数量和类型。

以下DocumentBuilderFactory方法让您控制在 DOM 中看到的词法信息。

setCoalescing()

CDATA节点转换为Text节点并附加到相邻的Text节点(如果有)。

setExpandEntityReferences()

为了扩展实体引用节点。

setIgnoringComments()

忽略注释。

setIgnoringElementContentWhitespace()

忽略不是元素内容的空白。

所有这些属性的默认值都是 false,这保留了重建传入文档所需的所有词法信息,以其原始形式。将它们设置为 true 可以构建最简单的 DOM,以便应用程序可以专注于数据的语义内容,而不必担心词法语法细节。表 3-2 总结了设置的效果。

表 3-2 词法控制设置

API 保留词法信息 关注内容
setCoalescing() False True
setExpandEntityReferences() False True
setIgnoringComments() False True
setIgnoringElementContent``Whitespace() False True

这些方法在DomEcho示例的主方法中的实现如下所示。

// ...
dbf.setIgnoringComments(ignoreComments);
dbf.setIgnoringElementContentWhitespace(ignoreWhitespace);
dbf.setCoalescing(putCDATAIntoText);
dbf.setExpandEntityReferences(!createEntityRefs);
// ...

布尔变量ignoreCommentsignoreWhitespaceputCDATAIntoTextcreateEntityRefs在主方法代码的开头声明,并且当运行DomEcho时通过命令行参数设置。

public static void main(String[] args) throws Exception {
    // ...
    boolean ignoreWhitespace = false;
    boolean ignoreComments = false;
    boolean putCDATAIntoText = false;
    boolean createEntityRefs = false;
    for (int i = 0; i < args.length; i++) {
        if (...) {  // Validation arguments here
           // ... 
        } 
        else if (args[i].equals("-ws")) {
            ignoreWhitespace = true;
        } 
        else if (args[i].startsWith("-co")) {
            ignoreComments = true;
        }
        else if (args[i].startsWith("-cd")) {
            putCDATAIntoText = true;
        } 
        else if (args[i].startsWith("-e")) {
            createEntityRefs = true;
            // ...
        } 
        else {
            filename = args[i];
            // Must be last arg
            if (i != args.length - 1) {
                usage();
            }
        }
    }
    // ...
}

打印 DOM 树节点

DomEcho应用程序允许您查看 DOM 的结构,并演示了 DOM 由哪些节点组成以及它们是如何排列的。一般来说,DOM 树中绝大多数节点将是ElementText节点。


注意 - 文本节点存在于 DOM 中的元素节点下方,数据始终存储在文本节点中。在 DOM 处理中最常见的错误可能是导航到元素节点并期望它包含存储在该元素中的数据。事实并非如此!即使是最简单的元素节点下面也有一个包含数据的文本节点。


打印 DOM 树节点的代码以适当的缩进显示如下。

private void echo(Node n) {
    outputIndentation();
    int type = n.getNodeType();
    switch (type) {
        case Node.ATTRIBUTE_NODE:
            out.print("ATTR:");
            printlnCommon(n);
            break;
        case Node.CDATA_SECTION_NODE:
            out.print("CDATA:");
            printlnCommon(n);
            break;
        case Node.COMMENT_NODE:
            out.print("COMM:");
            printlnCommon(n);
            break;
        case Node.DOCUMENT_FRAGMENT_NODE:
            out.print("DOC_FRAG:");
            printlnCommon(n);
            break;
        case Node.DOCUMENT_NODE:
            out.print("DOC:");
            printlnCommon(n);
            break;
        case Node.DOCUMENT_TYPE_NODE:
            out.print("DOC_TYPE:");
            printlnCommon(n);
            NamedNodeMap nodeMap = ((DocumentType)n).getEntities();
            indent += 2;
            for (int i = 0; i < nodeMap.getLength(); i++) {
                Entity entity = (Entity)nodeMap.item(i);
                echo(entity);
            }
            indent -= 2;
            break;
        case Node.ELEMENT_NODE:
            out.print("ELEM:");
            printlnCommon(n);
            NamedNodeMap atts = n.getAttributes();
            indent += 2;
            for (int i = 0; i < atts.getLength(); i++) {
                Node att = atts.item(i);
                echo(att);
            }
            indent -= 2;
            break;
        case Node.ENTITY_NODE:
            out.print("ENT:");
            printlnCommon(n);
            break;
        case Node.ENTITY_REFERENCE_NODE:
            out.print("ENT_REF:");
            printlnCommon(n);
            break;
        case Node.NOTATION_NODE:
            out.print("NOTATION:");
            printlnCommon(n);
            break;
        case Node.PROCESSING_INSTRUCTION_NODE:
            out.print("PROC_INST:");
            printlnCommon(n);
            break;
        case Node.TEXT_NODE:
            out.print("TEXT:");
            printlnCommon(n);
            break;
        default:
            out.print("UNSUPPORTED NODE: " + type);
            printlnCommon(n);
            break;
    }
    indent++;
    for (Node child = n.getFirstChild(); child != null;
         child = child.getNextSibling()) {
        echo(child);
    }
    indent--;
}

该代码首先使用 switch 语句打印出不同的节点类型和任何可能的子节点,并进行适当的缩进。

节点属性不包括在 DOM 层次结构的子节点中。而是通过Node接口的getAttributes方法获取。

DocType接口是w3c.org.dom.Node的扩展。它定义了getEntities方法,您可以使用该方法获取Entity节点 - 定义实体的节点。与Attribute节点一样,Entity节点不会出现为 DOM 节点的子节点。

节点操作

本节简要介绍了您可能想要应用于 DOM 的一些操作。

  • 创建节点
  • 遍历节点
  • 搜索节点
  • 获取节点内容
  • 创建属性
  • 删除和更改节点
  • 插入节点

创建节点

您可以使用Document接口的方法创建不同类型的节点。例如,createElementcreateCommentcreateCDATAsectioncreateTextNode等。有关创建不同节点的方法的完整列表,请参阅org.w3c.dom.Document的 API 文档。

遍历节点

org.w3c.dom.Node接口定义了一些方法,您可以使用这些方法遍历节点,包括getFirstChildgetLastChildgetNextSiblinggetPreviousSiblinggetParentNode。这些操作足以从树中的任何位置到达树中的任何其他位置。

搜索节点

当您搜索具有特定名称的节点时,需要考虑更多因素。虽然诱人的做法是获取第一个子节点并检查它是否正确,但搜索必须考虑到子列表中的第一个子节点可能是注释或处理指令。如果 XML 数据尚未经过验证,甚至可能是包含可忽略空格的文本节点。

本质上,您需要查看子节点列表,忽略那些不相关的节点,并检查您关心的节点。以下是在 DOM 层次结构中搜索节点时需要编写的一种例程。它在这里完整呈现(包括注释),以便您可以将其用作应用程序中的模板。

/**
 * Find the named subnode in a node's sublist.
 * <ul>
 * <li>Ignores comments and processing instructions.
 * <li>Ignores TEXT nodes (likely to exist and contain
 *         ignorable whitespace, if not validating.
 * <li>Ignores CDATA nodes and EntityRef nodes.
 * <li>Examines element nodes to find one with
 *        the specified name.
 * </ul>
 * @param name  the tag name for the element to find
 * @param node  the element node to start searching from
 * @return the Node found
 */
public Node findSubNode(String name, Node node) {
    if (node.getNodeType() != Node.ELEMENT_NODE) {
        System.err.println("Error: Search node not of element type");
        System.exit(22);
    }
    if (! node.hasChildNodes()) return null;
    NodeList list = node.getChildNodes();
    for (int i=0; i < list.getLength(); i++) {
        Node subnode = list.item(i);
        if (subnode.getNodeType() == Node.ELEMENT_NODE) {
           if (subnode.getNodeName().equals(name)) 
               return subnode;
        }
    }
    return null;
}

要深入了解此代码,请参阅增加复杂性中的何时使用 DOM。此外,您还可以使用词法控制中描述的 API 来修改解析器构造的 DOM 类型。不过,这段代码的好处是几乎适用于任何 DOM。

获取节点内容

当您想要获取节点包含的文本时,您需要再次查看子节点列表,忽略不相关的条目,并在TEXT节点、CDATA节点和EntityRef节点中找到的文本累积起来。以下是您可以用于该过程的一种例程。

/**
  * Return the text that a node contains. This routine:
  * <ul>
  * <li>Ignores comments and processing instructions.
  * <li>Concatenates TEXT nodes, CDATA nodes, and the results of
  *     recursively processing EntityRef nodes.
  * <li>Ignores any element nodes in the sublist.
  *     (Other possible options are to recurse into element 
  *      sublists or throw an exception.)
  * </ul>
  * @param    node  a  DOM node
  * @return   a String representing its contents
  */
public String getText(Node node) {
    StringBuffer result = new StringBuffer();
    if (! node.hasChildNodes()) return "";
    NodeList list = node.getChildNodes();
    for (int i=0; i < list.getLength(); i++) {
        Node subnode = list.item(i);
        if (subnode.getNodeType() == Node.TEXT_NODE) {
            result.append(subnode.getNodeValue());
        }
        else if (subnode.getNodeType() == Node.CDATA_SECTION_NODE) {
            result.append(subnode.getNodeValue());
        }
        else if (subnode.getNodeType() == Node.ENTITY_REFERENCE_NODE) {
            // Recurse into the subtree for text
            // (and ignore comments)
            result.append(getText(subnode));
        }
    }
    return result.toString();
}

关于这段代码的更深入解释,请参见增加复杂性中的何时使用 DOM。同样,你可以通过使用词法控制中描述的 API 来简化这段代码,以修改解析器构造的 DOM 类型。但这段代码的好处是几乎适用于任何 DOM。

创建属性

扩展了 Node 接口的org.w3c.dom.Element接口定义了一个setAttribute操作,用于向该节点添加属性。(从 Java 平台的角度来看,更好的名称应该是addAttribute。该属性不是类的属性,而是创建了一个新对象。)你还可以使用DocumentcreateAttribute操作来创建Attribute的实例,然后使用setAttributeNode方法来添加它。

删除和更改节点

要删除一个节点,你可以使用其父节点的removeChild方法。要更改它,你可以使用父节点的replaceChild操作或节点的setNodeValue操作。

插入节点

在创建新节点时要记住的重要事情是,当你创建一个元素节点时,你只需指定一个名称。实际上,该节点给你提供了一个挂载物件的钩子。你可以通过向其子节点列表添加内容来将物件挂在钩子上。例如,你可以添加一个文本节点、一个CDATA节点或一个属性节点。在构建过程中,请记住你在本教程中看到的结构。记住:层次结构中的每个节点都非常简单,只包含一个数据元素。

Java 中文官方教程 2022 版(三十八)(4)https://developer.aliyun.com/article/1488150

相关文章
|
2天前
|
Web App开发 JavaScript 前端开发
《手把手教你》系列技巧篇(三十九)-java+ selenium自动化测试-JavaScript的调用执行-上篇(详解教程)
【5月更文挑战第3天】本文介绍了如何在Web自动化测试中使用JavaScript执行器(JavascriptExecutor)来完成Selenium API无法处理的任务。首先,需要将WebDriver转换为JavascriptExecutor对象,然后通过executeScript方法执行JavaScript代码。示例用法包括设置JS代码字符串并调用executeScript。文章提供了两个实战场景:一是当时间插件限制输入时,用JS去除元素的readonly属性;二是处理需滚动才能显示的元素,利用JS滚动页面。还给出了一个滚动到底部的代码示例,并提供了详细步骤和解释。
31 10
|
2天前
|
Java 测试技术 Python
《手把手教你》系列技巧篇(三十六)-java+ selenium自动化测试-单选和多选按钮操作-番外篇(详解教程)
【4月更文挑战第28天】本文简要介绍了自动化测试的实战应用,通过一个在线问卷调查(&lt;https://www.sojump.com/m/2792226.aspx/&gt;)为例,展示了如何遍历并点击问卷中的选项。测试思路包括找到单选和多选按钮的共性以定位元素,然后使用for循环进行点击操作。代码设计方面,提供了Java+Selenium的示例代码,通过WebDriver实现自动答题。运行代码后,可以看到控制台输出和浏览器的相应动作。文章最后做了简单的小结,强调了本次实践是对之前单选多选操作的巩固。
25 0
|
23小时前
|
算法 Java Python
保姆级Java入门练习教程,附代码讲解,小白零基础入门必备
保姆级Java入门练习教程,附代码讲解,小白零基础入门必备
|
1天前
|
Web App开发 JavaScript 测试技术
《手把手教你》系列技巧篇(四十五)-java+ selenium自动化测试-web页面定位toast-上篇(详解教程)
【5月更文挑战第9天】本文介绍了在Appium中处理App自动化测试中遇到的Toast元素定位的方法。Toast在Web UI测试中也常见,通常作为轻量级反馈短暂显示。文章提供了两种定位Toast元素的技巧.
10 0
|
2天前
|
Web App开发 缓存 前端开发
《手把手教你》系列技巧篇(四十四)-java+ selenium自动化测试-处理https 安全问题或者非信任站点-下篇(详解教程)
【5月更文挑战第8天】这篇文档介绍了如何在IE、Chrome和Firefox浏览器中处理不信任证书的问题。作者北京-宏哥分享了如何通过编程方式跳过浏览器的证书警告,直接访问不受信任的HTTPS网站。文章分为几个部分,首先简要介绍了问题背景,然后详细讲解了在Chrome浏览器中的两种方法,包括代码设计和运行效果,并给出了其他浏览器的相关信息和参考资料。最后,作者总结了处理此类问题的一些通用技巧。
16 2
|
2天前
|
Java Android开发
【Java开发指南 | 第十八篇】Eclipse安装教程
【Java开发指南 | 第十八篇】Eclipse安装教程
11 2
|
2天前
|
Web App开发 JavaScript 前端开发
《手把手教你》系列技巧篇(四十三)-java+ selenium自动化测试-处理https 安全问题或者非信任站点-上篇(详解教程)
【5月更文挑战第7天】本文介绍了如何在Java+Selenium自动化测试中处理浏览器对不信任证书的处理方法,特别是针对IE、Chrome和Firefox浏览器。在某些情况下,访问HTTPS网站时会遇到证书不可信的警告,但可以通过编程方式跳过这些警告。
13 1
|
2天前
|
前端开发 Java 测试技术
《手把手教你》系列技巧篇(四十二)-java+ selenium自动化测试 - 处理iframe -下篇(详解教程)
【5月更文挑战第6天】本文介绍了如何使用Selenium处理含有iframe的网页。作者首先解释了iframe是什么,即HTML中的一个框架,用于在一个页面中嵌入另一个页面。接着,通过一个实战例子展示了在QQ邮箱登录页面中,由于输入框存在于iframe内,导致直接定位元素失败。作者提供了三种方法来处理这种情况:1)通过id或name属性切换到iframe;2)使用webElement对象切换;3)通过索引切换。最后,给出了相应的Java代码示例,并提醒读者根据iframe的实际情况选择合适的方法进行切换和元素定位。
9 0
|
2天前
|
前端开发 测试技术 Python
《手把手教你》系列技巧篇(四十一)-java+ selenium自动化测试 - 处理iframe -上篇(详解教程)
【5月更文挑战第5天】本文介绍了HTML中的`iframe`标签,它用于在网页中嵌套其他网页。`iframe`常用于加载外部内容或网站的某个部分,以实现页面美观。文章还讲述了使用Selenium自动化测试时如何处理`iframe`,通过`switchTo().frame()`方法进入`iframe`,完成相应操作,然后使用`switchTo().defaultContent()`返回主窗口。此外,文章提供了一个包含`iframe`的HTML代码示例,并给出了一个简单的自动化测试代码实战,演示了如何在`iframe`中输入文本。
17 3
|
2天前
|
JavaScript 前端开发 Java
《手把手教你》系列技巧篇(四十)-java+ selenium自动化测试-JavaScript的调用执行-下篇(详解教程)
【5月更文挑战第4天】本文介绍了如何使用JavaScriptExecutor在自动化测试中实现元素高亮显示。通过创建并执行JS代码,可以改变元素的样式,例如设置背景色和边框,以突出显示被操作的元素。文中提供了一个Java示例,展示了如何在Selenium中使用此方法,并附有代码截图和运行效果展示。该技术有助于跟踪和理解测试过程中的元素交互。
10 0