774.md 27.0 KB
Newer Older
W
init  
wizardforcel 已提交
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140
# 将 XML 数据读入 DOM

> 原文: [https://docs.oracle.com/javase/tutorial/jaxp/dom/readingXML.html](https://docs.oracle.com/javase/tutorial/jaxp/dom/readingXML.html)

在本节中,您将通过读入现有 XML 文件来构建文档对象模型。

* * *

**注 -**[可扩展样式表语言转换](../xslt/index.html)中,您将看到如何将 DOM 写为 XML 文件。 (您还将看到如何相对轻松地将现有数据文件转换为 XML。)

* * *

## 创建程序

文档对象模型提供的 API 允许您创建,修改,删除和重新排列节点。在尝试创建 DOM 之前,了解 DOM 的结构是很有帮助的。这一系列示例将通过名为 `DOMEcho` 的示例程序使 DOM 内部可见,您可以在目录 `_INSTALL_DIR_ / jaxp- _ 版本 _ /中找到安装 JAXP API 后的 samples / dom`

### 创建骨架

首先,构建一个简单的程序,将 XML 文档读入 DOM,然后再将其写回。

从应用程序的常规基本逻辑开始,并检查以确保在命令行上提供了参数:

```
public class DOMEcho {

    static final String outputEncoding = "UTF-8";

    private static void usage() {
        // ...
    }

    public static void main(String[] args) throws Exception {
        String filename = null;

        for (int i = 0; i < args.length; i++) {
            if (...) { 
                // ...
            } 
            else {
                filename = args[i];
                if (i != args.length - 1) {
                    usage();
                }
            }
        }

        if (filename == null) {
            usage();
        }
    }
}

```

此代码执行所有基本设置操作。 `DOMEcho` 的所有输出都使用 UTF-8 编码。如果没有指定参数,则调用的 `usage()`方法只会告诉您 `DOMEcho` 所期望的参数,因此此处不显示代码。还声明了`文件名`字符串,它将是 `DOMEcho` 要解析为 DOM 的 XML 文件的名称。

### 导入必需的类

在本节中,所有类都单独命名,以便您可以查看每个类的来源,以防您想要引用 API 文档。在示例文件中,import 语句使用较短的形式,例如 `javax.xml.parsers。*`

这些是 `DOMEcho` 使用的 JAXP API:

```
package dom;
import javax.xml.parsers.DocumentBuilder; 
import javax.xml.parsers.DocumentBuilderFactory;

```

这些类用于解析 XML 文档时可以抛出的异常:

```
import org.xml.sax.ErrorHandler;
import org.xml.sax.SAXException; 
import org.xml.sax.SAXParseException;
import org.xml.sax.helpers.*

```

这些类读取示例 XML 文件并管理输出:

```
import java.io.File;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;

```

最后,导入 DOM,DOM 异常,实体和节点的 W3C 定义:

```
import org.w3c.dom.Document;
import org.w3c.dom.DocumentType;
import org.w3c.dom.Entity;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;

```

### 处理错误

接下来,添加错误处理逻辑。最重要的一点是,当解析 XML 文档时,需要符合 JAXP 的文档构建器来报告 SAX 异常。 DOM 解析器不必在内部实际使用 SAX 解析器,但由于 SAX 标准已经存在,因此使用它来报告错误是有意义的。因此,DOM 应用程序的错误处理代码与 SAX 应用程序的错误处理代码非常相似:

```
private static class MyErrorHandler implements ErrorHandler {

    private PrintWriter out;

    MyErrorHandler(PrintWriter out) {
        this.out = out;
    }

    private String getParseExceptionInfo(SAXParseException spe) {
        String systemId = spe.getSystemId();
        if (systemId == null) {
            systemId = "null";
        }

        String info = "URI=" + systemId + " Line=" + spe.getLineNumber() +
                      ": " + spe.getMessage();
        return info;
    }

    public void warning(SAXParseException spe) throws SAXException {
        out.println("Warning: " + getParseExceptionInfo(spe));
    }

    public void error(SAXParseException spe) throws SAXException {
        String message = "Error: " + getParseExceptionInfo(spe);
        throw new SAXException(message);
    }

    public void fatalError(SAXParseException spe) throws SAXException {
        String message = "Fatal Error: " + getParseExceptionInfo(spe);
        throw new SAXException(message);
    }
}

```

W
wizardforcel 已提交
141
如您所见, `DomEcho` 类的错误处理器使用 `PrintWriter` 实例生成其输出。
W
init  
wizardforcel 已提交
142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239

### 实例化工厂

接下来,将以下代码添加到 `main()`方法,以获取可以为我们提供文档构建器的工厂实例。

```
public static void main(String[] args) throws Exception {
    DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();

    // ...
}

```

### 获取解析器并解析文件

现在,将以下代码添加到 `main()`以获取构建器的实例,并使用它来解析指定的文件。

```
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db = dbf.newDocumentBuilder(); 
Document doc = db.parse(new File(filename));

```

正在解析的文件由`文件名`变量提供,该变量在 `main()`方法的开头声明,当作为参数传递给 `DOMEcho` 时程序运行。

### 配置工厂

默认情况下,工厂返回一个对表空间一无所知的非验证解析器。要获得验证解析器或理解名称空间(或两者)的解析器,可以使用以下代码将工厂配置为设置其中一个或两个选项。

```
public static void main(String[] args) throws Exception {

    String filename = null;
    boolean dtdValidate = false;
    boolean xsdValidate = false;
    String schemaSource = null;

    for (int i = 0; i < args.length; i++) {
        if (args[i].equals("-dtd"))  { 
            dtdValidate = true;
        } 
        else if (args[i].equals("-xsd")) {
            xsdValidate = true;
        } 
        else if (args[i].equals("-xsdss")) {
            if (i == args.length - 1) {
                usage();
            }
            xsdValidate = true;
            schemaSource = args[++i];
        }
        else {
            filename = args[i];
            if (i != args.length - 1) {
                usage();
            }
        }
    }

    if (filename == null) {
        usage();
    }

    DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();

    dbf.setNamespaceAware(true);
    dbf.setValidating(dtdValidate || xsdValidate);

    // ...

    DocumentBuilder db = dbf.newDocumentBuilder();
    Document doc = db.parse(new File(filename));
}

```

如您所见,设置了命令行参数,以便您可以通知 `DOMEcho` 对 DTD 或 XML Schema 执行验证,并且工厂配置为可识别名称空间并执行任何类型用户指定的验证。

* * *

**注 -** 符合 JAXP 的解析器不需要支持这些选项的所有组合,即使引用解析器也是如此。如果指定了无效的选项组合,则当您尝试获取解析器实例时,工厂会生成 `ParserConfigurationException`

* * *

有关如何使用名称空间和验证的更多信息,请参见[使用 XML 架构验证](validating.html),其中将描述上述摘录中缺少的代码。

### 处理验证错误

根据 SAX 标准的规定,对验证错误的默认响应是什么都不做。 JAXP 标准要求抛出 SAX 异常,因此您使用与用于 SAX 应用程序完全相同的错误处理机制。特别是,您使用 `DocumentBuilder` 类的 `setErrorHandler` 方法为其提供实现 SAX `ErrorHandler` 接口的对象。

* * *

**注意 -** `DocumentBuilder` 也有一个 `setEntityResolver` 方法可以使用。

* * *

W
wizardforcel 已提交
240
以下代码将文档构建器配置为使用[句柄错误](readingXML.html#gestm)中定义的错误处理器。
W
init  
wizardforcel 已提交
241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258

```
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 核心规范中的`节点`的规范下找到。 [下表 3-1](#gfzpy) 改编自该规范。`

表 3-1 节点类型

W
wizardforcel 已提交
259 260 261

   

W
init  
wizardforcel 已提交
262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349
| 

节点

 | 

节点名称

 | 

的 nodeValue

 | 

属性

 |
| :-- | :-- | :-- | :-- |
| `Attr` | 属性名称 | 属性的值 | 空值 |
| `CDATASection` | `#cdata-section` | CDATA 部分的内容 | 空值 |
| `评论` | `#comment` | 评论的内容 | 空值 |
| `文件` | `#document` | 空值 | 空值 |
| `DocumentFragment` | `#documentFragment` | 空值 | 空值 |
| `DocumentType` | 文档类型名称 | 空值 | 空值 |
| `元素` | 标签名称 | 空值 | 空值 |
| `实体` | 实体名称 | 空值 | 空值 |
| `EntityReference` | 引用的实体名称 | 空值 | 空值 |
| `符号` | 符号名称 | 空值 | 空值 |
| `ProcessingInstruction` | 目标 | 整个内容不包括目标 | 空值 |
| `正文` | `#text` | 文本节点的内容 | 空值 |

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

### 获取节点类型信息

DOM 节点元素类型信息是通过调用 `org.w3c.dom.Node` 类的各种方法获得的。由 `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 节点至少都有一个类型,一个名称和一个值,它们可能是空的,也可能不是。在上面的例子中,`节点`接口的 `getNamespaceURI()``getPrefix()``getLocalName()``getNodeValue( )`方法返回并打印回显节点的名称空间 URI,名称空间前缀,本地限定名称和值。请注意, `trim()`方法在 `getNodeValue()`返回的值上调用,以确定节点的值是否为空白空格并相应地打印消息。

有关`节点`方法的完整列表及其返回的不同信息,请参阅 [`Node`](https://docs.oracle.com/javase/8/docs/api/org/w3c/dom/Node.html) 的 API 文档。

接下来,定义一种方法来在节点打印时为节点设置缩进,以便可以容易地看到节点层次结构。

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

```

W
wizardforcel 已提交
350
`basicIndent` 常量定义 `DOMEcho` 显示节点树层次结构时使用的缩进的基本单位,通过将以下突出显示的行添加到 `DOMEcho` 构造器类来定义。
W
init  
wizardforcel 已提交
351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366

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

```

W
wizardforcel 已提交
367
[句柄错误](readingXML.html#gestm)中定义的错误处理器一样, `DOMEcho` 程序将其输出创建为 `PrintWriter` 实例。
W
init  
wizardforcel 已提交
368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396

### 词汇控制

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

但是,大多数应用程序仅关注 XML 结构的内容。他们可以忽略评论,他们不关心数据是以 CDATA 部分编码还是以纯文本编码,或者是否包含实体参考。对于此类应用程序,需要最少的词法信息,因为它简化了应用程序必须准备检查的 DOM 节点的数量和种类。

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

`setCoalescing()`

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

`setExpandEntityReferences()`

扩展实体引用节点。

`setIgnoringComments()`

忽略评论。

`setIgnoringElementContentWhitespace()`

忽略不是元素内容重要部分的空格。

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

表 3-2 词汇控制设置

W
wizardforcel 已提交
397 398 399

  

W
init  
wizardforcel 已提交
400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611
| 

API

 | 

保留词汇信息

 | 

专注于内容

 |
| :-- | :-- | :-- |
| `setCoalescing()` | 假 | 真正 |
| `setExpandEntityReferences()` | 假 | 真正 |
| `setIgnoringComments()` | 假 | 真正 |
| `setIgnoringElementContent` `Whitespace()` | 假 | 真正 |

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

```
// ...

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

// ...

```

布尔变量 `ignoreComments``ignoreWhitespace``putCDATAIntoText``createEntityRefs` 在主方法代码的开头声明,并设置它们运行 `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 树中的绝大多数节点将是 `Element``Text` 节点。

* * *

**注 -** 文本节点在 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 层次结构中。它们是通过`节点`接口的 `getAttributes` 方法获得的。

`DocType` 接口是 `w3c.org.dom.Node` 的扩展。它定义了 `getEntities` 方法,用于获取`实体`节点 - 定义实体的节点。与`属性`节点类似,`实体`节点不会显示为 DOM 节点的子节点。

## 节点操作

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

*   创建节点

*   遍历节点

*   搜索节点

*   获取节点内容

*   创建属性

*   删除和更改节点

*   插入节点

### 创建节点

W
wizardforcel 已提交
612
您可以使用 `Document` 接口的方法创建不同类型的节点。例如, `createElement``createComment``createCDATAsection``createTextNode` 等等。 [`org.w3c.dom.Document`](https://docs.oracle.com/javase/8/docs/api/org/w3c/dom/Document.html) 的 API 文档中提供了创建不同节点的完整方法列表。
W
init  
wizardforcel 已提交
613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816

### 遍历节点

`org.w3c.dom.Node` 接口定义了许多可用于遍历节点的方法,包括 `getFirstChild``getLastChild``getNextSibling` ], `getPreviousSibling``getParentNode` 。这些操作足以从树中的任何位置到树中的任何其他位置。

### 正在搜索节点

当您搜索具有特定名称的节点时,还需要考虑更多内容。尽管获得第一个孩子并检查它是否是正确的孩子是很诱人的,但搜索必须考虑到子列表中的第一个孩子可能是评论或处理指令的事实。如果 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;
}

```

有关此代码的更深入说明,请参阅[](when.html#gchls)[中增加复杂度](when.html)何时使用 DOM 。另请注意,您可以使用 [Lexical Controls](readingXML.html#ggdwv) 中描述的 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();
}

```

有关此代码的更深入说明,请参阅[](when.html#gchls)[中增加复杂度](when.html)何时使用 DOM 。同样,您可以使用 [Lexical Controls](readingXML.html#ggdwv) 中描述的 API 来简化此代码,以修改解析器构造的 DOM 类型。但是这个代码的优点在于它几乎可以用于任何 DOM。

### 创建属性

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

### 删除和更改节点

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

### 插入节点

创建新节点时要记住的重要一点是,在创建元素节点时,您指定的唯一数据是名称。实际上,该节点为您提供挂钩的挂钩。通过添加到子节点列表中挂起项目。例如,您可以添加文本节点, `CDATA` 节点或属性节点。在构建时,请记住您在本教程中看到的结构。请记住:层次结构中的每个节点都非常简单,只包含一个数据元素。

## 运行 `DOMEcho` 样本

要运行 `DOMEcho` 样本,请按照以下步骤操作。

1.  **导航至`样本`目录。** `%cd _install-dir_ `/ jaxp-1_4_2-` _ 释放日期 _ `/样品`。`
2.  **编译示例类。** `% javac dom/*`
3.  **Run the `DOMEcho` program on an XML file.**

    选择`数据`目录中的一个 XML 文件,然后运行 `DOMEcho` 程序。在这里,我们选择在文件 `personal-schema.xml` 上运行程序。

    `% java dom/DOMEcho data/personal-schema.xml`

    XML 文件 `personal-schema.xml` 包含小公司的人事档案。当您在其上运行 `DOMEcho` 程序时,您应该看到以下输出。

    ```
    DOC: nodeName="#document"
     ELEM: nodeName="personnel" 
           local="personnel"
     TEXT: nodeName="#text" 
           nodeValue=[WS]
     ELEM: nodeName="person" 
           local="person"
     ATTR: nodeName="id" 
           local="id" 
           nodeValue="Big.Boss"
     TEXT: nodeName="#text" 
           nodeValue=[WS]
     ELEM: nodeName="name" 
           local="name"
     ELEM: nodeName="family" 
           local="family"
     TEXT: nodeName="#text" 
           nodeValue="Boss"
     TEXT: nodeName="#text" 
           nodeValue=[WS]
     ELEM: nodeName="given" 
           local="given"
     TEXT: nodeName="#text" 
           nodeValue="Big"
     TEXT: nodeName="#text" 
           nodeValue=[WS]
     ELEM: nodeName="email" 
           local="email"
     TEXT: nodeName="#text" 
           nodeValue="chief@foo.example.com"
     TEXT: nodeName="#text" 
           nodeValue=[WS]
     ELEM: nodeName="link" 
           local="link"
     ATTR: nodeName="subordinates" 
           local="subordinates" 
           nodeValue="one.worker two.worker 
                      three.worker four.worker
                      five.worker"
     TEXT: nodeName="#text" 
           nodeValue=[WS]
     TEXT: nodeName="#text" 
           nodeValue=[WS]
     ELEM: nodeName="person" 
           local="person"
     ATTR: nodeName="id" 
           local="id" 
           nodeValue="one.worker"
     TEXT: nodeName="#text" 
           nodeValue=[WS]
     ELEM: nodeName="name" 
           local="name"
     ELEM: nodeName="family" 
           local="family"
     TEXT: nodeName="#text" 
           nodeValue="Worker"
     TEXT: nodeName="#text" 
           nodeValue=[WS]
     ELEM: nodeName="given" 
           local="given"
     TEXT: nodeName="#text" 
           nodeValue="One"
     TEXT: nodeName="#text" 
           nodeValue=[WS]
     ELEM: nodeName="email" 
           local="email"
     TEXT: nodeName="#text" 
           nodeValue="one@foo.example.com"
     TEXT: nodeName="#text" 
           nodeValue=[WS]
     ELEM: nodeName="link" 
           local="link"
     ATTR: nodeName="manager" 
           local="manager" 
           nodeValue="Big.Boss"
     TEXT: nodeName="#text"
           nodeValue=[WS]

    [...]

    ```

    如您所见, `DOMEcho` 打印出文档中不同元素的所有节点,并使用正确的缩进来显示节点层次结构。