Webサービスや設定ファイルで広く使われているXML(Extensible Markup Language)。このXMLを理解する上で欠かせないのがノード(Node)という概念です。
ノードは、XMLドキュメントを構成する最小単位であり、要素やテキスト、属性など様々な種類があります。これらのノードがツリー構造を形成することで、複雑なデータ表現が可能になるんです。
この記事では、XMLノードの基本概念から種類、ツリー構造、実際のプログラミングでの操作方法まで、分かりやすく解説していきます。XMLを扱う開発者はもちろん、これから学ぶ方にも必見の内容です!
XMLノードとは?ドキュメントを構成する基本単位

ノード(Node)とは、XMLドキュメントを構成する個々の要素や情報の単位のことです。
英語で「node」は「節点」や「結び目」を意味します。XMLでは、データを表現する様々な部品がノードとして扱われるんですね。
簡単に言うと
XMLドキュメントは、様々な種類のノードが集まって構成されていると考えられます。
イメージ:
- 家族を表す家系図のようなもの
- 組織図の各ポジション
- ファイルシステムのフォルダとファイル
それぞれのノードが役割を持ち、相互に関連し合ってドキュメント全体を形成しています。
DOM(Document Object Model)との関係
ノードという概念は、DOM(Document Object Model)というXML/HTMLの標準的な表現方法に基づいています。
DOMとは:
- XMLやHTMLドキュメントをツリー構造として表現する仕組み
- W3C(World Wide Web Consortium)が策定した標準
- プログラミング言語からドキュメントを操作するためのAPI
DOMでは、ドキュメント内のすべてがノードとして扱われます。
XMLの基本構造:ノードで見るドキュメント
まず、具体的なXMLを見てみましょう。
サンプルXML
<?xml version="1.0" encoding="UTF-8"?>
<!-- 図書館の蔵書リスト -->
<library>
<book id="1" category="technology">
<title>Effective Java</title>
<author>Joshua Bloch</author>
<price currency="JPY">4500</price>
<description>Javaプログラミングのベストプラクティス集</description>
</book>
<book id="2" category="business">
<title>Clean Code</title>
<author>Robert Martin</author>
<price currency="JPY">3800</price>
</book>
</library>
このXMLには、様々な種類のノードが含まれています。
このXMLに含まれるノード
1. ドキュメントノード
- XML宣言(
<?xml version="1.0"?>)
2. コメントノード
<!-- 図書館の蔵書リスト -->
3. 要素ノード
<library>、<book>、<title>など
4. 属性ノード
id="1"、category="technology"など
5. テキストノード
- “Effective Java”、”Joshua Bloch”など
それぞれのノードについて、詳しく見ていきましょう。
ノードの種類:XMLを構成する12のタイプ
DOM仕様では、12種類のノードタイプが定義されています。
主要な7種類のノード
実際によく使われるのは、以下の7種類です。
1. ドキュメントノード(Document Node)
特徴:
- XMLドキュメント全体を表す最上位のノード
- すべてのノードの親
- ドキュメント全体に1つだけ存在
役割:
- ドキュメント全体へのアクセスポイント
- ルート要素への参照を保持
- 新しいノードの作成機能を提供
DOMでの表現:
Document doc = builder.parse(file);
// docがドキュメントノード
2. 要素ノード(Element Node)
特徴:
- XMLのタグに対応するノード
- 最も重要で頻繁に使用される
- 属性や子ノードを持つことができる
例:
<book id="1">
<title>Effective Java</title>
</book>
ここでは<book>と<title>が要素ノードです。
特性:
- 開始タグと終了タグで囲まれる
- 子ノード(子要素やテキスト)を含める
- 属性を持てる
- 階層構造を形成
3. 属性ノード(Attribute Node)
特徴:
- 要素の属性を表すノード
- 要素ノードに付属する追加情報
- 名前と値のペア
例:
<book id="1" category="technology">
ここではid="1"とcategory="technology"が属性ノードです。
特性:
- 必ず要素ノードに属する
- 開始タグ内に記述される
- 複数の属性を持てる
- 順序は保証されない
注意点:
- 属性ノードは要素ノードの「子」ではない
- 特殊な関係性を持つ
4. テキストノード(Text Node)
特徴:
- 要素のテキスト内容を表すノード
- 実際のデータを保持
- 常に要素ノードの子になる
例:
<title>Effective Java</title>
ここでは”Effective Java”がテキストノードです。
特性:
- 要素の開始タグと終了タグの間のテキスト
- 子ノードを持たない(リーフノード)
- 空白や改行もテキストノードとして扱われる
重要な点:
<author>
Robert Martin
</author>
この場合、テキストノードは実際には以下を含みます:
- 改行と空白
- “Robert Martin”
- 改行
5. CDATAセクションノード(CDATA Section Node)
特徴:
- 特殊文字をそのまま扱うためのノード
- パーサーに解析させないテキスト
<![CDATA[...]]>で囲まれる
例:
<code>
<![CDATA[
if (x < 10 && y > 5) {
System.out.println("条件成立");
}
]]>
</code>
用途:
- プログラムコードの埋め込み
- HTMLタグを含むテキスト
<や&などの特殊文字を含むデータ
通常のテキストとの違い:
<!-- 通常のテキスト(エスケープ必要) -->
<code>if (x < 10 && y > 5)</code>
<!-- CDATA(エスケープ不要) -->
<code><![CDATA[if (x < 10 && y > 5)]]></code>
6. コメントノード(Comment Node)
特徴:
- XMLのコメントを表すノード
<!-- ... -->で囲まれる- ドキュメントの説明や注釈に使用
例:
<!-- これは図書館の蔵書データです -->
<library>
<!-- 2025年1月時点のデータ -->
<book>...</book>
</library>
特性:
- パーサーに解析されるが、アプリケーションには影響しない
- ドキュメント内のどこにでも配置可能
- 入れ子はできない(コメント内にコメントは書けない)
用途:
- ドキュメントの説明
- 開発者向けのメモ
- 一時的なコードの無効化
7. 処理命令ノード(Processing Instruction Node)
特徴:
- アプリケーション固有の命令を含むノード
<?ターゲット 命令?>の形式- XML宣言も処理命令の一種
例:
<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" href="style.xsl"?>
<document>
...
</document>
構成要素:
- ターゲット:命令を処理するアプリケーション名
- データ:アプリケーションへの命令内容
用途:
- スタイルシートの指定
- アプリケーション固有の設定
- 変換ルールの指定
その他のノードタイプ
実務ではあまり使いませんが、以下のタイプもあります:
8. Document Type Definition(DTD)ノード
9. Document Fragmentノード
10. Entityノード
11. Entity Referenceノード
12. Notationノード
これらは特殊な用途で使用されます。
ノードツリー構造:親子関係と階層
XMLのノードは、ツリー構造(木構造)を形成します。
ツリー構造の基本
ツリーの特徴:
- ルートノードが1つだけ存在(最上位)
- 各ノードは親ノードを1つ持つ(ルート以外)
- ノードは子ノードを複数持てる
- リーフノードは子を持たない(末端)
サンプルXMLのツリー
<library>
<book id="1">
<title>Effective Java</title>
<author>Joshua Bloch</author>
</book>
</library>
ツリー表現:
Document(ドキュメントノード)
└─ library(要素ノード)
├─ "\n "(テキストノード:改行と空白)
├─ book(要素ノード)
│ ├─ id="1"(属性ノード)
│ ├─ "\n "(テキストノード)
│ ├─ title(要素ノード)
│ │ └─ "Effective Java"(テキストノード)
│ ├─ "\n "(テキストノード)
│ ├─ author(要素ノード)
│ │ └─ "Joshua Bloch"(テキストノード)
│ └─ "\n "(テキストノード)
└─ "\n"(テキストノード)
ノード間の関係性
1. 親子関係(Parent-Child)
親ノード(Parent Node):
- 直接の上位ノード
- 例:
<book>は<title>の親
子ノード(Child Nodes):
- 直接の下位ノード
- 例:
<title>と<author>は<book>の子
コード例:
Element book = ...; // bookノード
NodeList children = book.getChildNodes(); // 子ノードを取得
Node parent = book.getParentNode(); // 親ノードを取得
2. 兄弟関係(Siblings)
兄弟ノード:
- 同じ親を持つノード同士
- 例:
<title>と<author>は兄弟
先行兄弟(Previous Sibling):
- 自分より前にある兄弟
後続兄弟(Next Sibling):
- 自分より後にある兄弟
コード例:
Node title = ...; // titleノード
Node nextSibling = title.getNextSibling(); // 次の兄弟
Node prevSibling = title.getPreviousSibling(); // 前の兄弟
3. 祖先関係(Ancestors)
祖先ノード:
- 親、親の親、さらにその親…
- ルートまでの経路上のすべてのノード
子孫ノード(Descendants):
- 子、子の子、さらにその子…
- すべての下位ノード
ルート要素とドキュメントノードの違い
ドキュメントノード:
- XML全体を表す最上位のノード
- 実際のXMLタグではない
ルート要素:
- XMLの最上位の要素ノード
- 例:
<library>
関係性:
ドキュメントノード(Document)
├─ XML宣言(Processing Instruction)
├─ コメント(Comment)
└─ library(Root Element:ルート要素)
└─ その他の要素...
ノードのプロパティ:持っている情報
各ノードは、様々なプロパティ(属性情報)を持っています。
共通のプロパティ
すべてのノードタイプに共通する基本的な情報です。
1. nodeType(ノードタイプ)
ノードの種類を示す数値です。
主なノードタイプの値:
1:要素ノード(ELEMENT_NODE)2:属性ノード(ATTRIBUTE_NODE)3:テキストノード(TEXT_NODE)4:CDATAセクションノード(CDATA_SECTION_NODE)8:コメントノード(COMMENT_NODE)9:ドキュメントノード(DOCUMENT_NODE)
例:
int type = node.getNodeType();
if (type == Node.ELEMENT_NODE) {
System.out.println("これは要素ノードです");
}
2. nodeName(ノード名)
ノードの名前です。
ノードタイプ別の内容:
- 要素ノード:タグ名(例:”book”)
- 属性ノード:属性名(例:”id”)
- テキストノード:”#text”
- コメントノード:”#comment”
- ドキュメントノード:”#document”
例:
String name = node.getNodeName();
System.out.println("ノード名: " + name);
3. nodeValue(ノード値)
ノードが保持する値です。
ノードタイプ別の内容:
- 要素ノード:null
- 属性ノード:属性の値
- テキストノード:テキスト内容
- コメントノード:コメント内容
- ドキュメントノード:null
例:
String value = node.getNodeValue();
if (value != null) {
System.out.println("ノード値: " + value);
}
4. parentNode(親ノード)
このノードの親ノードへの参照です。
例:
Node parent = node.getParentNode();
if (parent != null) {
System.out.println("親: " + parent.getNodeName());
}
5. childNodes(子ノードリスト)
このノードの子ノードのリストです。
例:
NodeList children = node.getChildNodes();
for (int i = 0; i < children.getLength(); i++) {
Node child = children.item(i);
System.out.println("子: " + child.getNodeName());
}
6. firstChild / lastChild(最初の子/最後の子)
子ノードの最初と最後への直接参照です。
例:
Node first = node.getFirstChild();
Node last = node.getLastChild();
7. nextSibling / previousSibling(次/前の兄弟)
兄弟ノードへの参照です。
例:
Node next = node.getNextSibling();
Node prev = node.getPreviousSibling();
要素ノード固有のプロパティ
要素ノードには、追加のプロパティがあります。
tagName(タグ名):
Element element = (Element) node;
String tagName = element.getTagName();
attributes(属性マップ):
NamedNodeMap attrs = element.getAttributes();
int attrCount = attrs.getLength();
textContent(テキスト内容):
String text = element.getTextContent();
// すべての子孫テキストノードを結合
ノードの操作:DOMでの実践
実際にプログラムでノードを操作する方法を見ていきましょう。
Javaでのノード操作
基本的なXML読み込み:
import javax.xml.parsers.*;
import org.w3c.dom.*;
import java.io.File;
public class NodeExample {
public static void main(String[] args) throws Exception {
// XMLファイルをパース
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
Document doc = builder.parse(new File("library.xml"));
// ドキュメントノードから開始
System.out.println("Document: " + doc.getNodeName());
// ルート要素を取得
Element root = doc.getDocumentElement();
System.out.println("Root element: " + root.getNodeName());
}
}
ノードの走査(トラバーサル)
すべての子ノードを巡回:
public static void traverseNodes(Node node, int level) {
// インデントを作成
String indent = " ".repeat(level);
// ノード情報を表示
System.out.println(indent + "Node: " + node.getNodeName()
+ " (Type: " + node.getNodeType() + ")");
// 要素ノードの場合は属性も表示
if (node.getNodeType() == Node.ELEMENT_NODE) {
Element element = (Element) node;
NamedNodeMap attrs = element.getAttributes();
for (int i = 0; i < attrs.getLength(); i++) {
Node attr = attrs.item(i);
System.out.println(indent + " @" + attr.getNodeName()
+ "=\"" + attr.getNodeValue() + "\"");
}
}
// テキストノードの場合は内容を表示
if (node.getNodeType() == Node.TEXT_NODE) {
String text = node.getNodeValue().trim();
if (!text.isEmpty()) {
System.out.println(indent + " Text: \"" + text + "\"");
}
}
// 再帰的に子ノードを処理
NodeList children = node.getChildNodes();
for (int i = 0; i < children.getLength(); i++) {
traverseNodes(children.item(i), level + 1);
}
}
// 使用例
traverseNodes(doc.getDocumentElement(), 0);
特定のノードを検索
タグ名で要素を検索:
// すべてのbook要素を取得
NodeList bookNodes = doc.getElementsByTagName("book");
for (int i = 0; i < bookNodes.getLength(); i++) {
Node bookNode = bookNodes.item(i);
if (bookNode.getNodeType() == Node.ELEMENT_NODE) {
Element book = (Element) bookNode;
// 属性を取得
String id = book.getAttribute("id");
System.out.println("Book ID: " + id);
// 子要素を取得
NodeList titles = book.getElementsByTagName("title");
if (titles.getLength() > 0) {
String title = titles.item(0).getTextContent();
System.out.println(" Title: " + title);
}
}
}
ノードの作成と追加
新しいノードを作成:
// 新しい要素ノードを作成
Element newBook = doc.createElement("book");
newBook.setAttribute("id", "3");
// 子要素を作成
Element title = doc.createElement("title");
title.setTextContent("Design Patterns");
Element author = doc.createElement("author");
author.setTextContent("Gang of Four");
// 要素を組み立て
newBook.appendChild(title);
newBook.appendChild(author);
// ルート要素に追加
Element root = doc.getDocumentElement();
root.appendChild(newBook);
ノードの削除
// 特定のノードを削除
Node nodeToRemove = ...; // 削除したいノード
Node parent = nodeToRemove.getParentNode();
if (parent != null) {
parent.removeChild(nodeToRemove);
System.out.println("ノードを削除しました");
}
ノードの置換
// 既存のノードを新しいノードで置換
Node oldNode = ...; // 古いノード
Node newNode = ...; // 新しいノード
Node parent = oldNode.getParentNode();
if (parent != null) {
parent.replaceChild(newNode, oldNode);
System.out.println("ノードを置換しました");
}
JavaScriptでのノード操作
WebブラウザでもノードAPIを使えます。
基本的な操作
// XMLをパース
const parser = new DOMParser();
const xmlString = `
<library>
<book id="1">
<title>Effective Java</title>
</book>
</library>
`;
const doc = parser.parseFromString(xmlString, "text/xml");
// ルート要素を取得
const root = doc.documentElement;
console.log("Root:", root.nodeName); // "library"
// すべてのbook要素を取得
const books = doc.getElementsByTagName("book");
console.log("Books count:", books.length);
// 最初のbook要素
const book = books[0];
console.log("Book ID:", book.getAttribute("id"));
// title要素のテキスト
const title = book.getElementsByTagName("title")[0];
console.log("Title:", title.textContent);
ノードの走査
function traverseNodes(node, level = 0) {
const indent = " ".repeat(level);
console.log(`${indent}${node.nodeName} (Type: ${node.nodeType})`);
// 属性を表示
if (node.attributes) {
for (let attr of node.attributes) {
console.log(`${indent} @${attr.name}="${attr.value}"`);
}
}
// 子ノードを再帰的に処理
for (let child of node.childNodes) {
traverseNodes(child, level + 1);
}
}
traverseNodes(doc.documentElement);
新しいノードの作成
// 新しい要素を作成
const newBook = doc.createElement("book");
newBook.setAttribute("id", "2");
const title = doc.createElement("title");
title.textContent = "Clean Code";
newBook.appendChild(title);
// ルートに追加
doc.documentElement.appendChild(newBook);
Pythonでのノード操作
Pythonでも様々なライブラリでノードを扱えます。
xml.dom.minidomを使用
from xml.dom import minidom
# XMLをパース
doc = minidom.parse('library.xml')
# ルート要素を取得
root = doc.documentElement
print(f"Root: {root.nodeName}")
# すべてのbook要素を取得
books = doc.getElementsByTagName('book')
for book in books:
# 属性を取得
book_id = book.getAttribute('id')
print(f"Book ID: {book_id}")
# title要素を取得
titles = book.getElementsByTagName('title')
if titles:
title = titles[0].firstChild.nodeValue
print(f" Title: {title}")
ElementTreeを使用(より簡単)
import xml.etree.ElementTree as ET
# XMLをパース
tree = ET.parse('library.xml')
root = tree.getroot()
print(f"Root: {root.tag}")
# すべてのbook要素を検索
for book in root.findall('book'):
book_id = book.get('id')
title = book.find('title').text
print(f"Book ID: {book_id}")
print(f" Title: {title}")
XPathによるノード選択
XPathは、XMLノードを選択するための強力な言語です。
XPathの基本
パス表現:
/:ルートから//:ドキュメント内のどこからでも.:現在のノード..:親ノード@:属性
例:
/library/book # libraryの子のbook要素
//book # すべてのbook要素
/library/book[@id='1'] # id属性が1のbook
//title # すべてのtitle要素
/library/book[1] # 最初のbook要素
Javaでの使用
import javax.xml.xpath.*;
// XPathを作成
XPathFactory xPathfactory = XPathFactory.newInstance();
XPath xpath = xPathfactory.newXPath();
// XPath式で検索
XPathExpression expr = xpath.compile("//book[@id='1']/title");
NodeList nodes = (NodeList) expr.evaluate(doc, XPathConstants.NODESET);
// 結果を処理
for (int i = 0; i < nodes.getLength(); i++) {
Node node = nodes.item(i);
System.out.println("Title: " + node.getTextContent());
}
// 単一ノードを取得
Node titleNode = (Node) xpath.evaluate("//book[@id='1']/title",
doc, XPathConstants.NODE);
Pythonでの使用
import xml.etree.ElementTree as ET
tree = ET.parse('library.xml')
root = tree.getroot()
# XPath式で検索
books = root.findall(".//book[@id='1']")
for book in books:
title = book.find('title').text
print(f"Title: {title}")
# より高度なXPathにはlxmlを使用
from lxml import etree
doc = etree.parse('library.xml')
# 複雑なXPath
titles = doc.xpath("//book[@category='technology']/title/text()")
for title in titles:
print(f"Title: {title}")
ノード操作のベストプラクティス
実践的なノウハウをまとめます。
1. 空白テキストノードの扱い
問題:
XMLの整形によって、意図しない空白テキストノードが含まれます。
<book>
<title>Test</title>
</book>
このXMLでは、<book>と<title>の間に改行と空白のテキストノードが存在します。
対策1:空白を無視する設定
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setIgnoringElementContentWhitespace(true);
対策2:処理時にスキップ
NodeList children = element.getChildNodes();
for (int i = 0; i < children.getLength(); i++) {
Node child = children.item(i);
// テキストノードで空白のみの場合はスキップ
if (child.getNodeType() == Node.TEXT_NODE) {
if (child.getNodeValue().trim().isEmpty()) {
continue;
}
}
// 処理
}
2. nullチェックの徹底
Node parent = node.getParentNode();
if (parent != null) {
// 安全に処理
String parentName = parent.getNodeName();
}
NodeList children = node.getChildNodes();
if (children != null && children.getLength() > 0) {
// 子ノードの処理
}
3. 型変換の安全性
Node node = ...;
if (node.getNodeType() == Node.ELEMENT_NODE) {
Element element = (Element) node;
// 安全に要素として扱える
}
4. 属性の存在確認
Element element = ...;
// hasAttribute で存在確認
if (element.hasAttribute("id")) {
String id = element.getAttribute("id");
}
// 空文字列との区別
String category = element.getAttribute("category");
if (category != null && !category.isEmpty()) {
// 処理
}
5. メモリ効率の考慮
大容量XMLの場合、DOMは全体をメモリに読み込むため注意が必要です。
代替案:
- StAXを使用(ストリーミング処理)
- SAXを使用(イベント駆動)
- 部分的なDOM構築
ノードとXMLスキーマ
XMLスキーマによって、ノードの構造を定義できます。
DTD(Document Type Definition)
<!DOCTYPE library [
<!ELEMENT library (book+)>
<!ELEMENT book (title, author, price)>
<!ATTLIST book id ID #REQUIRED>
<!ELEMENT title (#PCDATA)>
<!ELEMENT author (#PCDATA)>
<!ELEMENT price (#PCDATA)>
]>
XML Schema(XSD)
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="library">
<xs:complexType>
<xs:sequence>
<xs:element name="book" maxOccurs="unbounded">
<xs:complexType>
<xs:sequence>
<xs:element name="title" type="xs:string"/>
<xs:element name="author" type="xs:string"/>
<xs:element name="price" type="xs:decimal"/>
</xs:sequence>
<xs:attribute name="id" type="xs:integer" use="required"/>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:schema>
検証の実装
// スキーマ検証を有効化
SchemaFactory schemaFactory = SchemaFactory.newInstance(
XMLConstants.W3C_XML_SCHEMA_NS_URI);
Schema schema = schemaFactory.newSchema(new File("library.xsd"));
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setSchema(schema);
factory.setValidating(true);
DocumentBuilder builder = factory.newDocumentBuilder();
// エラーハンドラーを設定
builder.setErrorHandler(new ErrorHandler() {
public void error(SAXParseException e) {
System.err.println("Error: " + e.getMessage());
}
// 他のメソッドも実装
});
Document doc = builder.parse(new File("library.xml"));
まとめ:ノードはXMLの基礎概念
XMLノードは、XMLドキュメントを構成する基本要素です。
この記事のポイント:
- ノードはXMLを構成する最小単位
- 12種類のノードタイプが定義されている
- 主要なノードは要素、属性、テキスト、コメントなど
- ノードはツリー構造を形成する
- 親子関係と兄弟関係でノード間が関連付けられる
- DOM APIでプログラムから操作可能
- XPathで効率的にノードを選択できる
- Java、JavaScript、Pythonなど様々な言語で扱える
- 空白テキストノードに注意が必要
- 大容量XMLではメモリ効率を考慮
ノードの概念を理解することで、XMLの操作が格段に分かりやすくなります。
WebサービスのAPI、設定ファイル、データ交換など、XMLは今でも広く使われています。ノードの仕組みを理解して、効率的なXML処理を実現しましょう!
特にDOM APIを使った開発では、ノードの種類や階層構造を意識することが、バグの少ない堅牢なコードにつながります。ぜひこの知識を実践で活用してください!


コメント