typesxml
Version:
Open source XML library written in TypeScript
274 lines • 9.97 kB
JavaScript
"use strict";
/*******************************************************************************
* Copyright (c) 2023-2026 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
*******************************************************************************/
Object.defineProperty(exports, "__esModule", { value: true });
exports.JsonNodeReader = void 0;
const NeedMoreDataError_js_1 = require("../NeedMoreDataError.js");
class JsonNodeReader {
tokenizer;
sequenceStarted = false;
sequenceEnded = false;
expectComma = false;
pendingTrailingValidation = false;
constructor(tokenizer) {
this.tokenizer = tokenizer;
}
readNextEvent() {
if (this.sequenceEnded) {
if (this.pendingTrailingValidation) {
const trailing = this.tokenizer.peekToken();
if (trailing.type !== 'eof') {
throw new Error('Unexpected trailing content after JSON event stream');
}
this.pendingTrailingValidation = false;
}
return undefined;
}
this.ensureSequenceStarted();
if (this.expectComma) {
const nextToken = this.tokenizer.peekToken();
if (nextToken.type === 'bracketClose') {
this.consumeAndFinish();
return undefined;
}
this.consumeDelimiter('comma');
this.expectComma = false;
}
const lookAhead = this.tokenizer.peekToken();
if (lookAhead.type === 'bracketClose') {
this.consumeAndFinish();
return undefined;
}
const value = this.parseValue();
if (value === null || typeof value !== 'object' || Array.isArray(value)) {
throw new Error('JSON event entries must be objects');
}
const payload = value;
const event = this.toEvent(payload);
this.expectComma = true;
return event;
}
ensureSequenceStarted() {
if (this.sequenceStarted) {
return;
}
const token = this.tokenizer.nextToken();
if (token.type !== 'bracketOpen') {
throw new Error('JSON event stream must start with an array');
}
this.sequenceStarted = true;
}
consumeAndFinish() {
const endToken = this.tokenizer.nextToken();
if (endToken.type !== 'bracketClose') {
throw new Error('Malformed JSON event stream: expected closing bracket');
}
this.sequenceEnded = true;
try {
const trailing = this.tokenizer.peekToken();
if (trailing.type !== 'eof') {
throw new Error('Unexpected trailing content after JSON event stream');
}
this.pendingTrailingValidation = false;
}
catch (error) {
if (error instanceof NeedMoreDataError_js_1.NeedMoreDataError) {
this.pendingTrailingValidation = true;
return;
}
throw error;
}
}
consumeDelimiter(expected) {
const token = this.tokenizer.nextToken();
if (token.type !== expected) {
throw new Error('Malformed JSON event stream: expected comma delimiter');
}
}
parseValue() {
const token = this.tokenizer.nextToken();
switch (token.type) {
case 'string':
return token.value;
case 'number':
return token.value;
case 'boolean':
return token.value;
case 'null':
return null;
case 'braceOpen':
return this.parseObject();
case 'bracketOpen':
return this.parseArray();
case 'eof':
throw new Error('Unexpected end of JSON stream while parsing value');
default:
throw new Error('Unexpected token while parsing value');
}
}
parseObject() {
const result = {};
const next = this.tokenizer.peekToken();
if (next.type === 'braceClose') {
this.tokenizer.nextToken();
return result;
}
while (true) {
const keyToken = this.tokenizer.nextToken();
if (keyToken.type !== 'string') {
throw new Error('Object keys must be strings');
}
const colon = this.tokenizer.nextToken();
if (colon.type !== 'colon') {
throw new Error('Expected colon after object key');
}
const value = this.parseValue();
result[keyToken.value] = value;
const delimiter = this.tokenizer.nextToken();
if (delimiter.type === 'braceClose') {
break;
}
if (delimiter.type !== 'comma') {
throw new Error('Expected comma between object members');
}
}
return result;
}
parseArray() {
const result = [];
const next = this.tokenizer.peekToken();
if (next.type === 'bracketClose') {
this.tokenizer.nextToken();
return result;
}
while (true) {
result.push(this.parseValue());
const delimiter = this.tokenizer.nextToken();
if (delimiter.type === 'bracketClose') {
break;
}
if (delimiter.type !== 'comma') {
throw new Error('Expected comma between array items');
}
}
return result;
}
toEvent(payload) {
const type = this.requireString(payload, 'type');
switch (type) {
case 'startDocument':
return { type: 'startDocument' };
case 'endDocument':
return { type: 'endDocument' };
case 'xmlDeclaration':
return {
type: 'xmlDeclaration',
version: this.requireString(payload, 'version'),
encoding: this.requireString(payload, 'encoding'),
standalone: this.optionalString(payload, 'standalone')
};
case 'startElement':
return {
type: 'startElement',
name: this.requireString(payload, 'name'),
attributes: this.readAttributes(payload.attributes)
};
case 'endElement':
return {
type: 'endElement',
name: this.requireString(payload, 'name')
};
case 'characters':
return {
type: 'characters',
value: this.requireString(payload, 'value')
};
case 'ignorableWhitespace':
return {
type: 'ignorableWhitespace',
value: this.requireString(payload, 'value')
};
case 'comment':
return {
type: 'comment',
value: this.requireString(payload, 'value')
};
case 'processingInstruction':
return {
type: 'processingInstruction',
target: this.requireString(payload, 'target'),
data: this.requireString(payload, 'data')
};
case 'startCDATA':
return { type: 'startCDATA' };
case 'endCDATA':
return { type: 'endCDATA' };
case 'startDTD':
return {
type: 'startDTD',
name: this.requireString(payload, 'name'),
publicId: this.optionalString(payload, 'publicId') ?? '',
systemId: this.optionalString(payload, 'systemId') ?? ''
};
case 'internalSubset':
return {
type: 'internalSubset',
declaration: this.requireString(payload, 'declaration')
};
case 'endDTD':
return { type: 'endDTD' };
case 'skippedEntity':
return {
type: 'skippedEntity',
name: this.requireString(payload, 'name')
};
default:
throw new Error('Unsupported JSON event type: ' + type);
}
}
readAttributes(source) {
if (source === undefined) {
return [];
}
if (!Array.isArray(source)) {
throw new Error('Attributes must be an array');
}
return source.map((item) => {
if (item === null || typeof item !== 'object') {
throw new Error('Attribute entries must be objects');
}
const record = item;
const name = this.requireString(record, 'name');
const value = this.requireString(record, 'value');
return { name, value };
});
}
requireString(record, key) {
const value = record[key];
if (typeof value === 'string') {
return value;
}
throw new Error('Expected string value for property "' + key + '"');
}
optionalString(record, key) {
const value = record[key];
if (value === undefined) {
return undefined;
}
if (typeof value === 'string') {
return value;
}
throw new Error('Expected string value for property "' + key + '"');
}
}
exports.JsonNodeReader = JsonNodeReader;
//# sourceMappingURL=JsonNodeReader.js.map