Chapter 38: DOM Traversing
XML DOM – Traversing the Node Tree.
I will explain it as if I’m your personal teacher sitting next to you — drawing on the whiteboard, showing step-by-step examples, using analogies, pointing out typical beginner traps, and giving you realistic code you can copy-paste and experiment with.
We will go slowly and methodically so you really feel how to walk around the tree, understand the relationships between nodes, and know when to use each technique.
1. What does “traversing the node tree” actually mean?
Traversing = walking around the DOM tree in a controlled way.
You start from one node (usually the root or some element you found with querySelector) and then move:
- down → to children
- up → to parent
- sideways → to siblings
- or recursively → visit every node in a subtree
Why do we need to traverse manually?
Because:
- querySelector / querySelectorAll are great when you know exactly what you are looking for
- But many real tasks require walking the structure because:
- You want all descendants in a specific order
- You need to preserve hierarchy (parent–child relationships)
- You are building a tree view, pretty-printing, validating structure, extracting paths, transforming XML, etc.
2. The most important navigation properties (your “directions”)
| Property | Returns | Direction | Includes text/comment nodes? | Live? | Most common use case |
|---|---|---|---|---|---|
| parentNode | Node or null | ↑ up | Yes | Yes | Go to parent (safest general property) |
| parentElement | Element or null | ↑ up | — | Yes | Go to parent when you are sure it’s an element |
| childNodes | NodeList | ↓ all children | Yes | Yes | Get everything inside (text + elements + comments) |
| children | HTMLCollection | ↓ only elements | No | Yes | Get only element children (skip whitespace) |
| firstChild | Node or null | ↓ first | Yes | Yes | First child (often whitespace text node) |
| firstElementChild | Element or null | ↓ first element | No | Yes | First real element child |
| lastChild | Node or null | ↓ last | Yes | Yes | Last child |
| lastElementChild | Element or null | ↓ last element | No | Yes | Last real element child |
| nextSibling | Node or null | → next | Yes | Yes | Next node at same level (often whitespace) |
| nextElementSibling | Element or null | → next element | No | Yes | Next real element sibling |
| previousSibling | Node or null | ← previous | Yes | Yes | Previous node |
| previousElementSibling | Element or null | ← previous element | No | Yes | Previous real element sibling |
Golden rule for beginners
If you want only elements (most of the time) → use properties with “Element” in the name (children, firstElementChild, nextElementSibling, etc.)
If you want everything (text nodes, comments, etc.) → use properties without “Element” (childNodes, firstChild, nextSibling, etc.)
3. Visual example – Let’s see the tree & relationships
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
<library> <book id="101"> <title>Atomic Habits</title> <author>James Clear</author> </book> <book id="102"> <title>Rich Dad Poor Dad</title> </book> </library> |
Simplified tree (ignoring whitespace text nodes for clarity):
|
0 1 2 3 4 5 6 7 8 9 10 11 |
library (root element) ├── book #1 (id="101") │ ├── title │ └── author └── book #2 (id="102") └── title |
Now let’s pick the <author> node and see what we can reach:
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
const author = xmlDoc.querySelector("author"); // Up console.log(author.parentNode.tagName); // "book" console.log(author.parentElement.tagName); // "book" // Down (children) console.log(author.childNodes.length); // usually 1 (text node) console.log(author.firstChild.nodeType); // 3 = Text console.log(author.firstChild.textContent.trim()); // "James Clear" // Sideways (siblings) console.log(author.previousElementSibling.tagName); // "title" console.log(author.nextElementSibling); // null (no next element) // Grandparent console.log(author.parentElement.parentElement.tagName); // "library" |
4. Practical code – Manual tree traversal patterns
Pattern 1 – Recursive “visit every element” function
|
0 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 |
function printTree(node, level = 0) { const indent = " ".repeat(level); if (node.nodeType === 1) { // only elements let info = `<${node.tagName}>`; if (node.hasAttributes()) { info += " ["; for (let attr of node.attributes) { info += ` ${attr.name}="${attr.value}"`; } info += " ]"; } console.log(indent + info); // Visit children for (let child of node.children) { printTree(child, level + 1); } } } // Usage printTree(xmlDoc.documentElement); |
Output looks like:
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
<library> <book> [ id="101" lang="en" category="self-help" ] <title> <author> <price> [ currency="INR" ] <inStock> <book> [ id="102" lang="en" category="finance" ] <title> <magazine> [ id="M001" ] <title> <issue> <price> [ currency="INR" ] |
Pattern 2 – Get path from any node to root
|
0 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 |
function getPathToRoot(node) { const path = []; let current = node; while (current && current.nodeType !== 9) { // stop at Document if (current.nodeType === 1) { let name = current.tagName.toLowerCase(); if (current.hasAttribute("id")) { name += `#{current.getAttribute("id")}`; } path.unshift(name); } current = current.parentNode; } return "/" + path.join("/"); } // Usage const priceNode = xmlDoc.querySelector("price"); console.log(getPathToRoot(priceNode)); // → /library/book/price |
Pattern 3 – Flatten all text content (skip markup)
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
function getAllText(node) { let text = ""; for (let child of node.childNodes) { if (child.nodeType === 3) { // Text node text += child.textContent; } else if (child.nodeType === 1) { // Element text += getAllText(child); } } return text.trim(); } console.log(getAllText(xmlDoc.querySelector("book"))); // → "Atomic HabitsJames Clear499.00true" |
5. Common traps & how to avoid them
Trap 1 — Assuming childNodes only contains elements
|
0 1 2 3 4 5 6 7 8 |
for (let child of book.childNodes) { console.log(child.tagName); // many undefined — whitespace text nodes } |
Fix: for (let child of book.children) or check child.nodeType === 1
Trap 2 — Modifying the tree while traversing it
|
0 1 2 3 4 5 6 7 8 |
for (let child of parent.childNodes) { child.remove(); // can break the loop — collection changes live } |
Fix: Convert to array first or loop backwards
|
0 1 2 3 4 5 6 7 8 9 |
const kids = Array.from(parent.childNodes); for (let child of kids) { child.remove(); } |
Summary – XML DOM Tree Navigation Quick Reference
| You want to go… | Property / Method | Returns | Includes text nodes? | Tip / Note |
|---|---|---|---|---|
| Up – to parent | parentNode / parentElement | Node / Element | — | parentElement skips non-element parents |
| Down – all children | childNodes | NodeList | Yes | Includes whitespace, comments |
| Down – only elements | children | HTMLCollection | No | Most useful in practice |
| First/last child | firstChild / lastChild | Node | Yes | Often whitespace |
| First/last element child | firstElementChild / lastElementChild | Element | No | Usually what you want |
| Next/previous sibling | nextSibling / previousSibling | Node | Yes | Often whitespace |
| Next/previous element sibling | nextElementSibling / previousElementSibling | Element | No | Skips text/comment nodes |
Would you like to continue with one of these practical next steps?
- Recursive traversal – build your own XML pretty-printer
- Path from node to root – very useful for debugging
- Flatten / collect all text – common in extraction tasks
- Ignore / clean whitespace text nodes
- Modify the tree while traversing (safely)
- Real-world examples: RSS feed, SOAP envelope, configuration file
Just tell me which direction feels most interesting or useful for you right now! 😊
