The NotesDOM Node Types You Skipped — Until a Round-Trip Drops Your Comments
You’ve used NotesDOMParser before — walk the tree with GetFirstChild / GetNextSibling, read element and text nodes, done. Then a real document lands on your desk: an XML config with a leading <!-- generated, do not edit --> comment, a <![CDATA[ ... ]]> block holding a chunk of markup, and a <?xml-stylesheet ... ?> processing instruction at the top. You parse it, change one attribute, serialise it back — and the comment and the PI are gone, the CDATA came out as escaped text. Nothing errored. The output is just quietly wrong.
The DOM node types beyond element / text / attribute are the reason. Most of them you’ll never instantiate by hand, but two of them (NotesDOMNodeList, NotesDOMDocumentFragmentNode) earn their place in everyday code, and three more (CDATA / comment / processing-instruction nodes) are the difference between a faithful round-trip and a lossy one. This is a field report on that long tail, tested against the LotusScript DOM parser. All of these classes are the same vintage — new in Release 6, and none are supported in COM.
TL;DR
NotesDOMNodeList.GetItem(i)is 1-based — you loopFor i = 1 To list.NumberOfEntries. That’s the LotusScript convention, and it’s the opposite of the W3C DOM’s 0-baseditem(). Porting a JavaScript loop straight over is an off-by-one waiting to happen.NotesDOMDocumentFragmentNodeis a scratch container: append nodes to it, insert it, and “the fragment’s children are inserted, not the fragment itself.” One insert instead of N.- Comments, CDATA sections, and processing instructions are their own node types. A parse walk that only looks for element and text nodes skips them, and a serialise that never re-creates them drops them. Preserving them means handling those node types explicitly.
- You create the manual ones off the document node:
CreateCommentNode,CreateCDATASectionNode,CreateProcessingInstructionNode(target, data).
NodeList: the 1-based GetItem
A NotesDOMNodeList is what tree queries hand back — GetElementsByTagName on a document or element node returns one, and so do the child-collection accessors. It has exactly one property and one method: NumberOfEntries (read-only) and GetItem, which returns “the node at the position index& in the list, or null if that is not a valid index.”
The trap is the index base. The list walks from 1, not 0:
Dim nl As NotesDOMNodeListSet nl = docNode.GetElementsByTagName("item")Dim i As LongFor i = 1 To nl.NumberOfEntries Dim n As NotesDOMNode Set n = nl.GetItem(i) Print n.NodeNameNextIf you’ve spent time in the browser DOM, your fingers will write for (i = 0; i < list.length; i++) and translate it to For i = 0 To nl.NumberOfEntries - 1. That silently skips the first node and reads one past the end (which returns null, then blows up on the next property access). The LotusScript collections are 1-based across the board; NotesDOMNodeList is no exception, even though it mirrors a W3C interface that is 0-based. When in doubt, anchor the loop to NumberOfEntries and start at 1.
DocumentFragment: insert N nodes with one call
A NotesDOMDocumentFragmentNode is a node that never appears in the finished tree. You make one with CreateDocumentFragmentNode on the document node, append however many nodes you want to it, then append the fragment to a real parent. The documented behaviour is the whole point: “the fragment’s children are inserted, not the fragment itself.”
Dim frag As NotesDOMDocumentFragmentNodeSet frag = docNode.CreateDocumentFragmentNode()Dim r As NotesDOMElementNodeForall row In rows Set r = docNode.CreateElementNode("row") Call r.SetAttribute("id", row) Call frag.AppendChild(r)End ForallCall tableNode.AppendChild(frag) ' the rows land under tableNode, the fragment doesn'tWithout the fragment you’d AppendChild each row directly under tableNode, touching the live tree once per row. The fragment lets you assemble off to the side and splice the whole batch in with a single operation against the live tree. For a handful of nodes it’s a readability win; for a few hundred built in a loop it’s the difference that keeps the build from crawling.
Comments, CDATA, and PIs: the round-trip you have to opt into
The three “content” node types are easy to forget because a naive walk never surfaces them. If your traversal only branches on element and text nodes, a comment node, a CDATA node, and a processing-instruction node all fall through — and if your serialiser only re-emits elements and text, they never come back. Preserving them is a matter of recognising the node types on the way in and re-creating them on the way out.
Creating them is uniform — each has a factory on the document node:
Dim c As NotesDOMCommentNodeSet c = docNode.CreateCommentNode(" generated, do not edit ")
Dim cd As NotesDOMCDATASectionNodeSet cd = docNode.CreateCDATASectionNode("<b>literal markup</b>")
Dim pi As NotesDOMProcessingInstructionNodeSet pi = docNode.CreateProcessingInstructionNode("xml-stylesheet", "type=""text/xsl"" href=""view.xsl""")Two details worth keeping straight. First, the inheritance: a CDATA node is a text node — NotesDOMCDATASectionNode extends NotesDOMTextNode, which extends NotesDOMCharacterDataNode — and a comment node extends NotesDOMCharacterDataNode directly. So the character-data editing methods (AppendData, InsertData, SubstringData, ReplaceData, DeleteData) work on all three, which is handy but also a trap: a CDATA node querying as character data means a walk that treats “has character data” as “is text” will misclassify it. Branch on the node type, not on whether NodeValue is set.
Second, the processing-instruction node splits its content into two named parts — Target (the application the instruction is aimed at, xml-stylesheet above) and Data (everything after it). A PI is “a way to keep processor-specific information in the text of the document,” so if you’re transforming a stylesheet-linked XML and want the link to survive, you read Target and Data off the old node and re-create the pair, rather than trying to reconstruct the <? ?> text by hand.
What about Java and SSJS?
There’s no cross-language table to draw here, and that’s the interesting part. The NotesDOM* classes are the LotusScript DOM parser’s own object model. On the Java side you don’t reach for a Domino class at all — you use the standard org.w3c.dom interfaces (NodeList, DocumentFragment, Comment, CDATASection, ProcessingInstruction) through a JAXP parser, where NodeList.item() is 0-based. In SSJS / XPages you’re typically in the browser or server-side JavaScript DOM, again 0-based. The node concepts are the same W3C model everywhere; only the LotusScript surface renumbers the list to 1. That renumbering is the one thing to carry in your head when you move a DOM loop between LotusScript and either of the others.
Sources
- NotesDOMNodeList (LotusScript) — HCL Domino Designer Help
- NotesDOMDocumentFragmentNode (LotusScript) — HCL Domino Designer Help
- NotesDOMProcessingInstructionNode (LotusScript) — HCL Domino Designer Help
- NotesDOMCDATASectionNode (LotusScript) — HCL Domino Designer Help
- NotesDOMCommentNode (LotusScript) — HCL Domino Designer Help