nxkit
Version:
This is a collection of tools, independent of any other libraries
456 lines (455 loc) • 16.4 kB
JavaScript
"use strict";
/* ***** BEGIN LICENSE BLOCK *****
* Distributed under the BSD license:
*
* Copyright (c) 2015, xuewen.chu
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of xuewen.chu nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL xuewen.chu BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* ***** END LICENSE BLOCK ***** */
Object.defineProperty(exports, "__esModule", { value: true });
// import util from '../util';
const doc = require("./document");
const node_1 = require("./node");
const ENTITY_MAP = { 'lt': '<', 'gt': '>', 'amp': '&', 'quot': '"', 'apos': "'", 'nbsp': '\u00a0' };
class Attributes {
constructor() {
this.length = 0;
}
getLocalName(i) { return this[i].localName; }
getOffset(i) { return this[i].offset; }
getQName(i) { return this[i].qName; }
getURI(i) { return this[i].uri; }
getValue(i) { return this[i].value; }
}
function split(source) {
var match;
var buf = [];
var reg = /'[^']+'|"[^"]+"|[^\s<>\/=]+(?:\s*=\s*)?|(\/?\s*>|<)/g;
reg.lastIndex = 0;
reg.exec(source); //skip <
while (match = reg.exec(source)) {
buf.push(match);
if (match[1])
return buf;
}
throw 'Err';
}
class XMLReader {
/**
* constructor function
* @constructor
*/
constructor(handler, handler1, handler2) {
this.contentHandler = handler;
this.lexicalHandler = handler1;
this.errorHandler = handler2;
this._stack = [{ nsMap: {}, nsStack: {} }];
}
_entityReplacer(a) {
var k = a.slice(1, -1);
if (k.charAt(0) == '#')
return String.fromCharCode(parseInt(k.substr(1).replace('x', '0x')));
else if (k in ENTITY_MAP)
return ENTITY_MAP[k];
else {
this.errorHandler && this.errorHandler.error('entity not found:' + a);
return a;
}
}
_parse(source) {
while (true) {
var i = source.indexOf('<');
var next = source.charAt(i + 1);
if (i < 0) {
this._appendText(source, source.length);
return;
}
if (i > 0) {
this._appendText(source, i);
source = source.substring(i);
}
switch (next) {
case '/':
var end = source.indexOf('>', 3);
var qName = source.substring(2, end);
var config = this._stack.pop();
source = source.substring(end + 1);
this.contentHandler.endElement(config.uri, config.localName, qName);
for (qName in config.nsMap) {
this.contentHandler.endPrefixMapping(qName); //reuse qName as prefix
}
// end elment
break;
case '?': // ...
source = this._parseInstruction(source);
break;
case '!': // 0 ? key.substr(0, nsp) : null;
var localName = nsp > 0 ? value.substr(nsp + 1) : value;
let attr = attrs[attrs.length++] = {
prefix: prefix,
qName: value,
localName,
offset: 0,
value: '',
uri: '',
};
if (value == key) { //default value
//TODO:check
}
else {
//add key value
m = tokens[i++];
key = value;
value = m[0];
let nsp = value.charAt(0);
if ((nsp == '"' || nsp == "'") && nsp == value.charAt(value.length - 1)) {
value = value.slice(1, -1);
}
value = value.replace(/&#?\w+;/g, replace);
//TODO:encode value
}
if (prefix == 'xmlns' || key == 'xmlns') {
attr.uri = 'http://www.w3.org/2000/xmlns/';
if (!nsMap)
nsMap = {};
nsMap[prefix == 'xmlns' ? attr.localName : ''] = value;
}
else if (prefix) {
if (prefix == 'xml')
attr.uri = 'http://www.w3.org/XML/1998/namespace';
else
unsetURIs.push(attr);
}
attr.value = value;
attr.offset = m.index;
}
var stack = self._stack;
var top = stack[stack.length - 1];
var config = { qName: qName };
var nsStack = top.nsStack;
//print(stack+'#'+nsStack)
nsStack = config.nsStack =
(nsMap ? Object.assign({}, nsStack, nsMap) : nsStack);
config.uri = nsStack[qName.slice(0, -localName.length)];
while (attr = unsetURIs.pop())
attr.uri = nsStack[attr.prefix];
if (nsMap) {
for (prefix in nsMap)
self.contentHandler.startPrefixMapping(prefix, nsMap[prefix]);
}
self.contentHandler.startElement(localName, qName, attrs, uri);
if (end[0].charAt(0) == '/') {
self.contentHandler.endElement(localName, qName, uri);
if (nsMap) {
for (prefix in nsMap)
self.contentHandler.endPrefixMapping(prefix);
}
}
else
stack.push(config);
return source.substr(end.index + end[0].length);
}
_appendText(source, len) {
source = source.substr(0, len);
var contentHandler = this.contentHandler;
var reg = /&(#?)(\w+);/g;
var prevIndex = 0;
var mat;
while (mat = reg.exec(source)) {
var index = mat.index;
var text = mat[0];
if (prevIndex != index)
contentHandler.characters(source, prevIndex, index - prevIndex);
if (mat[1]) {
var value = this._entityReplacer(text);
contentHandler.characters(value, 0, value.length);
}
else
contentHandler.startEntityReference(mat[2]);
prevIndex = index + text.length;
}
if (prevIndex != len)
contentHandler.characters(source, prevIndex, len - prevIndex);
}
_parseInstruction(source) {
var match = source.match(/^<\?(\S*)\s*(.*)\?>/);
if (match) {
var len = match[0].length;
this.contentHandler.processingInstruction(match[1], match[2]);
}
else //error
this._appendText(source, len = 2);
return source.substring(len);
}
_parseDCC(source) {
var next = source.charAt(2);
if (next == '-') {
if (source.charAt(3) == '-') {
var end = source.indexOf('-->');
//append comment source.substring(4,end)//<!--
var lex = this.lexicalHandler;
lex && lex.comment(source, 4, end - 4);
return source.substring(end + 3);
}
else {
//error
this._appendText(source, 3);
return source.substr(3);
}
}
else {
if (/^<!\[CDATA\[/.test(source)) {
var end = source.indexOf(']]>');
var lex = this.lexicalHandler;
lex.startCDATA();
// appendText(self, source.substring(9, end), 0, end - 9);
this._appendText(source.substring(9, end), end - 9);
lex.endCDATA();
return source.substring(end + 3);
}
//<!DOCTYPE
//startDTD(java.lang.String name, java.lang.String publicId, java.lang.String systemId)
var matchs = split(source);
var len = matchs.length;
if (len > 1 && /!doctype/i.test(matchs[0][0])) {
var name = matchs[1][0];
var pubid = len > 3 && /^public$/i.test(matchs[2][0]) && matchs[3][0];
var sysid = len > 4 && matchs[4][0];
var lex = this.lexicalHandler;
var reg = /^"?([^"]*)"?$/;
lex.startDTD(name, pubid ? (pubid.match(reg)[1]) : '', sysid ? (sysid.match(reg)[1]) : '');
lex.endDTD();
let match = matchs[len - 1];
return source.substr(match.index + match[0].length);
}
else {
this._appendText(source, 2);
return source.substr(2);
}
}
}
parse(source) {
this.contentHandler.startDocument();
this._parse(source);
this.contentHandler.endDocument();
}
fragment(source) {
this._parse(source);
}
}
function noop() {
return null;
}
class DOMHandler {
/**
* constructor function
* @param {Document} doc (Optional)
* @param {Element} el (Optional)
* @constructor
*/
constructor(d, el) {
this.endEntityReference = noop;
this.endDTD = noop;
this.startEntity = noop;
this.endEntity = noop;
this.attributeDecl = noop;
this.elementDecl = noop;
this.externalEntityDecl = noop;
this.internalEntityDecl = noop;
this.resolveEntity = noop;
this.getExternalSubset = noop;
this.notationDecl = noop;
this.unparsedEntityDecl = noop;
this.saxExceptions = [];
this.cdata = false;
this.document = d || new doc.Document();
this.currentElement = el;
}
/* Private static helpers treated below as private instance methods,
so don't need to add these to the public API; we might use a Relator
to also get rid of non-standard public properties */
_appendElement(node) {
if (this.currentElement)
this.currentElement.appendChild(node);
else
this.document.appendChild(node);
}
startDocument() {
// this.document = new doc.Document();
// if (this.locator)
// this.document.documentURI = this.locator.getSystemId();
}
endDocument() {
this.document.normalize();
}
setDocumentLocator(locator) {
this.locator = locator;
}
startElement(localName, qName, attrs, namespaceURI) {
var doc = this.document;
var el = namespaceURI ?
doc.createElementNS(namespaceURI, qName || localName) :
doc.createElement(qName || localName);
var len = attrs.length;
this._appendElement(el);
this.currentElement = el;
for (var i = 0; i < len; i++) {
let namespaceURI = attrs.getURI(i);
let value = attrs.getValue(i);
let qName = attrs.getQName(i);
if (namespaceURI)
this.currentElement.setAttributeNS(namespaceURI, qName, value);
else
this.currentElement.setAttribute(qName, value);
}
}
endElement(localName, qName, namespaceURI) {
var parent = this.currentElement.parentNode;
// if (parent && parent.tagName != qName){
// var err = 'Xml format error "</' + qName + '>" no start tag';
// throw err;
// }
this.currentElement = parent;
}
toString(chars, start, length) {
return typeof chars == 'string' ?
chars.substr(start, length) :
Array.toArray(chars).slice(start, start + length).join('');
}
startPrefixMapping(prefix, uri) { }
endPrefixMapping(prefix) { }
processingInstruction(target, data) {
var ins = this.document.createProcessingInstruction(target, data);
this._appendElement(ins);
}
ignorableWhitespace(ch, start, length) { }
characters(chars, start, length) {
chars = this.toString(chars, start, length);
if (this.currentElement && chars) {
if (this.cdata) {
var cdataNode = this.document.createCDATASection(chars);
this.currentElement.appendChild(cdataNode);
}
else {
var textNode = this.document.createTextNode(chars);
this.currentElement.appendChild(textNode);
}
}
}
skippedEntity(name) { }
//LexicalHandler
comment(chars, start, length) {
chars = this.toString(chars, start, length);
var comment = this.document.createComment(chars);
this._appendElement(comment);
}
startCDATA() {
//used in characters() methods
this.cdata = true;
}
endCDATA() {
this.cdata = false;
}
startDTD(name, publicId, systemId) {
var doc = this.document;
var doctype = new node_1.DocumentType(doc, name, publicId, systemId);
doc.doctype = doctype;
doc.appendChild(doctype);
}
startEntityReference(name) {
var v = ENTITY_MAP[name];
var node = this.document.createEntityReference(name, v);
this.currentElement.appendChild(node);
}
warning(error) {
this.saxExceptions.push(Error.new(error));
}
error(error) {
this.saxExceptions.push(Error.new(error));
}
fatalError(error) {
console.error(error);
throw error;
}
}
class Parser {
/**
* constructor function
* @param {String} source
* @return {Document}
* @constructor
*/
parser(source) {
var handler = new DOMHandler();
var sax = new XMLReader(handler, handler, handler);
sax.parse(source);
return handler.document;
}
/**
* constructor function
* @param {Document} doc
* @param {Element} el
* @param {String} source
* @return {Document}
* @constructor
*/
fragment(doc, source, el) {
var handler = new DOMHandler(doc, el);
var sax = new XMLReader(handler, handler, handler);
sax.fragment(source);
return handler.document;
}
}
exports.Parser = Parser;