HighlightingLoader.Load(XmlReader) mis-parses non-normalizing readers (e.g. XDocument.CreateReader): silently empty main RuleSet, or misleading "Could not find main RuleSet." #454
Labels
No labels
Area-Accessibility
Area-Build
Area-CodeCompletion
Area-Document
Area-Documentation
Area-Editing
Area-Folding
Area-Highlighting
Area-i18n
Area-Indentation
Area-NetFx
Area-Rendering
Area-Search
Area-Snippets
dependencies
duplicate
Feedback Needed
help wanted
invalid
Issue-Bug
Issue-Discussion
Issue-Enhancement
Issue-Performance
Issue-Question
on-hold
Resolution-Answered
Resolution-By Design
Resolution-Duplicate
Resolution-Fixed
Resolution-Won't Fix
Up-for-Grabs
No milestone
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
gh-icsharpcode/AvalonEdit#454
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
HighlightingLoader.Load(XmlReader, IHighlightingManager)implicitly requires the caller's reader to report insignificant whitespace asXmlNodeType.Whitespace. When it does not —XDocument.CreateReader()(i.e.XDocument/XElement, public API) is the easy way to hit this — the hand-written parser inV2Loaderdesynchronizes and either returns a definition whose mainRuleSetis empty (no exception,Rules.Count == 0, highlighting silently does nothing), or throwsHighlightingDefinitionInvalidException: Could not find main RuleSet.Which one you get depends on the number of whitespace runs / XML comments in the file: adding or removing a single
<!-- -->flips the outcome, even though comments are never parsed. The input is fixed, yet the result looks random.Minimal reproduction
Valid
.xshd(loads correctly on its own):Identical bytes, only the reader differs: A → 3 rules, B → 0.
Root cause
XDocument.CreateReader()returns anXNodeReaderthat reports whitespace-only text asXmlNodeType.Text, notWhitespace. A node dump shows one such node per indentation run (d1 Text <> NS=[] val=[\r\n\t],d2 Text <> NS=[] val=[\r\n\t\t], …); withXmlTextReaderthe same document produces zero such nodes.GetValidatingReaderwraps the caller's reader withXmlReaderSettings { IgnoreComments = true, IgnoreWhitespace = true, ValidationType = Schema, Schemas = ModeV2 }.IgnoreWhitespacediscards only nodes whoseNodeTypeisWhitespace, so for a non-normalizing reader it is a no-op and every indentation run reaches the parser. (IgnoreCommentsdoes drop comments — but each comment splits the surrounding whitespace into two text runs, which is exactly why the comment count changes the outcome.)V2Loader.ParseElementscallsreader.Skip()for every node whoseNamespaceURI != Namespace, insidewhile (reader.Read() && reader.NodeType != XmlNodeType.EndElement). Text nodes have an empty namespace, so every whitespace node takes theSkip()branch;Skip()on a non-element node is just aRead(), and the loopRead()s again at the top of the next iteration. Two node positions are consumed per iteration, so element start tags are stepped over and never become the current node (they are only "hit" when the parity happens to line up).Consequences:
<RuleSet>, but all children are consumed as text siblings →XshdRuleSet.Elementsempty → main RuleSet with zero rules → no exception, no highlighting;<RuleSet>start tag itself is stepped over → no unnamed RuleSet indef.Elements→throw new HighlightingDefinitionInvalidException("Could not find main RuleSet.").Single-step trace (V2Loader's state machine replicated verbatim, run on the wrapped reader of case B):
With one extra
<!-- -->in the same file thePARSE RuleSetline never appears and the log ends with<def.Elements> ... parsed 0→ the exception.Why this looks like a library-side bug
Load(XmlReader)contract is not documented as "the reader must normalize whitespace", and returningTextfor whitespace-only content is legal for anXmlReader—XNodeReaderdoes exactly that.Suggested fixes (any one closes it)
A non-element node then falls through the loop and costs only the single
Read()it is supposed to cost, keeping alignment intact.Or harden
GetValidatingReader: filter whitespace-onlyText/SignificantWhitespacenodes coming from the inner reader (a small delegatingXmlReader). The parser only cares about significant content, so that is the safest place to normalize.Or improve diagnostics: when no unnamed
RuleSetis found, report the parsed element count and hint that the input reader must normalize whitespace (or fall back to the firstRuleSetwith a warning). The current message sends users hunting in the wrong place.Workaround for anyone hitting this
Verified: 3 rules for the file above, 4 rules after appending a rule via
XElement.Add.Happy to attach the standalone repro harness (a small
.csthat dumps both node streams and runs the replicated state machine) and the raw logs. Thanks for the library — this is the only issue we have hit in years of using it.