Considerations for ErrorCollection Presently, HTML Purifier takes a code-execution centric approach to handling errors. Errors are organized and grouped according to which segment of the code triggers them, not necessarily the portion of the input document that triggered the error. This means that errors are pseudo-sorted by category, rather than location in the document. One easy way to "fix" this problem would be to re-sort according to line number. However, the "category" style information we derive from naively following program execution is still useful. After all, each of the strategies which can report errors still process the document mostly linearly. Furthermore, not only do they process linearly, but the way they pass off operations to sub-systems mirrors that of the document. For example, AttrValidator will linearly proceed through elements, and on each element will use AttrDef to validate those contents. From there, the attribute might have more sub-components, which have execution passed off accordingly. In fact, each strategy handles a very specific class of "error." RemoveForeignElements - element tokens MakeWellFormed - element token ordering FixNesting - element token ordering ValidateAttributes - attributes of elements The crucial point is that while we care about the hierarchy governing these different errors, we *don't* care about any other information about what actually happens to the elements. This brings up another point: if HTML Purifier fixes something, this is not really a notice/warning/error; it's really a suggestion of a way to fix the aforementioned defects. In short, the refactoring to take this into account kinda sucks. Errors should not be recorded in order that they are reported. Instead, they should be bound to the line (and preferably element) in which they were found. This means we need some way to uniquely identify every element in the document, which doesn't presently exist. An easy way of adding this would be to track line columns. An important ramification of this is that we *must* use the DirectLex implementation. 1. Implement column numbers for DirectLex [DONE!] 2. Disable error collection when not using DirectLex [DONE!] Next, we need to re-orient all of the error declarations to place CurrentToken at utmost important. Since this is passed via Context, it's not always clear if that's available. ErrorCollector should complain HARD if it isn't available. There are some locations when we don't have a token available. These include: * Lexing - this can actually have a row and column, but NOT correspond to a token * End of document errors - bump this to the end Actually, we *don't* have to complain if CurrentToken isn't available; we just set it as a document-wide error. And actually, nothing needs to be done here. Something interesting to consider is whether or not we care about the locations of attributes and CSS properties, i.e. the sub-objects that compose these things. In terms of consistency, at the very least attributes should have column/line numbers attached to them. However, this may be overkill, as attributes are uniquely identifiable. You could go even further, with CSS, but they are also uniquely identifiable. Bottom-line is, however, this information must be available, in form of the CurrentAttribute and CurrentCssProperty (theoretical) context variables, and it must be used to organize the errors that the sub-processes may throw. There is also a hierarchy of sorts that may make merging this into one context variable more sense, if it hadn't been for HTML's reasonably rigid structure. A CSS property will never contain an HTML attribute. So we won't ever get recursive relations, and having multiple depths won't ever make sense. Leave this be. We already have this information, and consequently, using start and end is *unnecessary*, so long as the context variables are set appropriately. We don't care if an error was thrown by an attribute transform or an attribute definition; to the end user these are the same (for a developer, they are different, but they're better off with a stack trace (which we should add support for) in such cases). 3. Remove start()/end() code. Don't get rid of recursion, though [DONE] 4. Setup ErrorCollector to use context information to setup hierarchies. This may require a different internal format. Use objects if it gets complex. [DONE] ASIDE More on this topic: since we are now binding errors to lines and columns, a particular error can have three relationships to that specific location: 1. The token at that location directly RemoveForeignElements AttrValidator (transforms) MakeWellFormed 2. A "component" of that token (i.e. attribute) AttrValidator (removals) 3. A modification to that node (i.e. contents from start to end token) as a whole FixNesting This needs to be marked accordingly. In the presentation, it might make sense keep (3) separate, have (2) a sublist of (1). (1) can be a closing tag, in which case (3) makes no sense at all, OR it should be related with its opening tag (this may not necessarily be possible before MakeWellFormed is run). So, the line and column counts as our identifier, so: $errors[$line][$col] = ... Then, we need to identify case 1, 2 or 3. They are identified as such: 1. Need some sort of semaphore in RemoveForeignElements, etc. 2. If CurrentAttr/CurrentCssProperty is non-null 3. Default (FixNesting, MakeWellFormed) One consideration about (1) is that it usually is actually a (3) modification, but we have no way of knowing about that because of various optimizations. However, they can probably be treated the same. The other difficulty is that (3) is never a line and column; rather, it is a range (i.e. a duple) and telling the user the very start of the range may confuse them. For example, Foo
bar
^ ^ The node being operated on is , so the error would be assigned to the first caret, with a "node reorganized" error. Then, the ChildDef would have submitted its own suggestions and errors with regard to what's going in the internals. So I suppose this is ok. :-) Now, the structure of the earlier mentioned ... would be something like this: object { type = (token|attr|property), value, // appropriate for type errors => array(), sub-errors = [recursive], } This helps us keep things agnostic. It is also sufficiently complex enough to warrant an object. So, more wanking about the object format is in order. The way HTML Purifier is currently setup, the only possible hierarchy is: token -> attr -> css property These relations do not exist all of the time; a comment or end token would not ever have any attributes, and non-style attributes would never have CSS properties associated with them. I believe that it is worth supporting multiple paths. At some point, we might have a hierarchy like: * -> syntax -> token -> attr -> css property -> url -> css stylesheet