Parsing XML in LotusScript — DOM or SAX? Five Questions That Pick the Right Tool

Parsing XML in LotusScript — DOM or SAX? Five Questions That Pick the Right Tool

May 18, 2026 968 words

TL;DR

You have an XML file in LotusScript — three routes, each with its niche:

RouteRight forWrong for
NotesDOMParser (DOM)Small-to-medium XML, modification, random-access walkingLarge files, pure read
NotesSAXParser (SAX)Large files, pure read, extracting specific elementsModification, random-access walking
NotesXMLProcessor + NotesXSLTransformer (XSLT)Rule-based XML → XML (or HTML) transformationComplex conditional logic, calculations

Five decision questions to pick the right one in 30 seconds:

  1. File size?
  2. Need to modify?
  3. Traversal direction?
  4. Memory budget?
  5. Is it XML → XML transformation?

Picking wrong doesn’t break things, but you’ll suffer — 100 MB XML through DOM is an OOM; modifying XML through SAX is hand-rolled pain.

The three approaches at a fundamental level

AspectDOM (NotesDOMParser)SAX (NotesSAXParser)XSLT (NotesXSLTransformer)
ModelWhole tree loaded into memoryEvent-driven streamingRule-based transformation
MemoryProportional to file sizeConstantProportional to XML size (DOM underneath)
TraversalRandom access in any directionForward-only, single passXPath selectors
ModificationYesNo (read-only)Emits a new version via XSLT templates
Code shapeLinear (parse → walk → modify → serialize)Event handlers + global stateWrite an XSLT stylesheet (separate language)
Learning curveMedium (W3C DOM concepts)Medium-high (event-driven + state machine)High (XSLT is its own language)
IntroducedR6+R6+Later (XSLT generation)

Five decision questions

Question 1: File size?

SizePick
< 1 MBAnything — DOM is most pleasant to write
1 - 10 MBDOM is still fine, SAX also works
10 - 100 MBLean toward SAX, DOM starts pressuring memory
> 100 MBSAX mandatory, DOM will OOM

In practice 90% of XML integrations (API responses, config files, DXL exports) are under 1 MB — DOM is the no-brainer.

Question 2: Need to modify the XML?

  • Yes → DOM (or XSLT for transformation scenarios). SAX can’t modify XML
  • No → either SAX or DOM works

Note: “I want to emit a modified version of the input but don’t need to incrementally modify it” is exactly when XSLT is the real tool — describe input shape / output shape declaratively in a stylesheet, transformation becomes rule-based and maintainable.

Question 3: Traversal direction?

  • Forward-only is enough (read through once, extract specific elements) → SAX
  • Need to look back, jump to parent, compare with sibling → DOM

Example: “find each <order> whose <total> is greater than its <budget>” — need random access inside the <order> subtree, DOM fits. “list every <product> name” — one forward pass, SAX fits.

Question 4: Memory budget?

  • Generous (multi-GB heap) → DOM, freely
  • Tight (agent runtime limits, shared environment, low-spec server) → SAX

Client-side agents have a tighter memory budget than server scheduled agents. Processing big XML in a client agent → SAX is mandatory.

Question 5: Is it XML → XML / XML → HTML transformation?

XSLT is declarative — describe what the output should look like, the XSLT engine handles the rest. Complex branching is awkward in XSLT — at that point DOM + LS code is cleaner.

Combined decision tree

The five questions strung together:

You have an XML file
├─ Rule-based XML → XML / HTML transformation? ───────────► XSLT
├─ Need to modify the XML? ────── Yes ─────────────────────► DOM
│ │
│ No
│ │
├─ File > 100 MB? ──────────────── Yes ─────────────────────► SAX
│ │
│ No
│ │
├─ Memory tight? ───────────────── Yes ─────────────────────► SAX
│ │
│ No
│ │
├─ Need random-access traversal (parent / sibling)? ─ Yes ──► DOM
│ │
│ No
│ │
└─ ► DOM (small file, forward-only OK, easier to write)

Code-style side-by-side

Same task — extract every <title> text — DOM vs SAX:

DOM (walk freely, simple flow)

Sub Initialize
Dim session As New NotesSession
Dim parser As NotesDOMParser
Dim s As NotesStream
Set s = session.CreateStream
Call s.Open("c:\data\books.xml")
Set parser = session.CreateDOMParser(s, Nothing)
Call parser.Process()
' One recursive function handles it
Call PrintTitles(parser.Document)
End Sub
Sub PrintTitles(node As NotesDOMNode)
If node.NodeType = DOMNODETYPE_ELEMENT_NODE Then
Dim e As NotesDOMElementNode
Set e = node
If e.TagName = "title" And e.HasChildNodes Then
Print e.FirstChild.NodeValue
End If
End If
Dim c As NotesDOMNode
Set c = node.FirstChild
Do Until c Is Nothing
Call PrintTitles(c)
Set c = c.NextSibling
Loop
End Sub

