Skip to content

Commit a3f025f

Browse files
committed
Fixed validation issues
1 parent 6bdd951 commit a3f025f

1 file changed

Lines changed: 19 additions & 135 deletions

File tree

ts/DOMBuilder.ts

Lines changed: 19 additions & 135 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212

1313
import { CData } from "./CData";
1414
import { Catalog } from "./Catalog";
15-
import { Constants } from "./Constants";
1615
import { ContentHandler } from "./ContentHandler";
1716
import { ProcessingInstruction } from "./ProcessingInstruction";
1817
import { TextNode } from "./TextNode";
@@ -22,10 +21,9 @@ import { XMLDeclaration } from "./XMLDeclaration";
2221
import { XMLDocument } from "./XMLDocument";
2322
import { XMLDocumentType } from "./XMLDocumentType";
2423
import { XMLElement } from "./XMLElement";
25-
import { XMLNode } from "./XMLNode";
2624
import { XMLUtils } from "./XMLUtils";
2725
import { DTDParser } from "./dtd/DTDParser";
28-
import { AttributeUse, Grammar, ValidationContext } from "./grammar/Grammar";
26+
import { Grammar } from "./grammar/Grammar";
2927
import { GrammarHandler } from "./grammar/GrammarHandler";
3028

3129
export class DOMBuilder implements ContentHandler {
@@ -41,15 +39,13 @@ export class DOMBuilder implements ContentHandler {
4139
grammar: Grammar | undefined;
4240
validating: boolean = false;
4341
private includeDefaultAttributes: boolean = true;
44-
private rootElementValidated: boolean = false;
4542
private declaredIds: Set<string> = new Set();
4643
private pendingIdrefs: string[] = [];
4744

4845
initialize(): void {
4946
this.document = new XMLDocument();
5047
this.stack = new Array();
5148
this.inCdData = false;
52-
this.rootElementValidated = false;
5349
this.declaredIds.clear();
5450
this.pendingIdrefs = [];
5551
// Create initial GrammarHandler for this ContentHandler
@@ -78,21 +74,6 @@ export class DOMBuilder implements ContentHandler {
7874
this.grammar = this.grammarHandler.getGrammar();
7975
}
8076

81-
private validateRootElement(elementName: string): void {
82-
if (!this.rootElementValidated && this.validating && this.grammar) {
83-
const context = new ValidationContext([], new Map(), '', undefined, true);
84-
const result = this.grammar.validateElement(elementName, context);
85-
86-
if (!result.isValid) {
87-
result.errors.forEach(error => {
88-
console.error(`Root element validation error: ${error.message}`);
89-
});
90-
}
91-
92-
this.rootElementValidated = true;
93-
}
94-
}
95-
9677
private addDefaultAttributes(elementName: string, element: XMLElement): void {
9778
if (!this.grammar) {
9879
return;
@@ -117,29 +98,7 @@ export class DOMBuilder implements ContentHandler {
11798
});
11899
}
119100

120-
private validateRequiredAttributes(elementName: string, attributes: XMLAttribute[]): void {
121-
if (!this.validating || !this.grammar) {
122-
return;
123-
}
124-
const attDecls = this.grammar.getElementAttributes(elementName);
125-
if (!attDecls || attDecls.size === 0) {
126-
if (attributes.length > 0) {
127-
const attNames = attributes.map(att => att.getName()).join(', ');
128-
throw new Error(`Element '${elementName}' has no declared attributes but contains: [${attNames}]`);
129-
}
130-
return;
131-
}
132-
133-
const providedAttNames = new Set(attributes.map(att => att.getName()));
134-
135-
attDecls.forEach((attInfo, attName) => {
136-
if (attInfo.use === AttributeUse.REQUIRED && !providedAttNames.has(attName)) {
137-
throw new Error(`Required attribute '${attName}' is missing from element '${elementName}'`);
138-
}
139-
});
140-
}
141-
142-
private validateAttributeValues(elementName: string, attributes: XMLAttribute[]): void {
101+
private trackIdAttributes(elementName: string, element: XMLElement): void {
143102
if (!this.validating || !this.grammar) {
144103
return;
145104
}
@@ -149,32 +108,35 @@ export class DOMBuilder implements ContentHandler {
149108
return;
150109
}
151110

152-
// TODO: Additional DTD datatype validation (NMTOKEN, ENTITY, NOTATION) is now in DTDComposite
153-
154-
for (const attribute of attributes) {
155-
const attName = attribute.getName();
156-
const attValue = attribute.getValue();
111+
element.getAttributes().forEach(attr => {
112+
const attName = attr.getName();
113+
const attValue = attr.getValue();
157114
const attInfo = attDecls.get(attName);
158115

159-
if (attInfo) {
160-
// ID uniqueness and IDREF collection for validation
116+
if (attInfo && attInfo.datatype) {
117+
// ID uniqueness validation
161118
if (attInfo.datatype === 'ID') {
162119
if (this.declaredIds.has(attValue)) {
163120
throw new Error(`Duplicate ID value '${attValue}' found in element '${elementName}'`);
164121
}
165122
this.declaredIds.add(attValue);
166123
}
167124

125+
// Collect IDREF values for later validation (only if not already declared)
168126
if (attInfo.datatype === 'IDREF') {
169-
this.pendingIdrefs.push(attValue);
127+
if (!this.declaredIds.has(attValue)) {
128+
this.pendingIdrefs.push(attValue);
129+
}
170130
} else if (attInfo.datatype === 'IDREFS') {
171131
const idrefs = attValue.split(/\s+/);
172-
this.pendingIdrefs.push(...idrefs);
132+
idrefs.forEach(idref => {
133+
if (!this.declaredIds.has(idref)) {
134+
this.pendingIdrefs.push(idref);
135+
}
136+
});
173137
}
174-
} else {
175-
throw new Error(`Undeclared attribute '${attName}' found in element '${elementName}'`);
176138
}
177-
}
139+
});
178140
}
179141

180142
private validateIdReferences(): void {
@@ -189,46 +151,6 @@ export class DOMBuilder implements ContentHandler {
189151
}
190152
}
191153

192-
private validateElementContent(elementName: string, children: XMLNode[]): void {
193-
if (!this.validating || !this.grammar) {
194-
return;
195-
}
196-
197-
// Extract child element names
198-
const childElementNames = children
199-
.filter(child => child.getNodeType() === Constants.ELEMENT_NODE)
200-
.map(child => (child as XMLElement).getName());
201-
202-
// Extract text content
203-
const textNodes = children
204-
.filter(child => child.getNodeType() === Constants.TEXT_NODE)
205-
.map(child => (child as TextNode).getValue());
206-
const textContent = textNodes.join('');
207-
208-
// Create attributes map
209-
const currentElement = this.stack[this.stack.length - 1];
210-
const attributesArray = currentElement?.getAttributes() || [];
211-
const attributesMap = new Map<string, string>();
212-
attributesArray.forEach(attr => {
213-
attributesMap.set(attr.getName(), attr.getValue());
214-
});
215-
216-
// Create validation context
217-
const context = new ValidationContext(
218-
childElementNames,
219-
attributesMap,
220-
textContent,
221-
this.stack.length > 1 ? this.stack[this.stack.length - 2].getName() : undefined
222-
);
223-
224-
// Validate using Grammar interface
225-
const result = this.grammar.validateElement(elementName, context);
226-
if (!result.isValid) {
227-
const errorMessages = result.errors.map(err => err.message).join('; ');
228-
throw new Error(`Element '${elementName}' validation failed: ${errorMessages}`);
229-
}
230-
}
231-
232154
setDTDParser(dtdParser: DTDParser): void {
233155
this.dtdParser = dtdParser;
234156
}
@@ -250,10 +172,6 @@ export class DOMBuilder implements ContentHandler {
250172
}
251173

252174
startElement(name: string, atts: XMLAttribute[]): void {
253-
if (this.stack.length === 0) {
254-
this.validateRootElement(name);
255-
}
256-
257175
let element: XMLElement = new XMLElement(name);
258176
atts.forEach((att) => {
259177
element.setAttribute(att);
@@ -264,35 +182,8 @@ export class DOMBuilder implements ContentHandler {
264182
this.addDefaultAttributes(name, element);
265183
}
266184

267-
// Validate attributes when validating is enabled
268-
if (this.validating) {
269-
this.validateRequiredAttributes(name, element.getAttributes());
270-
}
271-
272-
// Delegate attribute validation to grammar handler (includes well-formedness and DTD validation)
273-
if (this.validating && this.grammarHandler && this.grammar) {
274-
const attributesMap = new Map<string, string>();
275-
element.getAttributes().forEach(attr => {
276-
attributesMap.set(attr.getName(), attr.getValue());
277-
});
278-
279-
const context = new ValidationContext(
280-
[], // childrenNames - empty for attribute validation
281-
attributesMap,
282-
'', // textContent - empty for attribute validation
283-
this.stack.length > 1 ? this.stack[this.stack.length - 2].getName() : undefined,
284-
true // attributeOnly flag
285-
);
286-
287-
const validationResult = this.grammar.validateAttributes(name, attributesMap, context);
288-
289-
if (!validationResult.isValid) {
290-
const errorMessage = validationResult.errors.length > 0 ? validationResult.errors[0].message : 'Attribute validation failed';
291-
throw new Error(errorMessage);
292-
}
293-
}
294-
295-
this.validateAttributeValues(name, element.getAttributes());
185+
// Track ID/IDREF attributes for validation
186+
this.trackIdAttributes(name, element);
296187

297188
if (this.stack.length === 0) {
298189
this.document?.setRoot(element);
@@ -303,13 +194,6 @@ export class DOMBuilder implements ContentHandler {
303194
}
304195

305196
endElement(name: string): void {
306-
const element = this.stack[this.stack.length - 1];
307-
308-
if (element && this.validating) {
309-
const children = element.getChildren ? element.getChildren() : [];
310-
this.validateElementContent(name, children);
311-
}
312-
313197
this.stack.pop();
314198
}
315199

0 commit comments

Comments
 (0)