NotesRichTextNavigator × NotesRichTextRange: Traversing and Rewriting Rich Text in Code
Here’s the task: across 500 documents, replace the old company name “Acme” with “AcmeCorp” everywhere in the Body field, and while you’re at it, count how many doclinks each document has. The earlier NotesRichTextItem piece gets you the whole Body field and lets you AppendText, but it hands you the field as one block — it can’t say “move to the next table” or “select this span and swap the text.”
For that kind of fine-grained traversal and rewriting, Domino added two companions back in Release 6: NotesRichTextNavigator (move a “cursor” through rich text) and NotesRichTextRange (select a span and then act on it). This article shows how they work together — and the one FindAndReplace trap that bites if nobody warns you.
TL;DR
- Both come from a
NotesRichTextItem:body.CreateNavigator(),body.CreateRange() - Navigator = a cursor: walk with
FindFirstElement/FindNextElementplus element-type constants (RTELEM_TYPE_TABLE,RTELEM_TYPE_DOCLINK…) - Limit: a navigator only moves within one type — it can find “the next table”, but there’s no “next element regardless of type”
- Range = a selection: bound it with
SetBegin/SetEnd(using a navigator), thenSetStyleto format,Removeto delete, or readTextRun/TextParagraph FindAndReplace(target, replacement, options)returns the number of replacements; options combine:RT_FIND_CASEINSENSITIVE,RT_REPL_ALL, etc.- The rule: after a
FindAndReplace, every navigation marker in that rich text item (ranges included) is invalidated — rebuild before continuing
Two tools, both from NotesRichTextItem
Neither class is created with New — both grow out of the rich text field you already have:
Dim body As NotesRichTextItemSet body = doc.GetFirstItem("Body")
Dim rtnav As NotesRichTextNavigatorSet rtnav = body.CreateNavigator()
Dim rtrange As NotesRichTextRangeSet rtrange = body.CreateRange()The division of labour is clean: the navigator decides “where to move to”; the range decides “what to do with the selected span.” They’re usually used together — the navigator locates a spot, then hands its position to the range as a boundary.
NotesRichTextNavigator: walk by element type
The official definition: “Represents a means of navigation in a rich text item.” It keeps a “current position” that you move with the Find family:
| Method | What it does |
|---|---|
FindFirstElement(type) | move to the first element of a type, returns Boolean |
FindNextElement(type) | move to the next element of the same type |
FindNthElement(type, n) | move to the nth one |
FindFirstString(s) / FindNextString(s) | move to a string occurrence |
GetElement() | get the element at the current position |
type uses the RTELEM_TYPE_* constants, covering the various “things” in rich text:
| Constant | Element |
|---|---|
RTELEM_TYPE_TEXTPARAGRAPH | text paragraph |
RTELEM_TYPE_TEXTRUN | text run (a same-style span) |
RTELEM_TYPE_DOCLINK | doclink |
RTELEM_TYPE_FILEATTACHMENT | file attachment |
RTELEM_TYPE_OLE | OLE object |
RTELEM_TYPE_SECTION | collapsible section |
RTELEM_TYPE_TABLE | table |
RTELEM_TYPE_TABLECELL | table cell |
⚠️ The key limitation, verbatim: “Navigation is within elements of the same type… You cannot find or get an element regardless of type.” A navigator only moves within one type. There’s no “give me the next element, whatever it is.” To walk a Body you have to decide “which type am I after this pass” and scan one type at a time.
The canonical traversal is a loop opened by FindFirstElement and closed by FindNextElement:
If rtnav.FindFirstElement(RTELEM_TYPE_DOCLINK) Then Dim n As Integer Do n = n + 1 Loop While rtnav.FindNextElement(RTELEM_TYPE_DOCLINK)End If' n = the number of doclinks in this document's BodyNotesRichTextRange: select a span, then act on it
The official definition: “Represents a range of elements in a rich text item.” The default range covers the whole item. Its read-only properties let you “see” what the span is:
| Property | Holds |
|---|---|
Type | the type of the first element in the range |
TextRun | text from the start to the next newline or style change |
TextParagraph | text from the start to the next paragraph |
Style | the style of the first text run |
Navigator | a navigator confined to the range |
The methods let you “act”: SetBegin(nav) / SetEnd(nav) to bound the span with a navigator, SetStyle(style) to apply a style across the range, Remove() to delete its content, Reset() to restore defaults, Clone() to copy.
Bumping a selected span’s font size is straight from the official example:
Set body = doc.GetFirstItem("Body")Set rtrange = body.CreateRangeSet rtstyle = session.CreateRichTextStylertstyle.FontSize = 18Call rtrange.SetStyle(rtstyle)Call doc.Save(True, True)FindAndReplace: find-and-swap across rich text
FindAndReplace is NotesRichTextRange’s most useful method. The signature:
count& = rtrange.FindAndReplace(target$, replacement$, [options&])The return value is the number of replacements actually made (Long). options combine via addition or Or:
| Constant | Value | Meaning |
|---|---|---|
RT_FIND_CASEINSENSITIVE | 1 | case-insensitive (default is case-sensitive) |
RT_FIND_PITCHINSENSITIVE | 2 | pitch-insensitive |
RT_FIND_ACCENTINSENSITIVE | 4 | accent-insensitive |
RT_REPL_PRESERVECASE | 8 | preserve case in the replacement |
RT_REPL_ALL | 16 | replace all occurrences (default replaces only the next one) |
Replacing every “Acme” with “AcmeCorp” in the Body, case-insensitive:
Dim rtrange As NotesRichTextRangeSet rtrange = body.CreateRangeDim cnt As Longcnt = rtrange.FindAndReplace("Acme", "AcmeCorp", _ RT_REPL_ALL Or RT_FIND_CASEINSENSITIVE)If cnt > 0 Then Call doc.Save(True, True)⚠️ And here’s the trap you must remember, verbatim: “All navigation markers in the RTitem, including ranges, are invalidated after a FindAndReplace operation.” Once FindAndReplace runs, every navigator and range on that rich text item becomes invalid. If you need to keep traversing or select another span, you must build a fresh range with SetBegin / SetEnd — you can’t reuse the old one. A lot of “everything goes weird after the replace” bugs trace back to exactly this.
What about Java and SSJS?
This pair exists in all three languages, with consistent names:
| Language | Counterpart | Created via |
|---|---|---|
Java (lotus.domino.*) | RichTextNavigator / RichTextRange | rtitem.createNavigator() / createRange() |
| SSJS / XPages | RichTextNavigator / RichTextRange | same |
The concepts are identical: navigator to traverse, range to select and rewrite, the same element-type constants. The only differences are that Java / SSJS use method calls (findFirstElement(...)) rather than property syntax, and the constants take the RichTextItem.RTELEM_TYPE_* form on the Java side. When you write a back-end Java batch over rich text, the flow in this article carries straight over.