oxe-cli
Version:
Oxe command line interface program for Oxe applications.
258 lines (188 loc) • 5.83 kB
JavaScript
// TODO need to test
const END_TAG = /^<\/([-A-Za-z0-9_]+)[^>]*>/;
const ATTRIBUTE = /([a-zA-Z_:][-a-zA-Z0-9_:.]*)(?:\s*=\s*(?:(?:"((?:\\.|[^"])*)")|(?:'((?:\\.|[^'])*)')|([^>\s]+)))?/g;
const START_TAG = /^<([-A-Za-z0-9_]+)((?:\s+[a-zA-Z_:][-a-zA-Z0-9_:.]*(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)>/;
module.exports = {
types: {
SPECIAL: [
'script','style'
],
CLOSE_SELF: [
'colgroup','dd','dt','li','options',
'p','td','tfoot','th','thead','tr'
],
EMPTY: [
'area','base','basefont','br','col','frame','hr','img','input',
'link','meta','param','embed','command','keygen','source','track','wbr'
],
FILL_ATTRIBUTES: [
'checked','compact','declare','defer','disabled','ismap',
'multiple','nohref','noresize','noshade','nowrap','readonly','selected'
],
INLINE: [
'abbr','acronym','applet','b','basefont','bdo','big','br','button',
'cite','code','del','dfn','em','font','i','iframe','img','input','ins',
'kbd','label','map','object','q','s','samp','script','select','small',
'span','strike','strong','sub','sup','textarea','tt','u','var'
],
BLOCK: [
'a','address','article','applet','aside','audio','blockquote','button','canvas',
'center','dd','del','dir','div','dl','dt','fieldset','figcaption','figure','footer',
'form','frameset','h1','h2','h3','h4','h5','h6','header','hgroup','hr','iframe','ins',
'isindex','li','map','menu','noframes','noscript','object','ol','output','p','pre','section',
'script','table','tbody','td','tfoot','th','thead','tr','ul','video','svg'
]
},
is (type, name) {
const self = this;
return self.types[type].indexOf(name) !== -1;
},
createTagStart (tag, attributes) {
const self = this;
let result = `<${tag}`;
for (let attribute of attributes) {
if (attribute.value) {
result += ` ${attribute.name}="${attribute.value}"`;
} else {
result += ` ${attribute.name}`;
}
}
result += `>`;
return result;
},
parseStartTag (data, tag, name, rest, unary) {
const self = this;
name = name.toLowerCase();
if (self.is('BLOCK', name)) {
while ( data.stack.last() && self.is('INLINE', data.stack.last()) ) {
self.parseEndTag(data, '', data.stack.last());
}
}
if ( self.is('CLOSE_SELF', name) && data.stack.last() === name ) {
self.parseEndTag(data, '', name);
}
unary = self.is('EMPTY', name) || !!unary;
if (!unary) {
data.stack.push(name);
}
if (data.start) {
var attributes = [];
rest.replace(ATTRIBUTE, function (match, name) {
var value = arguments[2] ? arguments[2] :
arguments[3] ? arguments[3] :
arguments[4] ? arguments[4] :
self.is('FILL_ATTRIBUTES', name) ? name : '';
attributes.push({
name: name,
value: value
// escaped: value.replace(/(^|[^\\])"/g, '$1\\\"')
});
});
if (data.start) {
data.start(name, attributes, unary);
}
}
},
parseEndTag (data, tag, name) {
const self = this;
let position;
// If no tag name is provided, clean shop
if (!name) {
position = 0;
} else {
// Find the closest opened tag of the same type
for (position = data.stack.length - 1; position >= 0; position--) {
if (data.stack[position] === name) {
break;
}
}
}
if (position >= 0) {
// Close all the open elements, up the stack
for (var i = data.stack.length - 1; i >= position; i--) {
if (data.end) {
data.end(data.stack[i]);
}
}
// Remove the open elements from the stack
data.stack.length = position;
}
},
parse (data) {
const self = this;
data.stack = [];
data.last = data.html;
if (data.html.indexOf('<!DOCTYPE html>') === 0) {
data.html = data.html.slice(15);
}
data.stack.last = function () {
return this[this.length-1];
};
while (data.html) {
let isChars = true;
let index, match;
// Make sure we are not in a script or style element
if ( !data.stack.last() || !self.is('SPECIAL', data.stack.last()) ) {
// Comment
if (data.html.indexOf('<!--') === 0) {
index = data.html.indexOf('-->');
if (index >= 0) {
if (data.comment) {
data.comment(data.html.substring(4, index));
}
data.html = data.html.substring(index + 3);
isChars = false;
}
// end tag
} else if (data.html.indexOf('</') === 0) {
match = data.html.match(END_TAG);
if (match) {
data.html = data.html.substring(match[0].length);
match[0].replace(END_TAG, self.parseEndTag.bind(self, data));
isChars = false;
}
// start tag
} else if (data.html.indexOf('<') === 0) {
match = data.html.match(START_TAG);
if (match) {
data.html = data.html.substring(match[0].length);
match[0].replace(START_TAG, self.parseStartTag.bind(self, data));
isChars = false;
}
}
if (isChars) {
index = data.html.indexOf('<');
var text = index < 0 ? data.html : data.html.substring(0, index);
data.html = index < 0 ? '' : data.html.substring(index);
if (data.chars) {
data.chars(text);
}
}
} else {
var pattern = new RegExp(`([\\s\\S]*?)<\/${data.stack.last()}[^>]*>`);
data.html = data.html.replace(pattern, function (all, text) {
text = text.replace(/<!--([\s\S]*?)-->|<!\[CDATA\[([\s\S]*?)]]>/g, '$1$2');
if (data.chars) {
data.chars(text);
}
return '';
});
self.parseEndTag(data, '', data.stack.last());
}
if (data.html === data.last) {
throw `Parse Error: ${html}`;
}
data.last = data.html;
}
// Clean up any remaining tags
self.parseEndTag(data);
}
};
/*
Parser.html(string, {
start: function (tag, attributes, unary) {},
end: function (tag) {},
chars: function (text) {},
comment: function (text) {}
});
*/