UNPKG

typesxml

Version:

Open source XML library written in TypeScript

295 lines 12.1 kB
"use strict"; /******************************************************************************* * Copyright (c) 2023-2026 Maxprograms. * * This program and the accompanying materials * are made available under the terms of the Eclipse Public 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 *******************************************************************************/ Object.defineProperty(exports, "__esModule", { value: true }); exports.AttListDecl = void 0; const Constants_js_1 = require("../Constants.js"); const XMLUtils_js_1 = require("../XMLUtils.js"); const AttDecl_js_1 = require("./AttDecl.js"); class AttListDecl { name; attributes; static attTypes = ['CDATA', 'ID', 'IDREF', 'IDREFS', 'ENTITY', 'ENTITIES', 'NMTOKEN', 'NMTOKENS']; constructor(name, attributesText) { this.name = name; this.attributes = new Map(); this.parseAttributes(attributesText); } getName() { return this.name; } getAttributes() { return this.attributes; } parseAttributes(text) { const parts = this.split(text); const state = { index: 0 }; let scanIndex = 0; while (state.index < parts.length) { const name = parts[state.index++]; if (!name) { continue; } if (!XMLUtils_js_1.XMLUtils.isValidXMLName(name)) { throw new Error('Invalid attribute name in ATTLIST declaration: ' + '\'' + name + '\''); } const nameMatch = this.locateToken(text, name, scanIndex); scanIndex = nameMatch.position + nameMatch.length; if (state.index >= parts.length) { throw new Error('Missing attribute type for attribute ' + '\'' + name + '\''); } const attType = this.readAttributeType(parts, state); if (!this.isValidAttributeType(attType)) { throw new Error('Invalid attribute type in ATTLIST declaration: ' + '\'' + attType + '\''); } const typeMatch = this.locateToken(text, attType, scanIndex); scanIndex = typeMatch.position + typeMatch.length; let defaultDecl = ''; let defaultValue = ''; if (state.index < parts.length) { const nextPart = parts[state.index]; if (nextPart === '#REQUIRED' || nextPart === '#IMPLIED') { const keywordMatch = this.locateToken(text, nextPart, scanIndex); this.ensureSeparated(text, scanIndex, keywordMatch.position, attType, nextPart); scanIndex = keywordMatch.position + keywordMatch.length; defaultDecl = nextPart; state.index++; } else if (nextPart === '#FIXED') { const fixedMatch = this.locateToken(text, nextPart, scanIndex); this.ensureSeparated(text, scanIndex, fixedMatch.position, attType, nextPart); scanIndex = fixedMatch.position + fixedMatch.length; defaultDecl = nextPart; state.index++; if (state.index >= parts.length) { throw new Error('Invalid attribute declaration: missing value for #FIXED attribute ' + '\'' + name + '\''); } const valueToken = parts[state.index++]; const valueMatch = this.locateToken(text, valueToken, scanIndex); this.ensureSeparated(text, scanIndex, valueMatch.position, nextPart, valueToken); defaultValue = this.isQuotedValue(valueToken) ? this.trimQuotes(valueToken) : valueToken; scanIndex = valueMatch.position + valueMatch.length; } else if (nextPart && this.isQuotedValue(nextPart)) { const valueMatch = this.locateToken(text, nextPart, scanIndex); this.ensureSeparated(text, scanIndex, valueMatch.position, attType, nextPart); defaultDecl = nextPart; defaultValue = this.trimQuotes(nextPart); scanIndex = valueMatch.position + valueMatch.length; state.index++; } else if (nextPart && !XMLUtils_js_1.XMLUtils.isValidXMLName(nextPart)) { throw new Error('Invalid attribute declaration: unexpected token ' + '\'' + nextPart + '\'' + ' after attribute type ' + '\'' + attType + '\''); } } const att = new AttDecl_js_1.AttDecl(name, attType, defaultDecl, defaultValue); this.attributes.set(name, att); } } split(text) { let result = []; let word = ''; let inQuotes = false; let quoteChar = ''; for (let i = 0; i < text.length; i++) { let c = text.charAt(i); if ((c === '"' || c === "'") && !inQuotes) { if (word.length > 0) { result.push(word); word = ''; } inQuotes = true; quoteChar = c; word += c; } else if (inQuotes && c === quoteChar) { inQuotes = false; quoteChar = ''; word += c; if (word.length > 0) { result.push(word); word = ''; } } else if ((c === ' ' || c === '\n' || c === '\r' || c === '\t') && !inQuotes) { // Whitespace outside quotes - split here if (word.length > 0) { result.push(word); word = ''; } } else { // Regular character (including parentheses) word += c; } } if (word.length > 0) { result.push(word); } return result; } readAttributeType(parts, state) { let token = parts[state.index++]; if (token === 'NOTATION') { if (state.index >= parts.length) { throw new Error('Expected NOTATION enumeration in ATTLIST declaration'); } let enumeration = parts[state.index++]; enumeration = this.readParenthesized(enumeration, parts, state); return 'NOTATION ' + enumeration; } if (token.includes('(')) { return this.readParenthesized(token, parts, state); } return token; } readParenthesized(initial, parts, state) { let result = initial; let balance = this.countParenthesis(initial); while (balance > 0) { if (state.index >= parts.length) { throw new Error('Unterminated parenthesized list in ATTLIST declaration'); } const next = parts[state.index++]; result += ' ' + next; balance += this.countParenthesis(next); } return this.normalizeEnumeration(result); } countParenthesis(value) { let balance = 0; for (const char of value) { if (char === '(') { balance++; } else if (char === ')') { balance--; } } return balance; } normalizeEnumeration(value) { let normalized = value.replaceAll(/\s+/g, ' '); normalized = normalized.replaceAll(' | ', '|').replaceAll(' |', '|').replaceAll('| ', '|'); normalized = normalized.replaceAll('( ', '('); normalized = normalized.replaceAll(' )', ')'); return normalized.trim(); } isQuotedValue(value) { return (value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'")); } trimQuotes(value) { return this.isQuotedValue(value) ? value.substring(1, value.length - 1) : value; } isValidAttributeType(attType) { if (AttListDecl.attTypes.includes(attType)) { return true; } if (attType.startsWith('NOTATION ')) { const notation = attType.substring('NOTATION '.length).trim(); return notation.startsWith('(') && notation.endsWith(')'); } if (attType.startsWith('(') && attType.endsWith(')')) { return true; } return false; } locateToken(text, token, startIndex) { const direct = text.indexOf(token, startIndex); if (direct !== -1) { return { position: direct, length: token.length }; } if (token.startsWith('(') && token.endsWith(')')) { const normalizedToken = this.normalizeEnumeration(token); let searchIndex = startIndex; while (searchIndex < text.length) { const openIndex = text.indexOf('(', searchIndex); if (openIndex === -1) { break; } let depth = 0; let cursor = openIndex; while (cursor < text.length) { const char = text.charAt(cursor); if (char === '(') { depth++; } else if (char === ')') { depth--; if (depth === 0) { const segment = text.substring(openIndex, cursor + 1); if (this.normalizeEnumeration(segment) === normalizedToken) { return { position: openIndex, length: segment.length }; } cursor++; break; } } cursor++; } if (depth > 0) { break; } searchIndex = cursor; } } throw new Error('Invalid attribute declaration: unable to locate token ' + '\'' + token + '\''); } ensureSeparated(text, startIndex, tokenPosition, previousToken, currentToken) { if (tokenPosition < startIndex) { throw new Error('Invalid attribute declaration: unexpected ordering between ' + '\'' + previousToken + '\'' + ' and ' + '\'' + currentToken + '\''); } const between = text.substring(startIndex, tokenPosition); if (!this.containsWhitespace(between)) { throw new Error('Invalid attribute declaration: missing whitespace between ' + '\'' + previousToken + '\'' + ' and ' + '\'' + currentToken + '\''); } } containsWhitespace(segment) { for (const char of segment) { if (XMLUtils_js_1.XMLUtils.isXmlSpace(char)) { return true; } } return false; } getNodeType() { return Constants_js_1.Constants.ATTRIBUTE_LIST_DECL_NODE; } toString() { let result = '<!ATTLIST ' + this.name + '\n'; this.attributes.forEach((a) => { result += ' ' + a.toString() + '\n'; }); return result + '>'; } equals(node) { if (node instanceof AttListDecl) { let nodeAtts = node.getAttributes(); if (this.name !== node.getName() || this.attributes.size !== nodeAtts.size) { return false; } for (let [key, value] of this.attributes) { let att = nodeAtts.get(key); if (att === undefined) { return false; } if (!value.equals(att)) { return false; } } return true; } return false; } } exports.AttListDecl = AttListDecl; //# sourceMappingURL=AttListDecl.js.map