public static SyntaxTreeNode ReplaceTextSpans(SyntaxTreeNode node, Func <String, IList <TextSpanReplaceInfo> > getTextSpansToReplace, Func <TagNode, bool> tagFilter) { if (node == null) { throw new ArgumentNullException("node"); } if (getTextSpansToReplace == null) { throw new ArgumentNullException("getTextSpansToReplace"); } TextNode Node = node as TextNode; if (Node != null) { String text = Node.Text; IList <TextSpanReplaceInfo> replacements = getTextSpansToReplace(text); if (replacements == null || replacements.Count == 0) { return(node); } List <SyntaxTreeNode> replacementNodes = new List <SyntaxTreeNode>(replacements.Count * 2 + 1); Int32 lastPos = 0; foreach (TextSpanReplaceInfo r in replacements) { if (r.Index < lastPos) { throw new ArgumentException("the replacement text spans must be ordered by index and non-overlapping"); } if (r.Index > text.Length - r.Length) { throw new ArgumentException("every replacement text span must reference a range within the text node"); } if (r.Index != lastPos) { replacementNodes.Add(new TextNode(text.Substring(lastPos, r.Index - lastPos))); } if (r.Replacement != null) { replacementNodes.Add(r.Replacement); } lastPos = r.Index + r.Length; } if (lastPos != text.Length) { replacementNodes.Add(new TextNode(text.Substring(lastPos))); } return(new SequenceNode(replacementNodes)); } List <SyntaxTreeNode> fixedSubNodes = node.SubNodes.Select(n => { if (n is TagNode && (tagFilter != null && !tagFilter((TagNode)n))) { return(n); //skip filtered tags } SyntaxTreeNode repl = ReplaceTextSpans(n, getTextSpansToReplace, tagFilter); Debug.Assert(repl != null); return(repl); }).ToList(); return(fixedSubNodes.SequenceEqual(node.SubNodes, ReferenceEqualityComparer <SyntaxTreeNode> .Instance) ? node : node.SetSubNodes(fixedSubNodes)); }