Linear thinking: parse → recurse → print.

SAX (state machine, global variables)

Option Public
Dim insideTitle As Boolean
Dim currentText As String
Sub Initialize
Dim session As New NotesSession
Dim parser As NotesSAXParser
Dim s As NotesStream
Set s = session.CreateStream
Call s.Open("c:\data\books.xml")
Set parser = session.CreateSAXParser(s, Nothing)
On Event SAX_StartElement From parser Call OnStart
On Event SAX_EndElement From parser Call OnEnd
On Event SAX_Characters From parser Call OnChars
Call parser.Parse()
End Sub
Sub OnStart(Source As NotesSAXParser, NS As String, _
LocalName As String, QName As String, Attrs As NotesSAXAttributeList)
If LocalName = "title" Then
insideTitle = True
currentText = ""
End If
End Sub
Sub OnChars(Source As NotesSAXParser, Chars As String, Start As Long, Length As Long)
If insideTitle Then
currentText = currentText & Mid(Chars, Start + 1, Length)
End If
End Sub
Sub OnEnd(Source As NotesSAXParser, NS As String, _
LocalName As String, QName As String)
If LocalName = "title" Then
Print currentText
insideTitle = False
End If
End Sub

State-machine thinking: track “which element am I currently in” via globals, accumulate in Characters, emit in EndElement.

Same functional output, SAX is roughly twice the lines and higher mental overhead — but the memory profile is ~1/100 of DOM. The tradeoff is explicit.

Performance and memory (rough numbers)

For a 10 MB XML file (~50,000 elements):

ToolParse timePeak memoryWalk-once time
DOM~5 sec~80 MB~0.5 sec (in-memory)
SAX~3 sec (combined parse+walk)~5 MBn/a — parse and walk are the same pass
XSLT~6-10 sec~80 MBsame as parse

Observations:

  • SAX parsing is faster than DOM — no tree construction overhead
  • DOM peak memory is roughly 8× the file size — node object overhead is real
  • DOM walking is essentially free — one parse, any number of cheap walks

If you need to walk multiple times (e.g. cross-referencing different elements), DOM still wins on total cost.

Consolidated pitfalls table

PitfallDOMSAXXSLT
Forgot to Serialize after modifying✅ Easy to hit — change tree but never serializen/a (can’t modify)n/a
Missing error handler⚠️ Check Log property after parse✅ Skipping SAX_FatalError causes silent failn/a
Large file OOM✅ Hits at 100 MB+n/a✅ Same as DOM
Text is a child node✅ Can’t do elem.Text directlyn/a (text comes via Characters event)n/a
Characters fires multiple timesn/a✅ Same text run may fire multiple timesn/a

The XML toolchain in 14.5 — basically unchanged

LotusScript’s XML tooling hasn’t moved in years — NotesDOMParser / NotesSAXParser / NotesXMLProcessor were introduced in R6 and the API stabilized. The 14.5 series added no new XML classes or major API changes (14.5.1 What’s New is mostly about JSON Copy and OIDC tokens).

The upside: learn it once, use it for ten years. Today’s API runs on R6 / R8 / R9 / 12.x / 14.x without change.

What about Java and SSJS?

LanguageDOMSAXXSLT
LotusScriptNotesDOMParserNotesSAXParserNotesXMLProcessor + NotesXSLTransformer
Javalotus.domino.DOMParser or standard javax.xml.parsers.DocumentBuilderFactorylotus.domino.SAXParser or standard javax.xml.parsers.SAXParserFactoryjavax.xml.transform.Transformer
SSJS (XPages)Usually uses Java’s javax.xml.parsers directlySameSame as Java

The Java/SSJS advantage: you can drop down to the standard javax.xml.parsers API which is richer than lotus.domino.* (XPath, Schema validation, Streaming XPath, etc.). LotusScript is stuck with NotesDOMParser / NotesSAXParser — no choice.

If LS XML processing has become painful, rewriting that piece as a Java agent is often the right move — performance and capability both step up.

Closing

XML processing is a hard requirement for LS integration work. Three routes, each with its niche:

  1. Default to DOM — small file, easy to write, modifiable, serializable
  2. Switch to SAX above 10 MB or when memory is tight — streaming, event-handler style is harder to write but memory stays constant
  3. Use XSLT for rule-based transformations — declarative, maintainable, steeper learning curve but long-term win

The decision tree to remember: modify? big file? random walk? — pick one of three.

Read alongside the two deep-dive pieces:

That closes the loop on the LS XML triad.

Sources

← Back to all posts