From 90f741d067a219c0b33fe86a9c55712c28f0f5a4 Mon Sep 17 00:00:00 2001 From: "Rodolfo M. Raya" Date: Tue, 12 Dec 2023 07:50:20 -0300 Subject: [PATCH 01/18] Added BOM when writing UTF-16LE --- package.json | 4 ++-- ts/XMLWriter.ts | 13 ++++++++++--- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index cc3c6e8..675938a 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,7 @@ "url": "https://github.com/rmraya/TypesXML.git" }, "devDependencies": { - "@types/node": "^20.10.0", - "typescript": "^5.3.2" + "@types/node": "^20.10.4", + "typescript": "^5.3.3" } } \ No newline at end of file diff --git a/ts/XMLWriter.ts b/ts/XMLWriter.ts index 4f79d76..2c6dc76 100644 --- a/ts/XMLWriter.ts +++ b/ts/XMLWriter.ts @@ -10,7 +10,7 @@ * Maxprograms - initial API and implementation *******************************************************************************/ -import { writeFileSync } from "fs"; +import { appendFileSync, writeFileSync } from "fs"; import { XMLDeclaration } from "./XMLDeclaration"; import { XMLDocument } from "./XMLDocument"; @@ -21,8 +21,15 @@ export class XMLWriter { encoding: 'utf8' }; let decl: XMLDeclaration = doc.getXmlDeclaration(); - if (decl) { - options.encoding = decl.getEncoding(); + if (decl && decl.getEncoding() === 'UTF-16LE') { + options.encoding = 'utf16le'; + } + if (options.encoding === 'utf16le') { + // write BOM for UTF-16LE + const UTF16: Buffer = Buffer.from([-2, -1]); + writeFileSync(file, UTF16, options); + appendFileSync(file, doc.toString(), options); + return; } writeFileSync(file, doc.toString(), options); } From 057b660412c35c637bcdc86892661dc6807777ab Mon Sep 17 00:00:00 2001 From: "Rodolfo M. Raya" Date: Thu, 14 Dec 2023 19:24:36 -0300 Subject: [PATCH 02/18] Added options for writing SAX events --- ts/XMLElement.ts | 13 +++++++++++++ ts/XMLWriter.ts | 40 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+) diff --git a/ts/XMLElement.ts b/ts/XMLElement.ts index 8931b95..0078a1a 100644 --- a/ts/XMLElement.ts +++ b/ts/XMLElement.ts @@ -102,6 +102,19 @@ export class XMLElement implements XMLNode { return Constants.ELEMENT_NODE; } + getHead(): string { + let result: string = '<' + this.name; + this.attributes.forEach((value: XMLAttribute) => { + result += ' ' + value.toString(); + }); + result += (this.content.length > 0) ? '>' : '/>'; + return result; + } + + getTail(): string { + return (this.content.length > 0) ? '' : ''; + } + toString(): string { let result: string = '<' + this.name; this.attributes.forEach((value: XMLAttribute) => { diff --git a/ts/XMLWriter.ts b/ts/XMLWriter.ts index 2c6dc76..c374194 100644 --- a/ts/XMLWriter.ts +++ b/ts/XMLWriter.ts @@ -13,9 +13,49 @@ import { appendFileSync, writeFileSync } from "fs"; import { XMLDeclaration } from "./XMLDeclaration"; import { XMLDocument } from "./XMLDocument"; +import { XMLNode } from "./XMLNode"; export class XMLWriter { + file: string; + options: any = { + encoding: 'utf8' + } + started: boolean; + + constructor(file: string) { + this.file = file; + this.started = false; + } + + writeNode(node: XMLNode): void { + if (node instanceof XMLDeclaration) { + let enc: string = (node as XMLDeclaration).getEncoding(); + if (enc === 'UTF-16LE') { + // write BOM for UTF-16LE + this.options.encoding = 'utf16le'; + const UTF16: Buffer = Buffer.from([-2, -1]); + writeFileSync(this.file, UTF16, this.options); + this.started = true; + } + } + if (!this.started) { + this.started = true; + writeFileSync(this.file, node.toString(), this.options); + return; + } + appendFileSync(this.file, node.toString(), this.options); + } + + writeString(str: string): void { + if (!this.started) { + this.started = true; + writeFileSync(this.file, str, this.options); + return; + } + appendFileSync(this.file, str, this.options); + } + static writeDocument(doc: XMLDocument, file: string): void { let options: any = { encoding: 'utf8' From b1ad33a5180a575dca0ddab3b233c1943e501d52 Mon Sep 17 00:00:00 2001 From: "Rodolfo M. Raya" Date: Thu, 14 Dec 2023 19:41:02 -0300 Subject: [PATCH 03/18] Fixed getHead() and getTail() --- ts/XMLElement.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ts/XMLElement.ts b/ts/XMLElement.ts index 0078a1a..24e24ac 100644 --- a/ts/XMLElement.ts +++ b/ts/XMLElement.ts @@ -107,12 +107,12 @@ export class XMLElement implements XMLNode { this.attributes.forEach((value: XMLAttribute) => { result += ' ' + value.toString(); }); - result += (this.content.length > 0) ? '>' : '/>'; + result += '>'; return result; } getTail(): string { - return (this.content.length > 0) ? '' : ''; + return ''; } toString(): string { From 72da73be32f2892a095aa1177b574172f4733bc3 Mon Sep 17 00:00:00 2001 From: "Rodolfo M. Raya" Date: Sat, 16 Dec 2023 07:57:46 -0300 Subject: [PATCH 04/18] Added code --- ts/Constants.ts | 7 + ts/SAXParser.ts | 172 ++++++++----- ts/XMLWriter.ts | 8 +- ts/dtd/AttDecl.ts | 42 ++++ ts/dtd/AttlistDecl.ts | 82 ++++++ ts/dtd/DTDParser.ts | 494 +++++++++++++++++++++++++++++++++++++ ts/dtd/ElementDecl.ts | 46 ++++ ts/dtd/EntityDecl.ts | 56 +++++ ts/dtd/InternalSubset.ts | 169 +++++++++++++ ts/dtd/NotationDecl.ts | 46 ++++ ts/grammar/ContentModel.ts | 24 ++ ts/grammar/Grammar.ts | 58 +++++ 12 files changed, 1136 insertions(+), 68 deletions(-) create mode 100644 ts/dtd/AttDecl.ts create mode 100644 ts/dtd/AttlistDecl.ts create mode 100644 ts/dtd/DTDParser.ts create mode 100644 ts/dtd/ElementDecl.ts create mode 100644 ts/dtd/EntityDecl.ts create mode 100644 ts/dtd/InternalSubset.ts create mode 100644 ts/dtd/NotationDecl.ts create mode 100644 ts/grammar/ContentModel.ts create mode 100644 ts/grammar/Grammar.ts diff --git a/ts/Constants.ts b/ts/Constants.ts index b5700e9..dc0ffc3 100644 --- a/ts/Constants.ts +++ b/ts/Constants.ts @@ -23,4 +23,11 @@ export class Constants { static readonly XML_DECLARATION_NODE: number = 8; static readonly ATTRIBUTE_LIST_DECL_NODE: number = 9; static readonly DOCUMENT_TYPE_NODE: number = 10; + + // constants for DTD parser + + static readonly ATTRIBUTE_DECL_NODE: number = 11; + static readonly ELEMENT_DECL_NODE: number = 12; + static readonly INTERNAL_SUBSET_NODE: number = 13; + static readonly NOTATION_DECL_NODE: number = 14; } \ No newline at end of file diff --git a/ts/SAXParser.ts b/ts/SAXParser.ts index 703891c..0541913 100644 --- a/ts/SAXParser.ts +++ b/ts/SAXParser.ts @@ -65,7 +65,7 @@ export class SAXParser { this.contentHandler.startDocument(); while (this.pointer < this.buffer.length) { if (this.lookingAt('')) { this.endCDATA(); continue; @@ -274,147 +278,187 @@ export class SAXParser { } parseDoctype() { - let declaration: string = ''; + this.cleanCharacterRun(); this.pointer += 9; // skip '= this.buffer.length && this.reader.dataAvailable()) { + if (this.pointer + 1 > this.buffer.length && this.reader.dataAvailable()) { this.buffer += this.reader.read(); } } - this.pointer += i; - i = 0; // read name let name: string = ''; - for (; i < this.buffer.length; i++) { - let char: string = this.buffer.charAt(this.pointer + i); + for (; this.pointer < this.buffer.length; this.pointer++) { + let char: string = this.buffer.charAt(this.pointer); if (XMLUtils.isXmlSpace(char)) { break; } name += char; - if (this.pointer + i + 1 >= this.buffer.length && this.reader.dataAvailable()) { + if (this.pointer + 1 >= this.buffer.length && this.reader.dataAvailable()) { this.buffer += this.reader.read(); } } - this.pointer += i; - i = 0; // skip spaces after root name - for (; i < this.buffer.length; i++) { - let char: string = this.buffer.charAt(this.pointer + i); + for (; this.pointer < this.buffer.length; this.pointer++) { + let char = this.buffer[this.pointer]; if (!XMLUtils.isXmlSpace(char)) { break; } - if (this.pointer + i + 1 >= this.buffer.length && this.reader.dataAvailable()) { + if (this.pointer + 1 > this.buffer.length && this.reader.dataAvailable()) { this.buffer += this.reader.read(); } } - this.pointer += i; - // read the rest of the declaration - let stack: number = 1; + // read external identifiers + let systemId: string = ''; + if (this.lookingAt('SYSTEM')) { + this.parseSystemDeclaration(); + } + let publicId: string = ''; + if (this.lookingAt('PUBLIC')) { + let pair: string[] = this.parsePublicDeclaration(); + publicId = pair[0]; + systemId = pair[1]; + } + this.contentHandler.startDTD(name, publicId, systemId); + // skip spaces after SYSTEM or PUBLIC for (; this.pointer < this.buffer.length; this.pointer++) { - let char: string = this.buffer[this.pointer]; - if ('<' === char) { - stack++; + let char = this.buffer[this.pointer]; + if (!XMLUtils.isXmlSpace(char)) { + break; } - if ('>' === char) { - stack--; - if (stack === 0) { + if (this.pointer + 1 > this.buffer.length && this.reader.dataAvailable()) { + this.buffer += this.reader.read(); + } + } + // check internal subset + let internalSubset: string = ''; + if (this.lookingAt('[')) { + this.pointer++; // skip '[' + for (; this.pointer < this.buffer.length; this.pointer++) { + let char: string = this.buffer[this.pointer]; + if (']' === char) { break; } + internalSubset += char; + if (this.pointer + 1 > this.buffer.length && this.reader.dataAvailable()) { + this.buffer += this.reader.read(); + } + } + } + // skip spaces after internal subset + for (; this.pointer < this.buffer.length; this.pointer++) { + let char = this.buffer[this.pointer]; + if (!XMLUtils.isXmlSpace(char)) { + break; } - declaration += char; if (this.pointer + 1 > this.buffer.length && this.reader.dataAvailable()) { this.buffer += this.reader.read(); } } this.buffer = this.buffer.substring(this.pointer + 1); // skip '>' this.pointer = 0; - let systemId: string = this.extractSystem(declaration); - let publicId: string = this.extractPublic(declaration); - let internalSubset: string = this.extractInternal(declaration); - this.contentHandler.startDTD(name, publicId, systemId); if (internalSubset !== '') { this.contentHandler.internalSubset(internalSubset); } this.contentHandler.endDTD(); } - extractInternal(declaration: string): string { - let index = declaration.indexOf('['); - if (index === -1) { - return ''; - } - let end = declaration.indexOf(']'); - if (end === -1) { - return ''; - } - return declaration.substring(index + 1, end); - } - - extractPublic(declaration: string): string { - let index = declaration.indexOf('PUBLIC'); - if (index === -1) { - return ''; - } + parsePublicDeclaration(): string[] { + this.pointer += 6; // skip 'PUBLIC' // skip spaces after PUBLIC - let i: number = 6; - for (; i < declaration.length; i++) { - let char = declaration[i]; + for (; this.pointer < this.buffer.length; this.pointer++) { + let char = this.buffer[this.pointer]; if (!XMLUtils.isXmlSpace(char)) { break; } + if (this.pointer + 1 > this.buffer.length && this.reader.dataAvailable()) { + this.buffer += this.reader.read(); + } } let separator: string = ''; let publicId: string = ''; - for (; i < declaration.length; i++) { - let char = declaration[i]; + for (; this.pointer < this.buffer.length; this.pointer++) { + let char = this.buffer[this.pointer]; if (separator === '' && ('\'' === char || '"' === char)) { separator = char; continue; } if (char === separator) { + this.pointer++; // skip separator break; } publicId += char; + if (this.pointer + 1 > this.buffer.length && this.reader.dataAvailable()) { + this.buffer += this.reader.read(); + } + } + // skip spaces after publicId + for (; this.pointer < this.buffer.length; this.pointer++) { + let char = this.buffer[this.pointer]; + if (!XMLUtils.isXmlSpace(char)) { + break; + } + if (this.pointer + 1 > this.buffer.length && this.reader.dataAvailable()) { + this.buffer += this.reader.read(); + } } - return publicId; + separator = ''; + let systemIdId: string = ''; + for (; this.pointer < this.buffer.length; this.pointer++) { + let char = this.buffer[this.pointer]; + if (separator === '' && ('\'' === char || '"' === char)) { + separator = char; + continue; + } + if (char === separator) { + this.pointer++; // skip separator + break; + } + systemIdId += char; + if (this.pointer + 1 > this.buffer.length && this.reader.dataAvailable()) { + this.buffer += this.reader.read(); + } + } + return [publicId, systemIdId]; } - extractSystem(declaration: string): string { - let index: number = declaration.indexOf('SYSTEM'); - if (index === -1) { - return ''; - } + parseSystemDeclaration(): string { + this.pointer += 6; // skip 'SYSTEM' // skip spaces after SYSTEM - let i: number = 6; - for (; i < declaration.length; i++) { - let char = declaration[i]; + for (; this.pointer < this.buffer.length; this.pointer++) { + let char = this.buffer[this.pointer]; if (!XMLUtils.isXmlSpace(char)) { break; } + if (this.pointer + 1 > this.buffer.length && this.reader.dataAvailable()) { + this.buffer += this.reader.read(); + } } let separator: string = ''; let systemId: string = ''; - for (; i < declaration.length; i++) { - let char = declaration[i]; + for (; this.pointer < this.buffer.length; this.pointer++) { + let char = this.buffer[this.pointer]; if (separator === '' && ('\'' === char || '"' === char)) { separator = char; continue; } if (char === separator) { + this.pointer++; // skip separator break; } systemId += char; + if (this.pointer + 1 > this.buffer.length && this.reader.dataAvailable()) { + this.buffer += this.reader.read(); + } } return systemId; } - parseXMLDecl() { + parseXMLDeclaration() { let declarationText: string = ''; this.pointer += 6; // skip '')) { diff --git a/ts/XMLWriter.ts b/ts/XMLWriter.ts index c374194..6a5bd13 100644 --- a/ts/XMLWriter.ts +++ b/ts/XMLWriter.ts @@ -17,6 +17,8 @@ import { XMLNode } from "./XMLNode"; export class XMLWriter { + static UTF16: Buffer = Buffer.from([-2, -1]); + file: string; options: any = { encoding: 'utf8' @@ -34,8 +36,7 @@ export class XMLWriter { if (enc === 'UTF-16LE') { // write BOM for UTF-16LE this.options.encoding = 'utf16le'; - const UTF16: Buffer = Buffer.from([-2, -1]); - writeFileSync(this.file, UTF16, this.options); + writeFileSync(this.file, XMLWriter.UTF16, this.options); this.started = true; } } @@ -66,8 +67,7 @@ export class XMLWriter { } if (options.encoding === 'utf16le') { // write BOM for UTF-16LE - const UTF16: Buffer = Buffer.from([-2, -1]); - writeFileSync(file, UTF16, options); + writeFileSync(file, XMLWriter.UTF16, options); appendFileSync(file, doc.toString(), options); return; } diff --git a/ts/dtd/AttDecl.ts b/ts/dtd/AttDecl.ts new file mode 100644 index 0000000..494cea8 --- /dev/null +++ b/ts/dtd/AttDecl.ts @@ -0,0 +1,42 @@ +/******************************************************************************* + * Copyright (c) 2023 Maxprograms. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse License 1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/org/documents/epl-v10.html + * + * Contributors: + * Maxprograms - initial API and implementation + *******************************************************************************/ + +import { Constants } from "../Constants"; +import { XMLNode } from "../XMLNode"; + +export class AttDecl implements XMLNode { + + private name: string; + private type: string; + private defaultType: string; + private defaultValue: string; + + constructor() { + // TODO + } + + getNodeType(): number { + return Constants.ATTRIBUTE_DECL_NODE; + } + + equals(node: XMLNode): boolean { + if (node instanceof AttDecl) { + return this.name === node.name && this.type === node.type && this.defaultType === node.defaultType && this.defaultValue === node.defaultValue; + } + return false; + } + + toString(): string { + // TODO + return this.name; + } +} \ No newline at end of file diff --git a/ts/dtd/AttlistDecl.ts b/ts/dtd/AttlistDecl.ts new file mode 100644 index 0000000..24c97a9 --- /dev/null +++ b/ts/dtd/AttlistDecl.ts @@ -0,0 +1,82 @@ +/******************************************************************************* + * Copyright (c) 2023 Maxprograms. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse License 1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/org/documents/epl-v10.html + * + * Contributors: + * Maxprograms - initial API and implementation + *******************************************************************************/ + +import { Constants } from "../Constants"; +import { XMLNode } from "../XMLNode"; +import { AttDecl } from "./AttDecl"; + +export class AttlistDecl implements XMLNode { + + private name: string; + private atttibutesText: string; + private attributes: Map; + + private attTypes: string[] = ['CDATA', 'ID', 'IDREF', 'IDREFS', 'ENTITY', 'ENTITIES', 'NMTOKEN', 'NMTOKENS']; + + constructor(name: string, attributesText: string) { + this.name = name; + this.atttibutesText = attributesText; + this.attributes = new Map(); + this.parseAttributes(attributesText); + } + + getListName(): string { + return this.name; + } + + getAttributes(): Map { + return this.attributes; + } + + parseAttributes(text: string) { + let parts: string[] = text.split(/[ \t\r\n]/); // (#x20 | #x9 | #xD | #xA) + let index: number = 0; + while (index < parts.length) { + let name: string = parts[index++]; + let type: string = parts[index++]; + let defaultType: string = parts[index++]; + let defaultValue: string = ''; + if ('#FIXED' === defaultType) { + defaultValue = parts[index++]; + } + } + + } + + getNodeType(): number { + return Constants.ATTRIBUTE_LIST_DECL_NODE; + } + + toString(): string { + let result: string = ' { + result += ' ' + a.toString() + '\n'; + }); + return result + '>'; + } + + equals(node: XMLNode): boolean { + if (node instanceof AttlistDecl) { + let nodeAtts: Map = node.getAttributes(); + if (this.name !== node.getListName() || this.attributes.size !== nodeAtts.size) { + return false; + } + this.attributes.forEach((value: AttDecl, key: string) => { + if (!value.equals(nodeAtts.get(key))) { + return false; + } + }); + return true; + } + return false; + } +} \ No newline at end of file diff --git a/ts/dtd/DTDParser.ts b/ts/dtd/DTDParser.ts new file mode 100644 index 0000000..04f79a5 --- /dev/null +++ b/ts/dtd/DTDParser.ts @@ -0,0 +1,494 @@ +/******************************************************************************* + * Copyright (c) 2023 Maxprograms. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse License 1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/org/documents/epl-v10.html + * + * Contributors: + * Maxprograms - initial API and implementation + *******************************************************************************/ + +import { Stats, closeSync, openSync, readSync, statSync } from "fs"; +import { XMLUtils } from "../XMLUtils"; +import { Grammar } from "../grammar/Grammar"; +import { AttlistDecl } from "./AttlistDecl"; +import { ElementDecl } from "./ElementDecl"; +import { EntityDecl } from "./EntityDecl"; +import { NotationDecl } from "./NotationDecl"; + +export class DTDParser { + + private grammar: Grammar; + private elementDeclMap: Map; + private attributeListMap: Map; + private entitiesMap: Map; + private notationsMap: Map; + + constructor() { + this.elementDeclMap = new Map(); + this.attributeListMap = new Map(); + this.entitiesMap = new Map(); + this.notationsMap = new Map(); + } + + parseFile(file: string): Grammar { + let source: string = ''; + let stats: Stats = statSync(file, { bigint: false, throwIfNoEntry: true }); + let blockSize: number = stats.blksize; + let fileHandle = openSync(file, 'r'); + let buffer = Buffer.alloc(blockSize); + let bytesRead: number = readSync(fileHandle, buffer, 0, blockSize, 0); + while (bytesRead > 0) { + source += buffer.toString('utf8', 0, bytesRead); + bytesRead = readSync(fileHandle, buffer, 0, blockSize, source.length); + } + closeSync(fileHandle); + return this.parseString(source); + } + + parseString(source: string): Grammar { + let pointer: number = 0; + this.grammar = new Grammar(); + + while (pointer < source.length) { + if (XMLUtils.lookingAt('', pointer); + if (index === -1) { + throw new Error('Malformed element declaration'); + } + let elementText: string = source.substring(pointer, index + '>'.length); + let elementDecl: ElementDecl = DTDParser.parseElementDeclaration(elementText); + this.elementDeclMap.set(elementDecl.getName(), elementDecl); + pointer += elementText.length; + continue; + } + if (XMLUtils.lookingAt('', pointer); + if (index === -1) { + throw new Error('Malformed attribute declaration'); + } + let attListText: string = source.substring(pointer, index + '>'.length); + let attList: AttlistDecl = DTDParser.parseAttributesListDeclaration(attListText); + this.attributeListMap.set(attList.getListName(), attList); + pointer += attListText.length; + continue; + } + if (XMLUtils.lookingAt('', pointer); + if (index === -1) { + throw new Error('Malformed entity declaration'); + } + let entityDeclText: string = source.substring(pointer, index + '>'.length); + let entityDecl: EntityDecl = DTDParser.parseEntityDeclaration(entityDeclText); + this.entitiesMap.set(entityDecl.getName(), entityDecl); + pointer += entityDeclText.length; + continue; + } + if (XMLUtils.lookingAt('', pointer); + if (index === -1) { + throw new Error('Malformed notation declaration'); + } + let notationDeclText: string = source.substring(pointer, index + '>'.length); + let notation: NotationDecl = DTDParser.parseNotationDeclaration(notationDeclText); + this.notationsMap.set(notation.getName(), notation); + pointer += notationDeclText.length; + continue; + } + if (XMLUtils.lookingAt('', pointer); + if (index === -1) { + throw new Error('Malformed processing instruction'); + } + let piText: string = source.substring(pointer, index + '?>'.length); + // ignore processing instructions + pointer += piText.length; + continue; + } + if (XMLUtils.lookingAt('', pointer); + if (index === -1) { + throw new Error('Malformed comment'); + } + let commentText: string = source.substring(pointer, index + '-->'.length); + // ignore comments + pointer += commentText.length; + continue; + } + if (XMLUtils.lookingAt('%', source, pointer)) { + let index: number = source.indexOf(';', pointer); + if (index == -1) { + throw new Error('Malformed entity reference'); + } + let entityName: string = source.substring(pointer + '%'.length, index); + pointer += '%'.length + entityName.length + ';'.length; + } + let char: string = source.charAt(pointer); + if (XMLUtils.isXmlSpace(char)) { + pointer++; + continue; + } + throw new Error('Error parsing DTD'); + } + this.grammar.setElementsMap(this.elementDeclMap); + this.grammar.setAttributesMap(this.attributeListMap); + this.grammar.setEntitiesMap(this.entitiesMap); + this.grammar.setNotationsMap(this.notationsMap); + return this.grammar; + } + + static parseEntityDeclaration(declaration: string): EntityDecl { + let name: string = ''; + let i: number = '') { + break; + } + externalId += char; + } + let publicId: string = ''; + let systemId: string = ''; + if (XMLUtils.lookingAt('PUBLIC', externalId, 0)) { + let index: number = externalId.indexOf('"', 'PUBLIC'.length); + if (index === -1) { + throw new Error('Malformed notation declaration'); + } + publicId = externalId.substring('PUBLIC'.length, index); + index = externalId.indexOf('"', index + '"'.length); + if (index === -1) { + throw new Error('Malformed notation declaration'); + } + systemId = externalId.substring(index + '"'.length); + } else if (XMLUtils.lookingAt('SYSTEM', externalId, 0)) { + let index: number = externalId.indexOf('"', 'SYSTEM'.length); + if (index === -1) { + throw new Error('Malformed notation declaration'); + } + systemId = externalId.substring('SYSTEM'.length, index); + } else { + throw new Error('Malformed notation declaration'); + } + return new NotationDecl(name, publicId, systemId); + } + + static parseAttributesListDeclaration(declaration: string): AttlistDecl { + let name: string = ''; + let i: number = '') { + break; + } + contentSpec += char; + } + return new ElementDecl(name, contentSpec); + } +} \ No newline at end of file diff --git a/ts/dtd/ElementDecl.ts b/ts/dtd/ElementDecl.ts new file mode 100644 index 0000000..14e6379 --- /dev/null +++ b/ts/dtd/ElementDecl.ts @@ -0,0 +1,46 @@ +/******************************************************************************* + * Copyright (c) 2023 Maxprograms. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse License 1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/org/documents/epl-v10.html + * + * Contributors: + * Maxprograms - initial API and implementation + *******************************************************************************/ + +import { Constants } from "../Constants"; +import { XMLNode } from "../XMLNode"; + +export class ElementDecl implements XMLNode { + + private name: string; + contentSpec: string; + + constructor(name: string, contentSpec: string) { + this.name = name; + this.contentSpec = contentSpec; + } + + getName(): string { + return this.name; + } + + getContentSpec(): string { + return this.contentSpec; + } + + getNodeType(): number { + return Constants.ELEMENT_DECL_NODE; + } + + toString(): string { + return ''; + } + + equals(node: XMLNode): boolean { + // TODO + throw new Error("Method not implemented."); + } +} \ No newline at end of file diff --git a/ts/dtd/EntityDecl.ts b/ts/dtd/EntityDecl.ts new file mode 100644 index 0000000..923a380 --- /dev/null +++ b/ts/dtd/EntityDecl.ts @@ -0,0 +1,56 @@ +/******************************************************************************* + * Copyright (c) 2023 Maxprograms. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse License 1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/org/documents/epl-v10.html + * + * Contributors: + * Maxprograms - initial API and implementation + *******************************************************************************/ + +import { Constants } from "../Constants"; +import { XMLNode } from "../XMLNode"; +import { XMLUtils } from "../XMLUtils"; + +export class EntityDecl implements XMLNode { + + private name: string; + private parameterEntity: boolean; + private value: string; systemId: string; + private publicId: string; + private ndata: string; + + constructor(name: string, parameterEntity: boolean, value: string, systemId: string, publicId: string, ndata: string) { + // parameterEntities are only used in DTDs + this.name = name; + this.parameterEntity = parameterEntity; + this.value = value; + this.systemId = systemId; + this.publicId = publicId; + this.ndata = ndata; + } + + getName(): string { + return this.name; + } + + getValue(): string { + return this.value; + } + + getNodeType(): number { + return Constants.ENTITY_DECL_NODE; + } + + toString(): string { + // TODO support SYTEM and PUBLIC + return '' + } + + equals(node: XMLNode): boolean { + // TODO + return false; + } +} \ No newline at end of file diff --git a/ts/dtd/InternalSubset.ts b/ts/dtd/InternalSubset.ts new file mode 100644 index 0000000..819dc83 --- /dev/null +++ b/ts/dtd/InternalSubset.ts @@ -0,0 +1,169 @@ +/******************************************************************************* + * Copyright (c) 2023 Maxprograms. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse License 1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/org/documents/epl-v10.html + * + * Contributors: + * Maxprograms - initial API and implementation + *******************************************************************************/ + +import { XMLComment } from "../XMLComment"; +import { ProcessingInstruction } from "../ProcessingInstruction"; +import { TextNode } from "../TextNode"; +import { XMLNode } from "../XMLNode"; +import { XMLUtils } from "../XMLUtils"; +import { AttlistDecl } from "./AttlistDecl"; +import { ElementDecl } from "./ElementDecl"; +import { EntityDecl } from "./EntityDecl"; +import { NotationDecl } from "./NotationDecl"; +import { Constants } from "../Constants"; +import { DTDParser } from "./DTDParser"; + +export class InternalSubset implements XMLNode { + + content: Array; + + constructor(declaration: string) { + this.content = new Array(); + this.parseDeclaration(declaration.substring(1, declaration.length - 1)); + } + + parseDeclaration(declaration: string) { + let pointer: number = 0; + let inSubset: boolean = true; + while (inSubset) { + if (XMLUtils.lookingAt('', pointer); + if (index === -1) { + throw new Error('Malformed element declaration'); + } + let elementText: string = declaration.substring(pointer, index + '>'.length); + this.content.push(DTDParser.parseElementDeclaration(elementText)); + pointer += elementText.length; + continue; + } + if (XMLUtils.lookingAt('', pointer); + if (index === -1) { + throw new Error('Malformed attribute declaration'); + } + let attListText: string = declaration.substring(pointer, index + '>'.length); + this.content.push(DTDParser.parseAttributesListDeclaration(attListText)); + pointer += attListText.length; + continue; + } + if (XMLUtils.lookingAt('', pointer); + if (index === -1) { + throw new Error('Malformed entity declaration'); + } + let entityDeclText: string = declaration.substring(pointer, index + '>'.length); + this.content.push(DTDParser.parseEntityDeclaration(entityDeclText)); + pointer += entityDeclText.length; + continue; + } + if (XMLUtils.lookingAt('', pointer); + if (index === -1) { + throw new Error('Malformed notation declaration'); + } + let notationDeclText: string = declaration.substring(pointer, index + '>'.length); + this.content.push(DTDParser.parseNotationDeclaration(notationDeclText)); + pointer += notationDeclText.length; + continue; + } + if (XMLUtils.lookingAt('', pointer); + if (index === -1) { + throw new Error('Malformed processing instruction in internal subset'); + } + let piText: string = declaration.substring(pointer, index + '?>'.length); + + this.content.push(this.parseProcessingInstruction(piText)); + pointer += piText.length; + continue; + } + if (XMLUtils.lookingAt('', pointer); + if (index === -1) { + throw new Error('Malformed comment in internal subset'); + } + let commentText: string = declaration.substring(pointer, index + '-->'.length); + this.content.push(new XMLComment(commentText)); + pointer += commentText.length; + continue; + } + if (XMLUtils.lookingAt('%', declaration, pointer)) { + // Parameter-entity references + // TODO + } + let char: string = declaration.charAt(pointer); + if (XMLUtils.isXmlSpace(char)) { + if (this.content.length > 0 && this.content[this.content.length - 1].getNodeType() === Constants.TEXT_NODE) { + let lastNode: TextNode = this.content[this.content.length - 1] as TextNode; + lastNode.setValue(lastNode.getValue() + char); + } else { + this.content.push(new TextNode(char)); + } + pointer++; + continue; + } + inSubset = false; + } + } + + parseProcessingInstruction(instructionText: string): ProcessingInstruction { + // TODO check if this is correct + instructionText = instructionText.trim(); + let target: string = ''; + let data: string = ''; + + instructionText = instructionText.trim(); + let i: number = 0; + // read target + for (; i < instructionText.length; i++) { + let char: string = instructionText[i]; + if (XMLUtils.isXmlSpace(char)) { + break; + } + target += char; + } + // skip spaces + for (; i < instructionText.length; i++) { + let char: string = instructionText[i]; + if (!XMLUtils.isXmlSpace(char)) { + break; + } + } + // set data + data = instructionText.substring(i); + return new ProcessingInstruction(target, data); + } + + getNodeType(): number { + return Constants.INTERNAL_SUBSET_NODE; + } + + toString(): string { + let result: string = '['; + this.content.forEach((value: XMLNode) => { + result += value.toString(); + }); + return result + ']'; + } + + equals(node: XMLNode): boolean { + if (node instanceof InternalSubset) { + for (let i: number = 0; i < this.content.length; i++) { + if (!this.content[i].equals(node.content[i])) { + return false; + } + } + return true; + } + return false; + } +} \ No newline at end of file diff --git a/ts/dtd/NotationDecl.ts b/ts/dtd/NotationDecl.ts new file mode 100644 index 0000000..cbf32b4 --- /dev/null +++ b/ts/dtd/NotationDecl.ts @@ -0,0 +1,46 @@ +/******************************************************************************* + * Copyright (c) 2023 Maxprograms. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse License 1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/org/documents/epl-v10.html + * + * Contributors: + * Maxprograms - initial API and implementation + *******************************************************************************/ + +import { Constants } from "../Constants"; +import { XMLNode } from "../XMLNode"; + +export class NotationDecl implements XMLNode { + + private name: string; + private publicId: string; + private systemId: string; + + constructor(name: string, publicId: string, systemId: string) { + this.name = name; + this.publicId = publicId; + this.systemId = systemId; + } + + getName():string { + return this.name; + } + + getNodeType(): number { + return Constants.NOTATION_DECL_NODE; + } + + toString(): string { + // TODO + throw new Error("Method not implemented."); + } + + equals(node: XMLNode): boolean { + // TODO + throw new Error("Method not implemented."); + } + +} \ No newline at end of file diff --git a/ts/grammar/ContentModel.ts b/ts/grammar/ContentModel.ts new file mode 100644 index 0000000..752432d --- /dev/null +++ b/ts/grammar/ContentModel.ts @@ -0,0 +1,24 @@ +/******************************************************************************* + * Copyright (c) 2023 Maxprograms. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse License 1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/org/documents/epl-v10.html + * + * Contributors: + * Maxprograms - initial API and implementation + *******************************************************************************/ + +export class ContentModel { + + private name: string; + + constructor(name: string) { + this.name = name; + } + + toString(): string { + return this.name; + } +} \ No newline at end of file diff --git a/ts/grammar/Grammar.ts b/ts/grammar/Grammar.ts new file mode 100644 index 0000000..3c6fc7a --- /dev/null +++ b/ts/grammar/Grammar.ts @@ -0,0 +1,58 @@ +/******************************************************************************* + * Copyright (c) 2023 Maxprograms. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse License 1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/org/documents/epl-v10.html + * + * Contributors: + * Maxprograms - initial API and implementation + *******************************************************************************/ + +import { AttlistDecl } from "../dtd/AttlistDecl"; +import { ElementDecl } from "../dtd/ElementDecl"; +import { EntityDecl } from "../dtd/EntityDecl"; +import { NotationDecl } from "../dtd/NotationDecl"; +import { ContentModel } from "./ContentModel"; + +export class Grammar { + + private models: Map; + entitiesMap: Map; + attributeListMap: Map; + elementDeclMap: Map; + notationsMap: Map; + + constructor() { + this.models = new Map(); + } + + getContentModel(elementName: string): ContentModel { + return this.models.get(elementName); + } + + toString(): string { + let result: string; + this.models.forEach((value: ContentModel) => { + result = result + value.toString() + '\n'; + }); + return result; + } + + setNotationsMap(notationsMap: Map) { + this.notationsMap = notationsMap; + } + + setEntitiesMap(entitiesMap: Map) { + this.entitiesMap = entitiesMap; + } + + setAttributesMap(attributeListMap: Map) { + this.attributeListMap = attributeListMap; + } + + setElementsMap(elementDeclMap: Map) { + this.elementDeclMap = elementDeclMap; + } +} \ No newline at end of file From 6607a9a7c2fd7efd5b41b723d4c5a12233dcea90 Mon Sep 17 00:00:00 2001 From: "Rodolfo M. Raya" Date: Sat, 16 Dec 2023 09:06:25 -0300 Subject: [PATCH 05/18] Ignored conditional sections --- ts/SAXParser.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/ts/SAXParser.ts b/ts/SAXParser.ts index 0541913..bfc32a7 100644 --- a/ts/SAXParser.ts +++ b/ts/SAXParser.ts @@ -88,10 +88,6 @@ export class SAXParser { this.startCDATA(); continue; } - if (this.lookingAt('')) { this.endCDATA(); continue; From 4af3256f5a031d0c7d05fb03355d1a6918cea5f6 Mon Sep 17 00:00:00 2001 From: "Rodolfo M. Raya" Date: Sat, 16 Dec 2023 18:46:27 -0300 Subject: [PATCH 06/18] Implemented DTD parsing --- ts/Catalog.ts | 10 +- ts/dtd/AttlistDecl.ts | 4 +- ts/dtd/DTDParser.ts | 328 +++++++++++++++++++++++++++++++-------- ts/dtd/EntityDecl.ts | 21 ++- ts/dtd/InternalSubset.ts | 146 ++--------------- ts/grammar/Grammar.ts | 65 ++++++-- 6 files changed, 349 insertions(+), 225 deletions(-) diff --git a/ts/Catalog.ts b/ts/Catalog.ts index 4cffbf0..916fad3 100644 --- a/ts/Catalog.ts +++ b/ts/Catalog.ts @@ -84,7 +84,7 @@ export class Catalog { let uri: string = this.makeAbsolute(child.getAttribute("uri").getValue()); if (existsSync(uri)) { this.publicCatalog.set(publicId, uri); - if (uri.endsWith(".dtd")) { + if (uri.endsWith(".dtd") || uri.endsWith(".ent") || uri.endsWith(".mod")) { let name: string = path.basename(uri); if (!this.dtdCatalog.has(name)) { this.dtdCatalog.set(name, uri); @@ -135,25 +135,25 @@ export class Catalog { let nextCatalog: string = this.makeAbsolute(child.getAttribute("catalog").getValue()); let catalog: Catalog = new Catalog(nextCatalog); let map: Map = catalog.getSystemCatalog(); - map.forEach((key, value) => { + map.forEach((value, key) => { if (!this.systemCatalog.has(key)) { this.systemCatalog.set(key, value); } }); map = catalog.getPublicCatalog(); - map.forEach((key, value) => { + map.forEach((value, key) => { if (!this.publicCatalog.has(key)) { this.publicCatalog.set(key, value); } }); map = catalog.getUriCatalog(); - map.forEach((key, value) => { + map.forEach((value, key) => { if (!this.uriCatalog.has(key)) { this.uriCatalog.set(key, value); } }); map = catalog.getDtdCatalog(); - map.forEach((key, value) => { + map.forEach((value, key) => { if (!this.dtdCatalog.has(key)) { this.dtdCatalog.set(key, value); } diff --git a/ts/dtd/AttlistDecl.ts b/ts/dtd/AttlistDecl.ts index 24c97a9..3a0fed6 100644 --- a/ts/dtd/AttlistDecl.ts +++ b/ts/dtd/AttlistDecl.ts @@ -29,7 +29,7 @@ export class AttlistDecl implements XMLNode { this.parseAttributes(attributesText); } - getListName(): string { + getName(): string { return this.name; } @@ -67,7 +67,7 @@ export class AttlistDecl implements XMLNode { equals(node: XMLNode): boolean { if (node instanceof AttlistDecl) { let nodeAtts: Map = node.getAttributes(); - if (this.name !== node.getListName() || this.attributes.size !== nodeAtts.size) { + if (this.name !== node.getName() || this.attributes.size !== nodeAtts.size) { return false; } this.attributes.forEach((value: AttDecl, key: string) => { diff --git a/ts/dtd/DTDParser.ts b/ts/dtd/DTDParser.ts index 04f79a5..9a4e25a 100644 --- a/ts/dtd/DTDParser.ts +++ b/ts/dtd/DTDParser.ts @@ -17,129 +17,252 @@ import { AttlistDecl } from "./AttlistDecl"; import { ElementDecl } from "./ElementDecl"; import { EntityDecl } from "./EntityDecl"; import { NotationDecl } from "./NotationDecl"; +import { Catalog } from "../Catalog"; +import path = require("path"); export class DTDParser { private grammar: Grammar; - private elementDeclMap: Map; - private attributeListMap: Map; - private entitiesMap: Map; - private notationsMap: Map; + private catalog: Catalog; + private pointer: number = 0; + private source: string; + private currentFile: string; constructor() { - this.elementDeclMap = new Map(); - this.attributeListMap = new Map(); - this.entitiesMap = new Map(); - this.notationsMap = new Map(); + this.currentFile = ''; + } + + setCatalog(catalog: Catalog) { + this.catalog = catalog; } parseFile(file: string): Grammar { - let source: string = ''; + this.source = ''; let stats: Stats = statSync(file, { bigint: false, throwIfNoEntry: true }); + this.currentFile = file; let blockSize: number = stats.blksize; let fileHandle = openSync(file, 'r'); let buffer = Buffer.alloc(blockSize); let bytesRead: number = readSync(fileHandle, buffer, 0, blockSize, 0); while (bytesRead > 0) { - source += buffer.toString('utf8', 0, bytesRead); - bytesRead = readSync(fileHandle, buffer, 0, blockSize, source.length); + this.source += buffer.toString('utf8', 0, bytesRead); + bytesRead = readSync(fileHandle, buffer, 0, blockSize, this.source.length); } closeSync(fileHandle); - return this.parseString(source); + return this.parse(); } parseString(source: string): Grammar { - let pointer: number = 0; + this.source = source; + return this.parse(); + } + + parse(): Grammar { + this.pointer = 0; this.grammar = new Grammar(); - while (pointer < source.length) { - if (XMLUtils.lookingAt('', pointer); + while (this.pointer < this.source.length) { + if (this.lookingAt('', this.pointer); if (index === -1) { throw new Error('Malformed element declaration'); } - let elementText: string = source.substring(pointer, index + '>'.length); - let elementDecl: ElementDecl = DTDParser.parseElementDeclaration(elementText); - this.elementDeclMap.set(elementDecl.getName(), elementDecl); - pointer += elementText.length; + let elementText: string = this.source.substring(this.pointer, index + '>'.length); + let length = elementText.length; + if (this.hasParameterEntity(elementText)) { + elementText = this.resolveEntities(elementText); + } + let elementDecl: ElementDecl = this.parseElementDeclaration(elementText); + this.grammar.addElement(elementDecl); + this.pointer += length; continue; } - if (XMLUtils.lookingAt('', pointer); + if (this.lookingAt('', this.pointer); if (index === -1) { throw new Error('Malformed attribute declaration'); } - let attListText: string = source.substring(pointer, index + '>'.length); - let attList: AttlistDecl = DTDParser.parseAttributesListDeclaration(attListText); - this.attributeListMap.set(attList.getListName(), attList); - pointer += attListText.length; + let attListText: string = this.source.substring(this.pointer, index + '>'.length); + let length = attListText.length; + if (this.hasParameterEntity(attListText)) { + attListText = this.resolveEntities(attListText); + } + let attList: AttlistDecl = this.parseAttributesListDeclaration(attListText); + this.grammar.addAttributesList(attList); + this.pointer += length; continue; } - if (XMLUtils.lookingAt('', pointer); + if (this.lookingAt('', this.pointer); if (index === -1) { throw new Error('Malformed entity declaration'); } - let entityDeclText: string = source.substring(pointer, index + '>'.length); - let entityDecl: EntityDecl = DTDParser.parseEntityDeclaration(entityDeclText); - this.entitiesMap.set(entityDecl.getName(), entityDecl); - pointer += entityDeclText.length; + let entityDeclText: string = this.source.substring(this.pointer, index + '>'.length); + let entityDecl: EntityDecl = this.parseEntityDeclaration(entityDeclText); + this.grammar.addEntity(entityDecl); + this.pointer += entityDeclText.length; continue; } - if (XMLUtils.lookingAt('', pointer); + if (this.lookingAt('', this.pointer); if (index === -1) { throw new Error('Malformed notation declaration'); } - let notationDeclText: string = source.substring(pointer, index + '>'.length); - let notation: NotationDecl = DTDParser.parseNotationDeclaration(notationDeclText); - this.notationsMap.set(notation.getName(), notation); - pointer += notationDeclText.length; + let notationDeclText: string = this.source.substring(this.pointer, index + '>'.length); + if (this.hasParameterEntity(notationDeclText)) { + notationDeclText = this.resolveEntities(notationDeclText); + } + let notation: NotationDecl = this.parseNotationDeclaration(notationDeclText); + this.grammar.addNotation(notation); + this.pointer += notationDeclText.length; continue; } - if (XMLUtils.lookingAt('', pointer); + if (this.lookingAt('')) { + this.endConditionalSection(); + continue; + } + if (this.lookingAt('', this.pointer); if (index === -1) { throw new Error('Malformed processing instruction'); } - let piText: string = source.substring(pointer, index + '?>'.length); - // ignore processing instructions - pointer += piText.length; + // skip processing instructions + this.pointer = index + '?>'.length; continue; } - if (XMLUtils.lookingAt('', pointer); + if (this.lookingAt('', this.pointer); if (index === -1) { throw new Error('Malformed comment'); } - let commentText: string = source.substring(pointer, index + '-->'.length); - // ignore comments - pointer += commentText.length; + // skip comments + let comment = this.source.substring(this.pointer, index + '-->'.length); + this.pointer = index + '-->'.length; continue; } - if (XMLUtils.lookingAt('%', source, pointer)) { - let index: number = source.indexOf(';', pointer); + if (this.lookingAt('%')) { + let index: number = this.source.indexOf(';', this.pointer); if (index == -1) { throw new Error('Malformed entity reference'); } - let entityName: string = source.substring(pointer + '%'.length, index); - pointer += '%'.length + entityName.length + ';'.length; + let entityName: string = this.source.substring(this.pointer + '%'.length, index); + let entity: EntityDecl = this.grammar.getEntity(entityName); + if (entity === undefined) { + throw new Error('Unknown entity: ' + entityName); + } + let value: string = entity.getValue(); + if (value !== '') { + let start: string = this.source.substring(0, this.pointer); + let end: string = this.source.substring(index + ';'.length); + this.source = start + value + end; + this.pointer += value.length; + } else if (entity.getSystemId() !== '' || entity.getPublicId() !== '') { + let location = this.resolveEntity(entity.getPublicId(), entity.getSystemId()); + let parser: DTDParser = new DTDParser(); + parser.setCatalog(this.catalog); + let externalGrammar: Grammar = parser.parseFile(location); + this.grammar.merge(externalGrammar); + this.pointer = index + ';'.length; + } else { + + } + continue; } - let char: string = source.charAt(pointer); + let char: string = this.source.charAt(this.pointer); if (XMLUtils.isXmlSpace(char)) { - pointer++; + this.pointer++; continue; } - throw new Error('Error parsing DTD'); + throw new Error('Error parsing ' + this.currentFile + ' at ' + this.source.substring(this.pointer - 10, this.pointer) + ' @ ' + this.source.substring(this.pointer, this.pointer + 30)); } - this.grammar.setElementsMap(this.elementDeclMap); - this.grammar.setAttributesMap(this.attributeListMap); - this.grammar.setEntitiesMap(this.entitiesMap); - this.grammar.setNotationsMap(this.notationsMap); return this.grammar; } - static parseEntityDeclaration(declaration: string): EntityDecl { + endConditionalSection() { + // jump over ]]> + this.pointer += ']]>'.length; + } + + parseConditionalSection() { + this.pointer += '')) { + stack--; + this.pointer += ']]>'.length; + if (stack === 0) { + return; + } + } else { + this.pointer++; + } + } + } + + hasParameterEntity(fragment: string) { + return fragment.indexOf('%') !== -1 && fragment.indexOf(';') !== -1; + } + + resolveEntities(fragment: string): string { + while (this.hasParameterEntity(fragment)) { + let start = fragment.indexOf('%'); + let end = fragment.indexOf(';'); + let entityName = fragment.substring(start + '%'.length, end); + let entity: EntityDecl = this.grammar.getEntity(entityName); + if (entity === undefined) { + throw new Error('Unknown entity: ' + entityName); + } + fragment = fragment.replace('%' + entityName + ';', entity.getValue()); + } + return fragment; + } + + parseEntityDeclaration(declaration: string): EntityDecl { let name: string = ''; let i: number = ' this.source.length) { + return false; + } + for (let i = 0; i < length; i++) { + if (this.source[this.pointer + i] !== text[i]) { + return false; + } + } + return true; + } + + resolveEntity(publicId: string, systemId: string): string { + let location: string = this.catalog.resolveEntity(publicId, systemId); + if (!location && systemId !== '') { + location = this.makeAbsolute(systemId); + } + if (location) { + return location; + } + throw new Error('Entity not found: ' + publicId + ' ' + systemId); + } + + makeAbsolute(uri: string): string { + let currentPath: string = path.dirname(this.currentFile); + return currentPath + path.sep + uri; + } + + getGrammar(): Grammar { + return this.grammar; + } } \ No newline at end of file diff --git a/ts/dtd/EntityDecl.ts b/ts/dtd/EntityDecl.ts index 923a380..1f424b7 100644 --- a/ts/dtd/EntityDecl.ts +++ b/ts/dtd/EntityDecl.ts @@ -40,17 +40,32 @@ export class EntityDecl implements XMLNode { return this.value; } + getSystemId(): string { + return this.systemId; + } + + getPublicId(): string { + return this.publicId; + } + getNodeType(): number { return Constants.ENTITY_DECL_NODE; } toString(): string { - // TODO support SYTEM and PUBLIC - return '' + let result = ''; + } else if (this.systemId !== '') { + result += ' SYSTEM "' + this.systemId + '">'; + } else { + result += ' "' + this.value + '">'; + } + return result; } equals(node: XMLNode): boolean { - // TODO + // TODO return false; } } \ No newline at end of file diff --git a/ts/dtd/InternalSubset.ts b/ts/dtd/InternalSubset.ts index 819dc83..005a83b 100644 --- a/ts/dtd/InternalSubset.ts +++ b/ts/dtd/InternalSubset.ts @@ -10,137 +10,20 @@ * Maxprograms - initial API and implementation *******************************************************************************/ -import { XMLComment } from "../XMLComment"; -import { ProcessingInstruction } from "../ProcessingInstruction"; -import { TextNode } from "../TextNode"; -import { XMLNode } from "../XMLNode"; -import { XMLUtils } from "../XMLUtils"; -import { AttlistDecl } from "./AttlistDecl"; -import { ElementDecl } from "./ElementDecl"; -import { EntityDecl } from "./EntityDecl"; -import { NotationDecl } from "./NotationDecl"; +import { Grammar } from "../grammar/Grammar"; import { Constants } from "../Constants"; +import { XMLNode } from "../XMLNode"; import { DTDParser } from "./DTDParser"; export class InternalSubset implements XMLNode { - content: Array; + declarationText: string; + grammar: Grammar; constructor(declaration: string) { - this.content = new Array(); - this.parseDeclaration(declaration.substring(1, declaration.length - 1)); - } - - parseDeclaration(declaration: string) { - let pointer: number = 0; - let inSubset: boolean = true; - while (inSubset) { - if (XMLUtils.lookingAt('', pointer); - if (index === -1) { - throw new Error('Malformed element declaration'); - } - let elementText: string = declaration.substring(pointer, index + '>'.length); - this.content.push(DTDParser.parseElementDeclaration(elementText)); - pointer += elementText.length; - continue; - } - if (XMLUtils.lookingAt('', pointer); - if (index === -1) { - throw new Error('Malformed attribute declaration'); - } - let attListText: string = declaration.substring(pointer, index + '>'.length); - this.content.push(DTDParser.parseAttributesListDeclaration(attListText)); - pointer += attListText.length; - continue; - } - if (XMLUtils.lookingAt('', pointer); - if (index === -1) { - throw new Error('Malformed entity declaration'); - } - let entityDeclText: string = declaration.substring(pointer, index + '>'.length); - this.content.push(DTDParser.parseEntityDeclaration(entityDeclText)); - pointer += entityDeclText.length; - continue; - } - if (XMLUtils.lookingAt('', pointer); - if (index === -1) { - throw new Error('Malformed notation declaration'); - } - let notationDeclText: string = declaration.substring(pointer, index + '>'.length); - this.content.push(DTDParser.parseNotationDeclaration(notationDeclText)); - pointer += notationDeclText.length; - continue; - } - if (XMLUtils.lookingAt('', pointer); - if (index === -1) { - throw new Error('Malformed processing instruction in internal subset'); - } - let piText: string = declaration.substring(pointer, index + '?>'.length); - - this.content.push(this.parseProcessingInstruction(piText)); - pointer += piText.length; - continue; - } - if (XMLUtils.lookingAt('', pointer); - if (index === -1) { - throw new Error('Malformed comment in internal subset'); - } - let commentText: string = declaration.substring(pointer, index + '-->'.length); - this.content.push(new XMLComment(commentText)); - pointer += commentText.length; - continue; - } - if (XMLUtils.lookingAt('%', declaration, pointer)) { - // Parameter-entity references - // TODO - } - let char: string = declaration.charAt(pointer); - if (XMLUtils.isXmlSpace(char)) { - if (this.content.length > 0 && this.content[this.content.length - 1].getNodeType() === Constants.TEXT_NODE) { - let lastNode: TextNode = this.content[this.content.length - 1] as TextNode; - lastNode.setValue(lastNode.getValue() + char); - } else { - this.content.push(new TextNode(char)); - } - pointer++; - continue; - } - inSubset = false; - } - } - - parseProcessingInstruction(instructionText: string): ProcessingInstruction { - // TODO check if this is correct - instructionText = instructionText.trim(); - let target: string = ''; - let data: string = ''; - - instructionText = instructionText.trim(); - let i: number = 0; - // read target - for (; i < instructionText.length; i++) { - let char: string = instructionText[i]; - if (XMLUtils.isXmlSpace(char)) { - break; - } - target += char; - } - // skip spaces - for (; i < instructionText.length; i++) { - let char: string = instructionText[i]; - if (!XMLUtils.isXmlSpace(char)) { - break; - } - } - // set data - data = instructionText.substring(i); - return new ProcessingInstruction(target, data); + this.declarationText = declaration; + let parser:DTDParser = new DTDParser(); + this.grammar = parser.parseString(declaration.substring(1, declaration.length - 1)); } getNodeType(): number { @@ -148,22 +31,11 @@ export class InternalSubset implements XMLNode { } toString(): string { - let result: string = '['; - this.content.forEach((value: XMLNode) => { - result += value.toString(); - }); - return result + ']'; + return this.declarationText; } equals(node: XMLNode): boolean { - if (node instanceof InternalSubset) { - for (let i: number = 0; i < this.content.length; i++) { - if (!this.content[i].equals(node.content[i])) { - return false; - } - } - return true; - } + // TODO Auto-generated method stub return false; } } \ No newline at end of file diff --git a/ts/grammar/Grammar.ts b/ts/grammar/Grammar.ts index 3c6fc7a..0f850a4 100644 --- a/ts/grammar/Grammar.ts +++ b/ts/grammar/Grammar.ts @@ -17,15 +17,19 @@ import { NotationDecl } from "../dtd/NotationDecl"; import { ContentModel } from "./ContentModel"; export class Grammar { - private models: Map; - entitiesMap: Map; - attributeListMap: Map; - elementDeclMap: Map; - notationsMap: Map; + + private entitiesMap: Map; + private attributeListMap: Map; + private elementDeclMap: Map; + private notationsMap: Map; constructor() { this.models = new Map(); + this.elementDeclMap = new Map(); + this.attributeListMap = new Map(); + this.entitiesMap = new Map(); + this.notationsMap = new Map(); } getContentModel(elementName: string): ContentModel { @@ -40,19 +44,54 @@ export class Grammar { return result; } - setNotationsMap(notationsMap: Map) { - this.notationsMap = notationsMap; + addElement(elementDecl: ElementDecl) { + if (!this.elementDeclMap.has(elementDecl.getName())) { + this.elementDeclMap.set(elementDecl.getName(), elementDecl); + } + } + + addAttributesList(attList: AttlistDecl) { + if (!this.attributeListMap.has(attList.getName())) { + this.attributeListMap.set(attList.getName(), attList); + } + } + + addEntity(entityDecl: EntityDecl) { + if (!this.entitiesMap.has(entityDecl.getName())) { + this.entitiesMap.set(entityDecl.getName(), entityDecl); + } + } + + getEntity(entityName: string): EntityDecl { + return this.entitiesMap.get(entityName); + } + + addNotation(notation: NotationDecl) { + if (!this.notationsMap.has(notation.getName())) { + this.notationsMap.set(notation.getName(), notation); + } + } + + merge(grammar: Grammar): void { + this.entitiesMap = new Map([...this.entitiesMap, ...grammar.getEntitiesMap()]); + this.attributeListMap = new Map([...this.attributeListMap, ...grammar.getAttributeListMap()]); + this.elementDeclMap = new Map([...this.elementDeclMap, ...grammar.getElementDeclMap()]); + this.notationsMap = new Map([...this.notationsMap, ...grammar.getNotationsMap()]); + } + + getNotationsMap(): Map { + return this.notationsMap; } - setEntitiesMap(entitiesMap: Map) { - this.entitiesMap = entitiesMap; + getElementDeclMap(): Map { + return this.elementDeclMap; } - setAttributesMap(attributeListMap: Map) { - this.attributeListMap = attributeListMap; + getAttributeListMap(): Map { + return this.attributeListMap; } - setElementsMap(elementDeclMap: Map) { - this.elementDeclMap = elementDeclMap; + getEntitiesMap(): Map { + return this.entitiesMap } } \ No newline at end of file From e4ec813849b350fa312ad66af7672bc5f0db0030 Mon Sep 17 00:00:00 2001 From: "Rodolfo M. Raya" Date: Sat, 16 Dec 2023 18:59:50 -0300 Subject: [PATCH 07/18] Code cleanup with SonarQube --- ts/XMLWriter.ts | 2 +- ts/dtd/DTDParser.ts | 5 +---- ts/dtd/EntityDecl.ts | 1 - 3 files changed, 2 insertions(+), 6 deletions(-) diff --git a/ts/XMLWriter.ts b/ts/XMLWriter.ts index 6a5bd13..eb28c28 100644 --- a/ts/XMLWriter.ts +++ b/ts/XMLWriter.ts @@ -32,7 +32,7 @@ export class XMLWriter { writeNode(node: XMLNode): void { if (node instanceof XMLDeclaration) { - let enc: string = (node as XMLDeclaration).getEncoding(); + let enc: string = node.getEncoding(); if (enc === 'UTF-16LE') { // write BOM for UTF-16LE this.options.encoding = 'utf16le'; diff --git a/ts/dtd/DTDParser.ts b/ts/dtd/DTDParser.ts index 9a4e25a..8af23f6 100644 --- a/ts/dtd/DTDParser.ts +++ b/ts/dtd/DTDParser.ts @@ -140,7 +140,6 @@ export class DTDParser { throw new Error('Malformed comment'); } // skip comments - let comment = this.source.substring(this.pointer, index + '-->'.length); this.pointer = index + '-->'.length; continue; } @@ -168,7 +167,7 @@ export class DTDParser { this.grammar.merge(externalGrammar); this.pointer = index + ';'.length; } else { - + throw new Error('Parameter entity without value or external subset: ' + entityName); } continue; } @@ -208,7 +207,6 @@ export class DTDParser { if (this.hasParameterEntity(keyword)) { keyword = this.resolveEntities(keyword); } - if ('INCLUDE' === keyword) { // jump to the start of the content for (; this.pointer < this.source.length; this.pointer++) { @@ -218,7 +216,6 @@ export class DTDParser { } } this.pointer++; - return; } else if ('IGNORE' === keyword) { this.skipIgnoreSection(); } else { diff --git a/ts/dtd/EntityDecl.ts b/ts/dtd/EntityDecl.ts index 1f424b7..0bcd5f8 100644 --- a/ts/dtd/EntityDecl.ts +++ b/ts/dtd/EntityDecl.ts @@ -12,7 +12,6 @@ import { Constants } from "../Constants"; import { XMLNode } from "../XMLNode"; -import { XMLUtils } from "../XMLUtils"; export class EntityDecl implements XMLNode { From 3ac8680d895e614f7a140dbb26e74639d14b75b4 Mon Sep 17 00:00:00 2001 From: "Rodolfo M. Raya" Date: Sat, 16 Dec 2023 19:02:24 -0300 Subject: [PATCH 08/18] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 2ab59b1..c75b7ca 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ Open source XML library written in TypeScript -Implements a SAX parser that exposes the these methods from the `ContentHandler` interface: +Implements a SAX parser that exposes these methods from the `ContentHandler` interface: * initialize(): void; * setCatalog(catalog: Catalog): void; @@ -26,7 +26,7 @@ Class `DOMBuilder` implements the `ContentHandler` interface and builds a DOM tr ## Features currently in development -* Parsing of the Internal Subset specified in the declaration +* Parsing of DTDs and internal subsets from ## Limitations From d538486f08832efc56d900ea47f5fe53b04ea899 Mon Sep 17 00:00:00 2001 From: "Rodolfo M. Raya" Date: Sat, 16 Dec 2023 21:33:44 -0300 Subject: [PATCH 09/18] Added code --- ts/DOMBuilder.ts | 14 +++ ts/XMLUtils.ts | 18 ++++ ts/dtd/AttlistDecl.ts | 2 +- ts/dtd/DTDParser.ts | 181 +++++++++++++++++++++++-------------- ts/grammar/ContentModel.ts | 15 ++- ts/grammar/Grammar.ts | 52 +++++++++++ 6 files changed, 213 insertions(+), 69 deletions(-) diff --git a/ts/DOMBuilder.ts b/ts/DOMBuilder.ts index 9b9357c..0b9e09b 100644 --- a/ts/DOMBuilder.ts +++ b/ts/DOMBuilder.ts @@ -10,6 +10,8 @@ import { CData } from "./CData"; import { XMLDocumentType } from "./XMLDocumentType"; import { Catalog } from "./Catalog"; import { XMLUtils } from "./XMLUtils"; +import { DTDParser } from "./dtd/DTDParser"; +import { Grammar } from "./grammar/Grammar"; export class DOMBuilder implements ContentHandler { @@ -18,7 +20,9 @@ export class DOMBuilder implements ContentHandler { document: XMLDocument; stack: Array; catalog: Catalog; + dtdParser: DTDParser; grammarUrl: string; + grammar: Grammar; initialize(): void { this.document = new XMLDocument(); @@ -30,6 +34,10 @@ export class DOMBuilder implements ContentHandler { this.catalog = catalog; } + setDTDParser(dtdParser: DTDParser): void { + this.dtdParser = dtdParser; + } + getDocument(): XMLDocument { return this.document; } @@ -175,6 +183,12 @@ export class DOMBuilder implements ContentHandler { this.document.setDocumentType(docType); if (this.catalog) { this.grammarUrl = this.catalog.resolveEntity(publicId, systemId); + if (this.dtdParser && this.grammarUrl) { + let dtdGrammar :Grammar = this.dtdParser.parseDTD(this.grammarUrl); + if (dtdGrammar) { + this.grammar = dtdGrammar; + } + } } } diff --git a/ts/XMLUtils.ts b/ts/XMLUtils.ts index 14828e4..2a895fc 100644 --- a/ts/XMLUtils.ts +++ b/ts/XMLUtils.ts @@ -33,6 +33,24 @@ export class XMLUtils { return this.SPACES.indexOf(char) > -1; } + static hasParameterEntity(text: string) { + let index: number = text.indexOf('%'); + if (index === -1) { + return false; + } + let length: number = text.length; + for (let i = index + 1; i < length; i++) { + let c: string = text.charAt(i); + if (this.isXmlSpace(c) ) { + return false; + } + if (c === ';') { + return true; + } + } + return false; + } + static normalizeSpaces(text: string): string { return text.replace(/\s+/g, ' '); } diff --git a/ts/dtd/AttlistDecl.ts b/ts/dtd/AttlistDecl.ts index 3a0fed6..2b78d62 100644 --- a/ts/dtd/AttlistDecl.ts +++ b/ts/dtd/AttlistDecl.ts @@ -38,6 +38,7 @@ export class AttlistDecl implements XMLNode { } parseAttributes(text: string) { + // TODO parse attributes let parts: string[] = text.split(/[ \t\r\n]/); // (#x20 | #x9 | #xD | #xA) let index: number = 0; while (index < parts.length) { @@ -49,7 +50,6 @@ export class AttlistDecl implements XMLNode { defaultValue = parts[index++]; } } - } getNodeType(): number { diff --git a/ts/dtd/DTDParser.ts b/ts/dtd/DTDParser.ts index 8af23f6..f30c286 100644 --- a/ts/dtd/DTDParser.ts +++ b/ts/dtd/DTDParser.ts @@ -36,6 +36,12 @@ export class DTDParser { this.catalog = catalog; } + parseDTD(file: string) : Grammar{ + this.parseFile(file); + this.grammar.processModels(); + return this.grammar; + } + parseFile(file: string): Grammar { this.source = ''; let stats: Stats = statSync(file, { bigint: false, throwIfNoEntry: true }); @@ -54,7 +60,9 @@ export class DTDParser { parseString(source: string): Grammar { this.source = source; - return this.parse(); + this.parse(); + this.grammar.processModels(); + return this.grammar; } parse(): Grammar { @@ -69,9 +77,6 @@ export class DTDParser { } let elementText: string = this.source.substring(this.pointer, index + '>'.length); let length = elementText.length; - if (this.hasParameterEntity(elementText)) { - elementText = this.resolveEntities(elementText); - } let elementDecl: ElementDecl = this.parseElementDeclaration(elementText); this.grammar.addElement(elementDecl); this.pointer += length; @@ -84,9 +89,6 @@ export class DTDParser { } let attListText: string = this.source.substring(this.pointer, index + '>'.length); let length = attListText.length; - if (this.hasParameterEntity(attListText)) { - attListText = this.resolveEntities(attListText); - } let attList: AttlistDecl = this.parseAttributesListDeclaration(attListText); this.grammar.addAttributesList(attList); this.pointer += length; @@ -109,7 +111,7 @@ export class DTDParser { throw new Error('Malformed notation declaration'); } let notationDeclText: string = this.source.substring(this.pointer, index + '>'.length); - if (this.hasParameterEntity(notationDeclText)) { + if (XMLUtils.hasParameterEntity(notationDeclText)) { notationDeclText = this.resolveEntities(notationDeclText); } let notation: NotationDecl = this.parseNotationDeclaration(notationDeclText); @@ -204,7 +206,7 @@ export class DTDParser { } keyword += char; } - if (this.hasParameterEntity(keyword)) { + if (XMLUtils.hasParameterEntity(keyword)) { keyword = this.resolveEntities(keyword); } if ('INCLUDE' === keyword) { @@ -241,12 +243,8 @@ export class DTDParser { } } - hasParameterEntity(fragment: string) { - return fragment.indexOf('%') !== -1 && fragment.indexOf(';') !== -1; - } - resolveEntities(fragment: string): string { - while (this.hasParameterEntity(fragment)) { + while (XMLUtils.hasParameterEntity(fragment)) { let start = fragment.indexOf('%'); let end = fragment.indexOf(';'); let entityName = fragment.substring(start + '%'.length, end); @@ -290,7 +288,7 @@ export class DTDParser { } name += char; } - if (this.hasParameterEntity(name)) { + if (XMLUtils.hasParameterEntity(name)) { name = this.resolveEntities(name); } // skip spaces before entity value or external id @@ -311,18 +309,19 @@ export class DTDParser { break; } } + let separator: string = declaration.charAt(i); i++; // skip opening " // get public id let publicId: string = ''; for (; i < declaration.length; i++) { char = declaration.charAt(i); - if (char === '"') { + if (char === separator) { break; } publicId += char; } i++; // skip closing " - if (this.hasParameterEntity(publicId)) { + if (XMLUtils.hasParameterEntity(publicId)) { publicId = this.resolveEntities(publicId); } // skip spaces before system id @@ -332,17 +331,18 @@ export class DTDParser { break; } } + separator = declaration.charAt(i); i++; // skip opening " // get system id let systemId: string = ''; for (; i < declaration.length; i++) { char = declaration.charAt(i); - if (char === '"') { + if (char === separator) { break; } systemId += char; } - if (this.hasParameterEntity(systemId)) { + if (XMLUtils.hasParameterEntity(systemId)) { systemId = this.resolveEntities(systemId); } return new EntityDecl(name, parameterEntity, '', systemId, publicId, ''); @@ -355,34 +355,33 @@ export class DTDParser { break; } } + let separator: string = declaration.charAt(i); i++; // skip opening " // get system id let systemId: string = ''; for (; i < declaration.length; i++) { char = declaration.charAt(i); - if (char === '"') { + if (char === separator) { break; } systemId += char; } - if (this.hasParameterEntity(systemId)) { + if (XMLUtils.hasParameterEntity(systemId)) { systemId = this.resolveEntities(systemId); } return new EntityDecl(name, parameterEntity, '', systemId, '', ''); } else { // get entity value + let separator: string = declaration.charAt(i); i++; // skip opening " let value: string = ''; for (; i < declaration.length; i++) { char = declaration.charAt(i); - if (char === '"') { + if (char === separator) { break; } value += char; } - if (this.hasParameterEntity(value)) { - value = this.resolveEntities(value); - } return new EntityDecl(name, parameterEntity, value, '', '', ''); } } else { @@ -396,18 +395,19 @@ export class DTDParser { break; } } + let separator: string = declaration.charAt(i); i++; // skip " // get public id let publicId: string = ''; for (; i < declaration.length; i++) { char = declaration.charAt(i); - if (char === '"') { + if (char === separator) { break; } publicId += char; } i++; // skip closing " - if (this.hasParameterEntity(publicId)) { + if (XMLUtils.hasParameterEntity(publicId)) { publicId = this.resolveEntities(publicId); } // skip spaces before system id @@ -417,18 +417,19 @@ export class DTDParser { break; } } + separator = declaration.charAt(i); i++; // skip " // get system id let systemId: string = ''; for (; i < declaration.length; i++) { char = declaration.charAt(i); - if (char === '"') { + if (char === separator) { break; } systemId += char; } i++; // skip closing " - if (this.hasParameterEntity(systemId)) { + if (XMLUtils.hasParameterEntity(systemId)) { systemId = this.resolveEntities(systemId); } // skip spaces before NDATA @@ -456,7 +457,7 @@ export class DTDParser { } ndata += char; } - if (this.hasParameterEntity(ndata)) { + if (XMLUtils.hasParameterEntity(ndata)) { ndata = this.resolveEntities(ndata); } return new EntityDecl(name, parameterEntity, '', systemId, publicId, ndata); @@ -471,17 +472,18 @@ export class DTDParser { break; } } + let separator: string = declaration.charAt(i); i++; // skip " // get system id let systemId: string = ''; for (; i < declaration.length; i++) { char = declaration.charAt(i); - if (char === '"') { + if (char === separator) { break; } systemId += char; } - if (this.hasParameterEntity(systemId)) { + if (XMLUtils.hasParameterEntity(systemId)) { systemId = this.resolveEntities(systemId); } // skip spaces before NDATA @@ -509,7 +511,7 @@ export class DTDParser { } ndata += char; } - if (this.hasParameterEntity(ndata)) { + if (XMLUtils.hasParameterEntity(ndata)) { ndata = this.resolveEntities(ndata); } return new EntityDecl(name, parameterEntity, '', systemId, '', ndata); @@ -517,18 +519,16 @@ export class DTDParser { return new EntityDecl(name, parameterEntity, '', systemId, '', ''); } else { // get entity value + let separator: string = declaration.charAt(i); i++; // skip " let value: string = ''; for (; i < declaration.length; i++) { char = declaration.charAt(i); - if (char === '"') { + if (char === separator) { break; } value += char; } - if (this.hasParameterEntity(value)) { - value = this.resolveEntities(value); - } return new EntityDecl(name, parameterEntity, value, '', '', ''); } } @@ -536,7 +536,7 @@ export class DTDParser { parseNotationDeclaration(declaration: string): NotationDecl { let name: string = ''; - let i: number = '') { - break; - } - externalId += char; - } let publicId: string = ''; let systemId: string = ''; - if (XMLUtils.lookingAt('PUBLIC', externalId, 0)) { - let index: number = externalId.indexOf('"', 'PUBLIC'.length); - if (index === -1) { - throw new Error('Malformed notation declaration'); - } - publicId = externalId.substring('PUBLIC'.length, index); - index = externalId.indexOf('"', index + '"'.length); - if (index === -1) { - throw new Error('Malformed notation declaration'); - } - systemId = externalId.substring(index + '"'.length); - } else if (XMLUtils.lookingAt('SYSTEM', externalId, 0)) { - let index: number = externalId.indexOf('"', 'SYSTEM'.length); - if (index === -1) { - throw new Error('Malformed notation declaration'); - } - systemId = externalId.substring('SYSTEM'.length, index); + if (XMLUtils.lookingAt('PUBLIC', declaration, i)) { + i += 'PUBLIC'.length; + // skip spaces before public id + for (; i < declaration.length; i++) { + char = declaration.charAt(i); + if (!XMLUtils.isXmlSpace(char)) { + break; + } + } + let separator: string = declaration.charAt(i); + i++; // skip opening " + // get public id + for (; i < declaration.length; i++) { + char = declaration.charAt(i); + if (char === separator) { + break; + } + publicId += char; + } + i++; // skip closing " + if (XMLUtils.hasParameterEntity(publicId)) { + publicId = this.resolveEntities(publicId); + } + // skip spaces before system id + for (; i < declaration.length; i++) { + char = declaration.charAt(i); + if (!XMLUtils.isXmlSpace(char)) { + break; + } + } + separator = declaration.charAt(i); + i++; // skip opening " + // get system id + for (; i < declaration.length; i++) { + char = declaration.charAt(i); + if (char === separator) { + break; + } + systemId += char; + } + + } else if (XMLUtils.lookingAt('SYSTEM', declaration, i)) { + i += 'SYSTEM'.length; + // skip spaces before system id + for (; i < declaration.length; i++) { + char = declaration.charAt(i); + if (!XMLUtils.isXmlSpace(char)) { + break; + } + } + let separator: string = declaration.charAt(i); + i++; // skip opening " + // get system id + for (; i < declaration.length; i++) { + char = declaration.charAt(i); + if (char === separator) { + break; + } + systemId += char; + } } else { throw new Error('Malformed notation declaration'); } @@ -595,7 +629,6 @@ export class DTDParser { } parseAttributesListDeclaration(declaration: string): AttlistDecl { - let name: string = ''; let i: number = '') { + break; + } + atttibutesText += char; + } return new AttlistDecl(name, atttibutesText); } diff --git a/ts/grammar/ContentModel.ts b/ts/grammar/ContentModel.ts index 752432d..138b4a5 100644 --- a/ts/grammar/ContentModel.ts +++ b/ts/grammar/ContentModel.ts @@ -10,14 +10,25 @@ * Maxprograms - initial API and implementation *******************************************************************************/ -export class ContentModel { +import { AttDecl } from "../dtd/AttDecl"; +export class ContentModel { + private name: string; + private attributes: Map; - constructor(name: string) { + constructor(name: string, contentSpec: string) { this.name = name; } + addAttributes(attributes: Map) { + this.attributes = attributes; + } + + getAttributes(): Map { + return this.attributes; + } + toString(): string { return this.name; } diff --git a/ts/grammar/Grammar.ts b/ts/grammar/Grammar.ts index 0f850a4..5ce1e29 100644 --- a/ts/grammar/Grammar.ts +++ b/ts/grammar/Grammar.ts @@ -10,6 +10,7 @@ * Maxprograms - initial API and implementation *******************************************************************************/ +import { XMLUtils } from "../XMLUtils"; import { AttlistDecl } from "../dtd/AttlistDecl"; import { ElementDecl } from "../dtd/ElementDecl"; import { EntityDecl } from "../dtd/EntityDecl"; @@ -17,6 +18,7 @@ import { NotationDecl } from "../dtd/NotationDecl"; import { ContentModel } from "./ContentModel"; export class Grammar { + private models: Map; private entitiesMap: Map; @@ -30,6 +32,15 @@ export class Grammar { this.attributeListMap = new Map(); this.entitiesMap = new Map(); this.notationsMap = new Map(); + this.addPredefinedEntities(); + } + + addPredefinedEntities() { + this.addEntity(new EntityDecl('lt', false, '<', '', '', '')); + this.addEntity(new EntityDecl('gt', false, '>', '', '', '')); + this.addEntity(new EntityDecl('amp', false, '&', '', '', '')); + this.addEntity(new EntityDecl('apos', false, "'", '', '', '')); + this.addEntity(new EntityDecl('quot', false, '"', '', '', '')); } getContentModel(elementName: string): ContentModel { @@ -50,6 +61,20 @@ export class Grammar { } } + resolveParameterEntities(text: string): string { + while (XMLUtils.hasParameterEntity(text)) { + let start = text.indexOf('%'); + let end = text.indexOf(';'); + let entityName = text.substring(start + '%'.length, end); + let entity: EntityDecl = this.getEntity(entityName); + if (entity === undefined) { + throw new Error('Unknown entity: ' + entityName); + } + text = text.replace('%' + entityName + ';', entity.getValue()); + } + return text; + } + addAttributesList(attList: AttlistDecl) { if (!this.attributeListMap.has(attList.getName())) { this.attributeListMap.set(attList.getName(), attList); @@ -94,4 +119,31 @@ export class Grammar { getEntitiesMap(): Map { return this.entitiesMap } + + processModels() { + this.elementDeclMap.forEach((elementDecl: ElementDecl) => { + let name: string = elementDecl.getName(); + if (XMLUtils.hasParameterEntity(name)) { + name = this.resolveParameterEntities(name); + } + let contentSpec: string = elementDecl.getContentSpec(); + if (XMLUtils.hasParameterEntity(contentSpec)) { + contentSpec = this.resolveParameterEntities(contentSpec); + } + let model: ContentModel = new ContentModel(name, contentSpec); + this.models.set(name, model); + }); + this.attributeListMap.forEach((attList: AttlistDecl) => { + let name: string = attList.getName(); + if (XMLUtils.hasParameterEntity(name)) { + name = this.resolveParameterEntities(name); + } + let model: ContentModel = this.models.get(name); + if (model) { + model.addAttributes(attList.getAttributes()); + } + this.models.set(name, model); + }); + } + } \ No newline at end of file From 4f692708ad7a3c7ac1ff4d68bd22cbc9168e2644 Mon Sep 17 00:00:00 2001 From: "Rodolfo M. Raya" Date: Sun, 17 Dec 2023 06:58:39 -0300 Subject: [PATCH 10/18] Fixed handling of internal subset --- ts/SAXParser.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ts/SAXParser.ts b/ts/SAXParser.ts index bfc32a7..ae6a3c4 100644 --- a/ts/SAXParser.ts +++ b/ts/SAXParser.ts @@ -345,6 +345,7 @@ export class SAXParser { } } } + this.pointer++; // skip ']' // skip spaces after internal subset for (; this.pointer < this.buffer.length; this.pointer++) { let char = this.buffer[this.pointer]; @@ -355,7 +356,8 @@ export class SAXParser { this.buffer += this.reader.read(); } } - this.buffer = this.buffer.substring(this.pointer + 1); // skip '>' + this.pointer++; // skip '>' + this.buffer = this.buffer.substring(this.pointer); this.pointer = 0; if (internalSubset !== '') { this.contentHandler.internalSubset(internalSubset); From 8cc57c547739dbe5d5a42f51d1986fa37bacfdc7 Mon Sep 17 00:00:00 2001 From: "Rodolfo M. Raya" Date: Sun, 17 Dec 2023 13:16:56 -0300 Subject: [PATCH 11/18] Updated DTD parser --- ts/Catalog.ts | 2 +- ts/SAXParser.ts | 1 + ts/dtd/DTDParser.ts | 29 ++++++++++++++++++----------- ts/grammar/Grammar.ts | 24 ++++++++++++++++++++---- 4 files changed, 40 insertions(+), 16 deletions(-) diff --git a/ts/Catalog.ts b/ts/Catalog.ts index 916fad3..554e42c 100644 --- a/ts/Catalog.ts +++ b/ts/Catalog.ts @@ -109,7 +109,7 @@ export class Catalog { let uri: string = this.makeAbsolute(child.getAttribute("uri").getValue()); if (existsSync(uri)) { this.uriCatalog.set(child.getAttribute("name").getValue(), uri); - if (uri.endsWith(".dtd")) { + if (uri.endsWith(".dtd") || uri.endsWith(".ent") || uri.endsWith(".mod")) { let name: string = path.basename(uri); if (!this.dtdCatalog.has(name)) { this.dtdCatalog.set(name, uri); diff --git a/ts/SAXParser.ts b/ts/SAXParser.ts index ae6a3c4..e0686cf 100644 --- a/ts/SAXParser.ts +++ b/ts/SAXParser.ts @@ -186,6 +186,7 @@ export class SAXParser { this.rootParsed = true; } if (this.lookingAt('/>')) { + this.cleanCharacterRun(); this.contentHandler.endElement(name); this.elementStack--; this.pointer += 2; // skip '/>' diff --git a/ts/dtd/DTDParser.ts b/ts/dtd/DTDParser.ts index f30c286..6125a7c 100644 --- a/ts/dtd/DTDParser.ts +++ b/ts/dtd/DTDParser.ts @@ -11,13 +11,14 @@ *******************************************************************************/ import { Stats, closeSync, openSync, readSync, statSync } from "fs"; +import { Catalog } from "../Catalog"; import { XMLUtils } from "../XMLUtils"; import { Grammar } from "../grammar/Grammar"; import { AttlistDecl } from "./AttlistDecl"; import { ElementDecl } from "./ElementDecl"; import { EntityDecl } from "./EntityDecl"; import { NotationDecl } from "./NotationDecl"; -import { Catalog } from "../Catalog"; + import path = require("path"); export class DTDParser { @@ -28,15 +29,20 @@ export class DTDParser { private source: string; private currentFile: string; - constructor() { + constructor(grammar?: Grammar) { this.currentFile = ''; + if (grammar) { + this.grammar = grammar; + } else { + this.grammar = new Grammar(); + } } setCatalog(catalog: Catalog) { this.catalog = catalog; } - parseDTD(file: string) : Grammar{ + parseDTD(file: string): Grammar { this.parseFile(file); this.grammar.processModels(); return this.grammar; @@ -67,8 +73,6 @@ export class DTDParser { parse(): Grammar { this.pointer = 0; - this.grammar = new Grammar(); - while (this.pointer < this.source.length) { if (this.lookingAt('', this.pointer); @@ -163,13 +167,14 @@ export class DTDParser { this.pointer += value.length; } else if (entity.getSystemId() !== '' || entity.getPublicId() !== '') { let location = this.resolveEntity(entity.getPublicId(), entity.getSystemId()); - let parser: DTDParser = new DTDParser(); + let parser: DTDParser = new DTDParser(this.grammar); parser.setCatalog(this.catalog); let externalGrammar: Grammar = parser.parseFile(location); this.grammar.merge(externalGrammar); this.pointer = index + ';'.length; } else { - throw new Error('Parameter entity without value or external subset: ' + entityName); + // empty entity, ignore + this.pointer = index + ';'.length; } continue; } @@ -602,7 +607,6 @@ export class DTDParser { } systemId += char; } - } else if (XMLUtils.lookingAt('SYSTEM', declaration, i)) { i += 'SYSTEM'.length; // skip spaces before system id @@ -718,13 +722,16 @@ export class DTDParser { resolveEntity(publicId: string, systemId: string): string { let location: string = this.catalog.resolveEntity(publicId, systemId); - if (!location && systemId !== '') { + if (!location && systemId !== '' && !systemId.startsWith('http')) { location = this.makeAbsolute(systemId); } if (location) { return location; } - throw new Error('Entity not found: ' + publicId + ' ' + systemId); + if (systemId.startsWith('http')) { + return systemId; + } + throw new Error('Entity not found: "' + publicId + '" "' + systemId + '"'); } makeAbsolute(uri: string): string { @@ -734,5 +741,5 @@ export class DTDParser { getGrammar(): Grammar { return this.grammar; - } + } } \ No newline at end of file diff --git a/ts/grammar/Grammar.ts b/ts/grammar/Grammar.ts index 5ce1e29..39d01e0 100644 --- a/ts/grammar/Grammar.ts +++ b/ts/grammar/Grammar.ts @@ -98,10 +98,26 @@ export class Grammar { } merge(grammar: Grammar): void { - this.entitiesMap = new Map([...this.entitiesMap, ...grammar.getEntitiesMap()]); - this.attributeListMap = new Map([...this.attributeListMap, ...grammar.getAttributeListMap()]); - this.elementDeclMap = new Map([...this.elementDeclMap, ...grammar.getElementDeclMap()]); - this.notationsMap = new Map([...this.notationsMap, ...grammar.getNotationsMap()]); + grammar.getEntitiesMap().forEach((value: EntityDecl, key: string) => { + if (!this.entitiesMap.has(key)) { + this.entitiesMap.set(key, value); + } + }); + grammar.getAttributeListMap().forEach((value: AttlistDecl, key: string) => { + if (!this.attributeListMap.has(key)) { + this.attributeListMap.set(key, value); + } + }); + grammar.getElementDeclMap().forEach((value: ElementDecl, key: string) => { + if (!this.elementDeclMap.has(key)) { + this.elementDeclMap.set(key, value); + } + }); + grammar.getNotationsMap().forEach((value: NotationDecl, key: string) => { + if (!this.notationsMap.has(key)) { + this.notationsMap.set(key, value); + } + }); } getNotationsMap(): Map { From ab3b42df4cd709b24a5a5d7b1b579afafcce7268 Mon Sep 17 00:00:00 2001 From: "Rodolfo M. Raya" Date: Tue, 26 Dec 2023 10:22:51 -0300 Subject: [PATCH 12/18] Switched to NodeNext module type --- ts/Catalog.ts | 2 +- ts/dtd/DTDParser.ts | 3 +-- tsconfig.json | 6 +++--- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/ts/Catalog.ts b/ts/Catalog.ts index 554e42c..752604a 100644 --- a/ts/Catalog.ts +++ b/ts/Catalog.ts @@ -10,8 +10,8 @@ * Maxprograms - initial API and implementation *******************************************************************************/ -import path = require("path"); import { existsSync } from "fs"; +import * as path from "node:path"; import { ContentHandler } from "./ContentHandler"; import { DOMBuilder } from "./DOMBuilder"; import { SAXParser } from "./SAXParser"; diff --git a/ts/dtd/DTDParser.ts b/ts/dtd/DTDParser.ts index 6125a7c..666b8b5 100644 --- a/ts/dtd/DTDParser.ts +++ b/ts/dtd/DTDParser.ts @@ -11,6 +11,7 @@ *******************************************************************************/ import { Stats, closeSync, openSync, readSync, statSync } from "fs"; +import * as path from "node:path"; import { Catalog } from "../Catalog"; import { XMLUtils } from "../XMLUtils"; import { Grammar } from "../grammar/Grammar"; @@ -19,8 +20,6 @@ import { ElementDecl } from "./ElementDecl"; import { EntityDecl } from "./EntityDecl"; import { NotationDecl } from "./NotationDecl"; -import path = require("path"); - export class DTDParser { private grammar: Grammar; diff --git a/tsconfig.json b/tsconfig.json index af2b8f2..d38ff25 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,8 +1,8 @@ { "compilerOptions": { - "target": "ES6", - "moduleResolution": "node", - "module": "CommonJS", + "target": "esnext", + "module": "NodeNext", + "moduleResolution": "NodeNext", "noImplicitAny": true, "allowUnreachableCode": false, "noImplicitThis": true, From 6691b03e8a387e737f0a5d7b6951b99ca8c49d2d Mon Sep 17 00:00:00 2001 From: "Rodolfo M. Raya" Date: Tue, 9 Jan 2024 21:11:08 -0300 Subject: [PATCH 13/18] Improved parsing of attributes list --- ts/SAXParser.ts | 1 + ts/dtd/AttDecl.ts | 16 ++++++++------- ts/dtd/AttlistDecl.ts | 47 +++++++++++++++++++++++++++++++++---------- ts/dtd/DTDParser.ts | 13 ++++++------ ts/grammar/Grammar.ts | 12 +++++------ 5 files changed, 59 insertions(+), 30 deletions(-) diff --git a/ts/SAXParser.ts b/ts/SAXParser.ts index e0686cf..2b117ec 100644 --- a/ts/SAXParser.ts +++ b/ts/SAXParser.ts @@ -177,6 +177,7 @@ export class SAXParser { let attributesMap: Map = this.parseAttributes(rest); let attributes: Array = []; attributesMap.forEach((value: string, key: string) => { + // TODO https://www.w3.org/TR/REC-xml/#AVNormalize let attribute: XMLAttribute = new XMLAttribute(key, value); attributes.push(attribute); }); diff --git a/ts/dtd/AttDecl.ts b/ts/dtd/AttDecl.ts index 494cea8..e4df858 100644 --- a/ts/dtd/AttDecl.ts +++ b/ts/dtd/AttDecl.ts @@ -16,12 +16,15 @@ import { XMLNode } from "../XMLNode"; export class AttDecl implements XMLNode { private name: string; - private type: string; - private defaultType: string; + private attType: string; + private defaultDecl: string; private defaultValue: string; - constructor() { - // TODO + constructor(name: string, attType: string, defaultDecl: string, defaultValue: string) { + this.name = name; + this.attType = attType; + this.defaultDecl = defaultDecl; + this.defaultValue = defaultValue; } getNodeType(): number { @@ -30,13 +33,12 @@ export class AttDecl implements XMLNode { equals(node: XMLNode): boolean { if (node instanceof AttDecl) { - return this.name === node.name && this.type === node.type && this.defaultType === node.defaultType && this.defaultValue === node.defaultValue; + return this.name === node.name && this.attType === node.attType && this.defaultDecl === node.defaultDecl && this.defaultValue === node.defaultValue; } return false; } toString(): string { - // TODO - return this.name; + return (this.name + ' ' + this.attType + ' ' + this.defaultDecl + ' ' + this.defaultValue).trim(); } } \ No newline at end of file diff --git a/ts/dtd/AttlistDecl.ts b/ts/dtd/AttlistDecl.ts index 2b78d62..727f74e 100644 --- a/ts/dtd/AttlistDecl.ts +++ b/ts/dtd/AttlistDecl.ts @@ -14,17 +14,15 @@ import { Constants } from "../Constants"; import { XMLNode } from "../XMLNode"; import { AttDecl } from "./AttDecl"; -export class AttlistDecl implements XMLNode { +export class AttListDecl implements XMLNode { private name: string; - private atttibutesText: string; private attributes: Map; - private attTypes: string[] = ['CDATA', 'ID', 'IDREF', 'IDREFS', 'ENTITY', 'ENTITIES', 'NMTOKEN', 'NMTOKENS']; + static attTypes: string[] = ['CDATA', 'ID', 'IDREF', 'IDREFS', 'ENTITY', 'ENTITIES', 'NMTOKEN', 'NMTOKENS']; constructor(name: string, attributesText: string) { this.name = name; - this.atttibutesText = attributesText; this.attributes = new Map(); this.parseAttributes(attributesText); } @@ -38,18 +36,45 @@ export class AttlistDecl implements XMLNode { } parseAttributes(text: string) { - // TODO parse attributes - let parts: string[] = text.split(/[ \t\r\n]/); // (#x20 | #x9 | #xD | #xA) + let parts: string[] = this.split(text); let index: number = 0; while (index < parts.length) { let name: string = parts[index++]; - let type: string = parts[index++]; - let defaultType: string = parts[index++]; + let attType: string = parts[index++]; + let defaultDecl: string = ''; let defaultValue: string = ''; - if ('#FIXED' === defaultType) { + if (AttListDecl.attTypes.includes(attType)) { + defaultDecl = parts[index++]; + if (defaultDecl === '#FIXED') { + defaultValue = parts[index++]; + } + } else { + // TODO could be a NOTATION or an ennumeration defaultValue = parts[index++]; } + let att: AttDecl = new AttDecl(name, attType, defaultDecl, defaultValue); + this.attributes.set(name, att); + } + } + + split(text: string): string[] { + let result: string[] = []; + let word: string = ''; + for (let i = 0; i < text.length; i++) { + let c: string = text.charAt(i); + if (c === ' ' || c === '\n' || c === '\r' || c === '\t') { + if (word.length > 0) { + result.push(word); + word = ''; + } + } else { + word += c; + } + } + if (word.length > 0) { + result.push(word); } + return result; } getNodeType(): number { @@ -57,7 +82,7 @@ export class AttlistDecl implements XMLNode { } toString(): string { - let result: string = ' { result += ' ' + a.toString() + '\n'; }); @@ -65,7 +90,7 @@ export class AttlistDecl implements XMLNode { } equals(node: XMLNode): boolean { - if (node instanceof AttlistDecl) { + if (node instanceof AttListDecl) { let nodeAtts: Map = node.getAttributes(); if (this.name !== node.getName() || this.attributes.size !== nodeAtts.size) { return false; diff --git a/ts/dtd/DTDParser.ts b/ts/dtd/DTDParser.ts index 666b8b5..6aa786b 100644 --- a/ts/dtd/DTDParser.ts +++ b/ts/dtd/DTDParser.ts @@ -15,7 +15,7 @@ import * as path from "node:path"; import { Catalog } from "../Catalog"; import { XMLUtils } from "../XMLUtils"; import { Grammar } from "../grammar/Grammar"; -import { AttlistDecl } from "./AttlistDecl"; +import { AttListDecl } from "./AttListDecl"; import { ElementDecl } from "./ElementDecl"; import { EntityDecl } from "./EntityDecl"; import { NotationDecl } from "./NotationDecl"; @@ -92,7 +92,7 @@ export class DTDParser { } let attListText: string = this.source.substring(this.pointer, index + '>'.length); let length = attListText.length; - let attList: AttlistDecl = this.parseAttributesListDeclaration(attListText); + let attList: AttListDecl = this.parseAttributesListDeclaration(attListText); this.grammar.addAttributesList(attList); this.pointer += length; continue; @@ -631,7 +631,7 @@ export class DTDParser { return new NotationDecl(name, publicId, systemId); } - parseAttributesListDeclaration(declaration: string): AttlistDecl { + parseAttributesListDeclaration(declaration: string): AttListDecl { let i: number = '') { break; } - atttibutesText += char; + attributesText += char; } - return new AttlistDecl(name, atttibutesText); + let list: AttListDecl = new AttListDecl(name, attributesText) + return list; } parseElementDeclaration(declaration: string): ElementDecl { diff --git a/ts/grammar/Grammar.ts b/ts/grammar/Grammar.ts index 39d01e0..809551a 100644 --- a/ts/grammar/Grammar.ts +++ b/ts/grammar/Grammar.ts @@ -11,7 +11,7 @@ *******************************************************************************/ import { XMLUtils } from "../XMLUtils"; -import { AttlistDecl } from "../dtd/AttlistDecl"; +import { AttListDecl } from "../dtd/AttListDecl"; import { ElementDecl } from "../dtd/ElementDecl"; import { EntityDecl } from "../dtd/EntityDecl"; import { NotationDecl } from "../dtd/NotationDecl"; @@ -22,7 +22,7 @@ export class Grammar { private models: Map; private entitiesMap: Map; - private attributeListMap: Map; + private attributeListMap: Map; private elementDeclMap: Map; private notationsMap: Map; @@ -75,7 +75,7 @@ export class Grammar { return text; } - addAttributesList(attList: AttlistDecl) { + addAttributesList(attList: AttListDecl) { if (!this.attributeListMap.has(attList.getName())) { this.attributeListMap.set(attList.getName(), attList); } @@ -103,7 +103,7 @@ export class Grammar { this.entitiesMap.set(key, value); } }); - grammar.getAttributeListMap().forEach((value: AttlistDecl, key: string) => { + grammar.getAttributeListMap().forEach((value: AttListDecl, key: string) => { if (!this.attributeListMap.has(key)) { this.attributeListMap.set(key, value); } @@ -128,7 +128,7 @@ export class Grammar { return this.elementDeclMap; } - getAttributeListMap(): Map { + getAttributeListMap(): Map { return this.attributeListMap; } @@ -149,7 +149,7 @@ export class Grammar { let model: ContentModel = new ContentModel(name, contentSpec); this.models.set(name, model); }); - this.attributeListMap.forEach((attList: AttlistDecl) => { + this.attributeListMap.forEach((attList: AttListDecl) => { let name: string = attList.getName(); if (XMLUtils.hasParameterEntity(name)) { name = this.resolveParameterEntities(name); From d2d059d7a63f28a8acbfd5f438935b60029812d5 Mon Sep 17 00:00:00 2001 From: "Rodolfo M. Raya" Date: Tue, 9 Jan 2024 21:37:25 -0300 Subject: [PATCH 14/18] Improved handling of ennumerations in attributes --- ts/dtd/AttlistDecl.ts | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/ts/dtd/AttlistDecl.ts b/ts/dtd/AttlistDecl.ts index 727f74e..e90442c 100644 --- a/ts/dtd/AttlistDecl.ts +++ b/ts/dtd/AttlistDecl.ts @@ -49,8 +49,11 @@ export class AttListDecl implements XMLNode { defaultValue = parts[index++]; } } else { - // TODO could be a NOTATION or an ennumeration - defaultValue = parts[index++]; + if (attType === 'NOTATION') { + // TODO parse the notations in the ennumeration that follows + } else { + defaultValue = parts[index++]; + } } let att: AttDecl = new AttDecl(name, attType, defaultDecl, defaultValue); this.attributes.set(name, att); @@ -62,6 +65,16 @@ export class AttListDecl implements XMLNode { let word: string = ''; for (let i = 0; i < text.length; i++) { let c: string = text.charAt(i); + if (c === '(') { + // starts an enumeration + let enumeration: string = '('; + while (c !== ')') { + c = text.charAt(++i); + enumeration += c; + } + result.push(enumeration); + continue; + } if (c === ' ' || c === '\n' || c === '\r' || c === '\t') { if (word.length > 0) { result.push(word); From 50347209522f2949e11c85cd455ce12c4c5b8788 Mon Sep 17 00:00:00 2001 From: "Rodolfo M. Raya" Date: Tue, 23 Jan 2024 12:19:43 -0300 Subject: [PATCH 15/18] Added keywords --- package.json | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/package.json b/package.json index 675938a..9f1b83c 100644 --- a/package.json +++ b/package.json @@ -3,6 +3,13 @@ "productName": "TypesXML", "version": "1.2.1", "description": "Open source XML library written in TypeScript", + "keywords": [ + "XML", + "Parser", + "DOM", + "SAX", + "DTD" + ], "scripts": { "build": "tsc" }, From 713c1869d04f9923be241b3011e0b25e28c37ec7 Mon Sep 17 00:00:00 2001 From: "Rodolfo M. Raya" Date: Sat, 2 Mar 2024 12:36:02 -0300 Subject: [PATCH 16/18] Fixed buffer handling --- ts/DOMBuilder.ts | 1 + ts/SAXParser.ts | 41 +++++++++++++++++++++-------------------- 2 files changed, 22 insertions(+), 20 deletions(-) diff --git a/ts/DOMBuilder.ts b/ts/DOMBuilder.ts index 0b9e09b..5d5c28f 100644 --- a/ts/DOMBuilder.ts +++ b/ts/DOMBuilder.ts @@ -183,6 +183,7 @@ export class DOMBuilder implements ContentHandler { this.document.setDocumentType(docType); if (this.catalog) { this.grammarUrl = this.catalog.resolveEntity(publicId, systemId); + // TODO check grammar type (DTD, XDS or RelaxNG) and use the ritght parser if (this.dtdParser && this.grammarUrl) { let dtdGrammar :Grammar = this.dtdParser.parseDTD(this.grammarUrl); if (dtdGrammar) { diff --git a/ts/SAXParser.ts b/ts/SAXParser.ts index 2b117ec..92b29db 100644 --- a/ts/SAXParser.ts +++ b/ts/SAXParser.ts @@ -27,6 +27,8 @@ export class SAXParser { characterRun: string; rootParsed: boolean; + static readonly MIN_BUFFER_SIZE: number = 2048; + constructor() { this.characterRun = ''; this.elementStack = 0; @@ -109,7 +111,7 @@ export class SAXParser { } this.characterRun += char; this.pointer++; - if (this.pointer > this.buffer.length && this.reader.dataAvailable()) { + if (this.buffer.length - this.pointer < SAXParser.MIN_BUFFER_SIZE && this.reader.dataAvailable()) { this.buffer += this.reader.read(); } if (this.rootParsed && this.elementStack === 0) { @@ -128,7 +130,7 @@ export class SAXParser { let name: string = ''; while (!this.lookingAt(';')) { name += this.buffer.charAt(this.pointer++); - if (this.pointer > this.buffer.length && this.reader.dataAvailable()) { + if (this.buffer.length - this.pointer < SAXParser.MIN_BUFFER_SIZE && this.reader.dataAvailable()) { this.buffer += this.reader.read(); } } @@ -162,14 +164,14 @@ export class SAXParser { let name: string = ''; while (!XMLUtils.isXmlSpace(this.buffer.charAt(this.pointer)) && !this.lookingAt('>') && !this.lookingAt('/>')) { name += this.buffer.charAt(this.pointer++); - if (this.pointer > this.buffer.length && this.reader.dataAvailable()) { + if (this.buffer.length - this.pointer < SAXParser.MIN_BUFFER_SIZE && this.reader.dataAvailable()) { this.buffer += this.reader.read(); } } let rest: string = ''; while (!this.lookingAt('>') && !this.lookingAt('/>')) { rest += this.buffer.charAt(this.pointer++); - if (this.pointer > this.buffer.length && this.reader.dataAvailable()) { + if (this.buffer.length - this.pointer < SAXParser.MIN_BUFFER_SIZE && this.reader.dataAvailable()) { this.buffer += this.reader.read(); } } @@ -284,7 +286,7 @@ export class SAXParser { if (!XMLUtils.isXmlSpace(char)) { break; } - if (this.pointer + 1 > this.buffer.length && this.reader.dataAvailable()) { + if (this.buffer.length - this.pointer < SAXParser.MIN_BUFFER_SIZE && this.reader.dataAvailable()) { this.buffer += this.reader.read(); } } @@ -296,7 +298,7 @@ export class SAXParser { break; } name += char; - if (this.pointer + 1 >= this.buffer.length && this.reader.dataAvailable()) { + if (this.buffer.length - this.pointer < SAXParser.MIN_BUFFER_SIZE && this.reader.dataAvailable()) { this.buffer += this.reader.read(); } } @@ -306,14 +308,14 @@ export class SAXParser { if (!XMLUtils.isXmlSpace(char)) { break; } - if (this.pointer + 1 > this.buffer.length && this.reader.dataAvailable()) { + if (this.buffer.length - this.pointer < SAXParser.MIN_BUFFER_SIZE && this.reader.dataAvailable()) { this.buffer += this.reader.read(); } } // read external identifiers let systemId: string = ''; if (this.lookingAt('SYSTEM')) { - this.parseSystemDeclaration(); + systemId = this.parseSystemDeclaration(); } let publicId: string = ''; if (this.lookingAt('PUBLIC')) { @@ -328,7 +330,7 @@ export class SAXParser { if (!XMLUtils.isXmlSpace(char)) { break; } - if (this.pointer + 1 > this.buffer.length && this.reader.dataAvailable()) { + if (this.buffer.length - this.pointer < SAXParser.MIN_BUFFER_SIZE && this.reader.dataAvailable()) { this.buffer += this.reader.read(); } } @@ -342,19 +344,19 @@ export class SAXParser { break; } internalSubset += char; - if (this.pointer + 1 > this.buffer.length && this.reader.dataAvailable()) { + if (this.buffer.length - this.pointer < SAXParser.MIN_BUFFER_SIZE && this.reader.dataAvailable()) { this.buffer += this.reader.read(); } } + this.pointer++; // skip ']' } - this.pointer++; // skip ']' // skip spaces after internal subset for (; this.pointer < this.buffer.length; this.pointer++) { let char = this.buffer[this.pointer]; if (!XMLUtils.isXmlSpace(char)) { break; } - if (this.pointer + 1 > this.buffer.length && this.reader.dataAvailable()) { + if (this.buffer.length - this.pointer < SAXParser.MIN_BUFFER_SIZE && this.reader.dataAvailable()) { this.buffer += this.reader.read(); } } @@ -375,7 +377,7 @@ export class SAXParser { if (!XMLUtils.isXmlSpace(char)) { break; } - if (this.pointer + 1 > this.buffer.length && this.reader.dataAvailable()) { + if (this.buffer.length - this.pointer < SAXParser.MIN_BUFFER_SIZE && this.reader.dataAvailable()) { this.buffer += this.reader.read(); } } @@ -392,7 +394,7 @@ export class SAXParser { break; } publicId += char; - if (this.pointer + 1 > this.buffer.length && this.reader.dataAvailable()) { + if (this.buffer.length - this.pointer < SAXParser.MIN_BUFFER_SIZE && this.reader.dataAvailable()) { this.buffer += this.reader.read(); } } @@ -402,7 +404,7 @@ export class SAXParser { if (!XMLUtils.isXmlSpace(char)) { break; } - if (this.pointer + 1 > this.buffer.length && this.reader.dataAvailable()) { + if (this.buffer.length - this.pointer < SAXParser.MIN_BUFFER_SIZE && this.reader.dataAvailable()) { this.buffer += this.reader.read(); } } @@ -419,7 +421,7 @@ export class SAXParser { break; } systemIdId += char; - if (this.pointer + 1 > this.buffer.length && this.reader.dataAvailable()) { + if (this.buffer.length - this.pointer < SAXParser.MIN_BUFFER_SIZE && this.reader.dataAvailable()) { this.buffer += this.reader.read(); } } @@ -434,7 +436,7 @@ export class SAXParser { if (!XMLUtils.isXmlSpace(char)) { break; } - if (this.pointer + 1 > this.buffer.length && this.reader.dataAvailable()) { + if (this.buffer.length - this.pointer < SAXParser.MIN_BUFFER_SIZE && this.reader.dataAvailable()) { this.buffer += this.reader.read(); } } @@ -451,7 +453,7 @@ export class SAXParser { break; } systemId += char; - if (this.pointer + 1 > this.buffer.length && this.reader.dataAvailable()) { + if (this.buffer.length - this.pointer < SAXParser.MIN_BUFFER_SIZE && this.reader.dataAvailable()) { this.buffer += this.reader.read(); } } @@ -473,7 +475,7 @@ export class SAXParser { lookingAt(text: string): boolean { let length: number = text.length; - if (this.pointer + length > this.buffer.length && this.reader.dataAvailable()) { + if (this.buffer.length - this.pointer < SAXParser.MIN_BUFFER_SIZE && this.reader.dataAvailable()) { this.buffer += this.reader.read(); } if (this.pointer + length > this.buffer.length) { @@ -542,5 +544,4 @@ export class SAXParser { this.pointer = 0; this.contentHandler.endCDATA(); } - } \ No newline at end of file From 9b0ec03133b0fb3aad3290bca976e3794b011971 Mon Sep 17 00:00:00 2001 From: "Rodolfo M. Raya" Date: Sat, 2 Mar 2024 13:37:52 -0300 Subject: [PATCH 17/18] Renamed file --- ts/dtd/{AttlistDecl.ts => AttListDecl.ts} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename ts/dtd/{AttlistDecl.ts => AttListDecl.ts} (100%) diff --git a/ts/dtd/AttlistDecl.ts b/ts/dtd/AttListDecl.ts similarity index 100% rename from ts/dtd/AttlistDecl.ts rename to ts/dtd/AttListDecl.ts From ba025eec2d3c62c1a613eda6621c7600a8ecc769 Mon Sep 17 00:00:00 2001 From: "Rodolfo M. Raya" Date: Sat, 2 Mar 2024 14:01:10 -0300 Subject: [PATCH 18/18] Updated index & copyright --- package.json | 2 +- ts/CData.ts | 2 +- ts/Catalog.ts | 2 +- ts/Constants.ts | 2 +- ts/ContentHandler.ts | 2 +- ts/FileReader.ts | 2 +- ts/Indenter.ts | 2 +- ts/ProcessingInstruction.ts | 2 +- ts/SAXParser.ts | 2 +- ts/StringReader.ts | 2 +- ts/TextNode.ts | 2 +- ts/XMLAttribute.ts | 2 +- ts/XMLComment.ts | 2 +- ts/XMLDeclaration.ts | 2 +- ts/XMLDocument.ts | 2 +- ts/XMLDocumentType.ts | 2 +- ts/XMLElement.ts | 2 +- ts/XMLNode.ts | 2 +- ts/XMLReader.ts | 2 +- ts/XMLUtils.ts | 2 +- ts/XMLWriter.ts | 2 +- ts/dtd/AttDecl.ts | 2 +- ts/dtd/AttListDecl.ts | 2 +- ts/dtd/DTDParser.ts | 2 +- ts/dtd/ElementDecl.ts | 2 +- ts/dtd/EntityDecl.ts | 2 +- ts/dtd/InternalSubset.ts | 2 +- ts/dtd/NotationDecl.ts | 2 +- ts/grammar/ContentModel.ts | 2 +- ts/grammar/Grammar.ts | 2 +- ts/index.ts | 19 ++++++++++++++++--- 31 files changed, 46 insertions(+), 33 deletions(-) diff --git a/package.json b/package.json index 9f1b83c..8c64815 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "typesxml", "productName": "TypesXML", - "version": "1.2.1", + "version": "1.3.0", "description": "Open source XML library written in TypeScript", "keywords": [ "XML", diff --git a/ts/CData.ts b/ts/CData.ts index c55aea6..24bd692 100644 --- a/ts/CData.ts +++ b/ts/CData.ts @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2023 Maxprograms. + * Copyright (c) 2023 - 2024 Maxprograms. * * This program and the accompanying materials * are made available under the terms of the Eclipse License 1.0 diff --git a/ts/Catalog.ts b/ts/Catalog.ts index 752604a..fa85c98 100644 --- a/ts/Catalog.ts +++ b/ts/Catalog.ts @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2023 Maxprograms. + * Copyright (c) 2023 - 2024 Maxprograms. * * This program and the accompanying materials * are made available under the terms of the Eclipse License 1.0 diff --git a/ts/Constants.ts b/ts/Constants.ts index dc0ffc3..b995028 100644 --- a/ts/Constants.ts +++ b/ts/Constants.ts @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2023 Maxprograms. + * Copyright (c) 2023 - 2024 Maxprograms. * * This program and the accompanying materials * are made available under the terms of the Eclipse License 1.0 diff --git a/ts/ContentHandler.ts b/ts/ContentHandler.ts index 8e8243c..60ddf50 100644 --- a/ts/ContentHandler.ts +++ b/ts/ContentHandler.ts @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2023 Maxprograms. + * Copyright (c) 2023 - 2024 Maxprograms. * * This program and the accompanying materials * are made available under the terms of the Eclipse License 1.0 diff --git a/ts/FileReader.ts b/ts/FileReader.ts index eb47368..931fc52 100644 --- a/ts/FileReader.ts +++ b/ts/FileReader.ts @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2023 Maxprograms. + * Copyright (c) 2023 - 2024 Maxprograms. * * This program and the accompanying materials * are made available under the terms of the Eclipse License 1.0 diff --git a/ts/Indenter.ts b/ts/Indenter.ts index 4a7f683..9dcf11f 100644 --- a/ts/Indenter.ts +++ b/ts/Indenter.ts @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2023 Maxprograms. + * Copyright (c) 2023 - 2024 Maxprograms. * * This program and the accompanying materials * are made available under the terms of the Eclipse License 1.0 diff --git a/ts/ProcessingInstruction.ts b/ts/ProcessingInstruction.ts index 975260c..4376e7d 100644 --- a/ts/ProcessingInstruction.ts +++ b/ts/ProcessingInstruction.ts @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2023 Maxprograms. + * Copyright (c) 2023 - 2024 Maxprograms. * * This program and the accompanying materials * are made available under the terms of the Eclipse License 1.0 diff --git a/ts/SAXParser.ts b/ts/SAXParser.ts index 92b29db..00e8950 100644 --- a/ts/SAXParser.ts +++ b/ts/SAXParser.ts @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2023 Maxprograms. + * Copyright (c) 2023 - 2024 Maxprograms. * * This program and the accompanying materials * are made available under the terms of the Eclipse License 1.0 diff --git a/ts/StringReader.ts b/ts/StringReader.ts index 508532c..2ddcdd5 100644 --- a/ts/StringReader.ts +++ b/ts/StringReader.ts @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2023 Maxprograms. + * Copyright (c) 2023 - 2024 Maxprograms. * * This program and the accompanying materials * are made available under the terms of the Eclipse License 1.0 diff --git a/ts/TextNode.ts b/ts/TextNode.ts index 6dc2d72..e066a17 100644 --- a/ts/TextNode.ts +++ b/ts/TextNode.ts @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2023 Maxprograms. + * Copyright (c) 2023 - 2024 Maxprograms. * * This program and the accompanying materials * are made available under the terms of the Eclipse License 1.0 diff --git a/ts/XMLAttribute.ts b/ts/XMLAttribute.ts index 3dd958a..b00586b 100644 --- a/ts/XMLAttribute.ts +++ b/ts/XMLAttribute.ts @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2023 Maxprograms. + * Copyright (c) 2023 - 2024 Maxprograms. * * This program and the accompanying materials * are made available under the terms of the Eclipse License 1.0 diff --git a/ts/XMLComment.ts b/ts/XMLComment.ts index 269e227..ae7df6f 100644 --- a/ts/XMLComment.ts +++ b/ts/XMLComment.ts @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2023 Maxprograms. + * Copyright (c) 2023 - 2024 Maxprograms. * * This program and the accompanying materials * are made available under the terms of the Eclipse License 1.0 diff --git a/ts/XMLDeclaration.ts b/ts/XMLDeclaration.ts index 49f99b1..78f98c1 100644 --- a/ts/XMLDeclaration.ts +++ b/ts/XMLDeclaration.ts @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2023 Maxprograms. + * Copyright (c) 2023 - 2024 Maxprograms. * * This program and the accompanying materials * are made available under the terms of the Eclipse License 1.0 diff --git a/ts/XMLDocument.ts b/ts/XMLDocument.ts index fe330c7..da70e40 100644 --- a/ts/XMLDocument.ts +++ b/ts/XMLDocument.ts @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2023 Maxprograms. + * Copyright (c) 2023 - 2024 Maxprograms. * * This program and the accompanying materials * are made available under the terms of the Eclipse License 1.0 diff --git a/ts/XMLDocumentType.ts b/ts/XMLDocumentType.ts index 64a1d1c..8979283 100644 --- a/ts/XMLDocumentType.ts +++ b/ts/XMLDocumentType.ts @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2023 Maxprograms. + * Copyright (c) 2023 - 2024 Maxprograms. * * This program and the accompanying materials * are made available under the terms of the Eclipse License 1.0 diff --git a/ts/XMLElement.ts b/ts/XMLElement.ts index 24e24ac..aeed238 100644 --- a/ts/XMLElement.ts +++ b/ts/XMLElement.ts @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2023 Maxprograms. + * Copyright (c) 2023 - 2024 Maxprograms. * * This program and the accompanying materials * are made available under the terms of the Eclipse License 1.0 diff --git a/ts/XMLNode.ts b/ts/XMLNode.ts index b977784..e57633b 100644 --- a/ts/XMLNode.ts +++ b/ts/XMLNode.ts @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2023 Maxprograms. + * Copyright (c) 2023 - 2024 Maxprograms. * * This program and the accompanying materials * are made available under the terms of the Eclipse License 1.0 diff --git a/ts/XMLReader.ts b/ts/XMLReader.ts index f5f3a77..ad8b79e 100644 --- a/ts/XMLReader.ts +++ b/ts/XMLReader.ts @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2023 Maxprograms. + * Copyright (c) 2023 - 2024 Maxprograms. * * This program and the accompanying materials * are made available under the terms of the Eclipse License 1.0 diff --git a/ts/XMLUtils.ts b/ts/XMLUtils.ts index 2a895fc..31d6acd 100644 --- a/ts/XMLUtils.ts +++ b/ts/XMLUtils.ts @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2023 Maxprograms. + * Copyright (c) 2023 - 2024 Maxprograms. * * This program and the accompanying materials * are made available under the terms of the Eclipse License 1.0 diff --git a/ts/XMLWriter.ts b/ts/XMLWriter.ts index eb28c28..5c25031 100644 --- a/ts/XMLWriter.ts +++ b/ts/XMLWriter.ts @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2023 Maxprograms. + * Copyright (c) 2023 - 2024 Maxprograms. * * This program and the accompanying materials * are made available under the terms of the Eclipse License 1.0 diff --git a/ts/dtd/AttDecl.ts b/ts/dtd/AttDecl.ts index e4df858..d222f0c 100644 --- a/ts/dtd/AttDecl.ts +++ b/ts/dtd/AttDecl.ts @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2023 Maxprograms. + * Copyright (c) 2023 - 2024 Maxprograms. * * This program and the accompanying materials * are made available under the terms of the Eclipse License 1.0 diff --git a/ts/dtd/AttListDecl.ts b/ts/dtd/AttListDecl.ts index e90442c..9027542 100644 --- a/ts/dtd/AttListDecl.ts +++ b/ts/dtd/AttListDecl.ts @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2023 Maxprograms. + * Copyright (c) 2023 - 2024 Maxprograms. * * This program and the accompanying materials * are made available under the terms of the Eclipse License 1.0 diff --git a/ts/dtd/DTDParser.ts b/ts/dtd/DTDParser.ts index 6aa786b..4bc4e07 100644 --- a/ts/dtd/DTDParser.ts +++ b/ts/dtd/DTDParser.ts @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2023 Maxprograms. + * Copyright (c) 2023 - 2024 Maxprograms. * * This program and the accompanying materials * are made available under the terms of the Eclipse License 1.0 diff --git a/ts/dtd/ElementDecl.ts b/ts/dtd/ElementDecl.ts index 14e6379..d7fcf52 100644 --- a/ts/dtd/ElementDecl.ts +++ b/ts/dtd/ElementDecl.ts @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2023 Maxprograms. + * Copyright (c) 2023 - 2024 Maxprograms. * * This program and the accompanying materials * are made available under the terms of the Eclipse License 1.0 diff --git a/ts/dtd/EntityDecl.ts b/ts/dtd/EntityDecl.ts index 0bcd5f8..422e1f0 100644 --- a/ts/dtd/EntityDecl.ts +++ b/ts/dtd/EntityDecl.ts @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2023 Maxprograms. + * Copyright (c) 2023 - 2024 Maxprograms. * * This program and the accompanying materials * are made available under the terms of the Eclipse License 1.0 diff --git a/ts/dtd/InternalSubset.ts b/ts/dtd/InternalSubset.ts index 005a83b..de07cf0 100644 --- a/ts/dtd/InternalSubset.ts +++ b/ts/dtd/InternalSubset.ts @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2023 Maxprograms. + * Copyright (c) 2023 - 2024 Maxprograms. * * This program and the accompanying materials * are made available under the terms of the Eclipse License 1.0 diff --git a/ts/dtd/NotationDecl.ts b/ts/dtd/NotationDecl.ts index cbf32b4..ec134df 100644 --- a/ts/dtd/NotationDecl.ts +++ b/ts/dtd/NotationDecl.ts @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2023 Maxprograms. + * Copyright (c) 2023 - 2024 Maxprograms. * * This program and the accompanying materials * are made available under the terms of the Eclipse License 1.0 diff --git a/ts/grammar/ContentModel.ts b/ts/grammar/ContentModel.ts index 138b4a5..81f0e45 100644 --- a/ts/grammar/ContentModel.ts +++ b/ts/grammar/ContentModel.ts @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2023 Maxprograms. + * Copyright (c) 2023 - 2024 Maxprograms. * * This program and the accompanying materials * are made available under the terms of the Eclipse License 1.0 diff --git a/ts/grammar/Grammar.ts b/ts/grammar/Grammar.ts index 809551a..3ad7e1a 100644 --- a/ts/grammar/Grammar.ts +++ b/ts/grammar/Grammar.ts @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2023 Maxprograms. + * Copyright (c) 2023 - 2024 Maxprograms. * * This program and the accompanying materials * are made available under the terms of the Eclipse License 1.0 diff --git a/ts/index.ts b/ts/index.ts index c698051..6d5c830 100644 --- a/ts/index.ts +++ b/ts/index.ts @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2023 Maxprograms. + * Copyright (c) 2023 - 2024 Maxprograms. * * This program and the accompanying materials * are made available under the terms of the Eclipse License 1.0 @@ -10,8 +10,8 @@ * Maxprograms - initial API and implementation *******************************************************************************/ -export { Catalog } from "./Catalog"; export { CData } from "./CData"; +export { Catalog } from "./Catalog"; export { Constants } from "./Constants"; export { ContentHandler } from "./ContentHandler"; export { DOMBuilder } from "./DOMBuilder"; @@ -19,6 +19,7 @@ export { FileReader } from "./FileReader"; export { Indenter } from "./Indenter"; export { ProcessingInstruction } from "./ProcessingInstruction"; export { SAXParser } from "./SAXParser"; +export { StringReader } from "./StringReader"; export { TextNode } from "./TextNode"; export { XMLAttribute } from "./XMLAttribute"; export { XMLComment } from "./XMLComment"; @@ -27,5 +28,17 @@ export { XMLDocument } from "./XMLDocument"; export { XMLDocumentType } from "./XMLDocumentType"; export { XMLElement } from "./XMLElement"; export { XMLNode } from "./XMLNode"; +export { XMLReader } from "./XMLReader"; export { XMLUtils } from "./XMLUtils"; -export { XMLWriter } from "./XMLWriter"; \ No newline at end of file +export { XMLWriter } from "./XMLWriter"; + +export { AttDecl } from "./dtd/AttDecl"; +export { AttListDecl } from "./dtd/AttListDecl"; +export { DTDParser } from "./dtd/DTDParser"; +export { ElementDecl } from "./dtd/ElementDecl"; +export { EntityDecl } from "./dtd/EntityDecl"; +export { InternalSubset } from "./dtd/InternalSubset"; +export { NotationDecl } from "./dtd/NotationDecl"; + +export { ContentModel } from "./grammar/ContentModel"; +export { Grammar } from "./grammar/Grammar"; \ No newline at end of file