src/v5/OutputBuilders/JsObjBuilder.js
const {buildOptions,registerCommonValueParsers} = require("./ParserOptionsBuilder");
class OutputBuilder{
constructor(builderOptions){
this.options = buildOptions(builderOptions);
this.registeredParsers = registerCommonValueParsers();
}
registerValueParser(name,parserInstance){//existing name will override the parser without warning
this.registeredParsers[name] = parserInstance;
}
getInstance(parserOptions){
return new JsObjBuilder(parserOptions, this.options, this.registeredParsers);
}
}
const BaseOutputBuilder = require("./BaseOutputBuilder");
const rootName = '^';
class JsObjBuilder extends BaseOutputBuilder{
constructor(parserOptions, builderOptions,registeredParsers) {
super();
//hold the raw detail of a tag and sequence with reference to the output
this.tagsStack = [];
this.parserOptions = parserOptions;
this.options = builderOptions;
this.registeredParsers = registeredParsers;
this.root = {};
this.parent = this.root;
this.tagName = rootName;
this.value = {};
this.textValue = "";
this.attributes = {};
}
addTag(tag){
let value = "";
if( !isEmpty(this.attributes)){
value = {};
if(this.options.attributes.groupBy){
value[this.options.attributes.groupBy] = this.attributes;
}else{
value = this.attributes;
}
}
this.tagsStack.push([this.tagName, this.textValue, this.value]); //parent tag, parent text value, parent tag value (jsobj)
this.tagName = tag.name;
this.value = value;
this.textValue = "";
this.attributes = {};
}
/**
* Check if the node should be added by checking user's preference
* @param {Node} node
* @returns boolean: true if the node should not be added
*/
closeTag(){
const tagName = this.tagName;
let value = this.value;
let textValue = this.textValue;
//update tag text value
if(typeof value !== "object" && !Array.isArray(value)){
value = this.parseValue(textValue.trim(), this.options.tags.valueParsers);
}else if(textValue.length > 0){
value[this.options.nameFor.text] = this.parseValue(textValue.trim(), this.options.tags.valueParsers);
}
let resultTag= {
tagName: tagName,
value: value
};
if(this.options.onTagClose !== undefined){
//TODO TagPathMatcher
resultTag = this.options.onClose(tagName, value, this.textValue, new TagPathMatcher(this.tagsStack,node));
if(!resultTag) return;
}
//set parent node in scope
let arr = this.tagsStack.pop();
let parentTag = arr[2];
parentTag=this._addChildTo(resultTag.tagName, resultTag.value, parentTag);
this.tagName = arr[0];
this.textValue = arr[1];
this.value = parentTag;
}
_addChild(key, val){
if(typeof this.value === "string"){
this.value = { [this.options.nameFor.text] : this.value };
}
this._addChildTo(key, val, this.value);
// this.currentNode.leafType = false;
this.attributes = {};
}
_addChildTo(key, val, node){
if(typeof node === 'string') node = {};
if(!node[key]){
node[key] = val;
}else{ //Repeated
if(!Array.isArray(node[key])){ //but not stored as array
node[key] = [node[key]];
}
node[key].push(val);
}
return node;
}
/**
* Add text value child node
* @param {string} text
*/
addValue(text){
//TODO: use bytes join
if(this.textValue.length > 0) this.textValue += " " + text;
else this.textValue = text;
}
addPi(name){
let value = "";
if( !isEmpty(this.attributes)){
value = {};
if(this.options.attributes.groupBy){
value[this.options.attributes.groupBy] = this.attributes;
}else{
value = this.attributes;
}
}
this._addChild(name, value);
}
getOutput(){
return this.value;
}
}
function isEmpty(obj) {
return Object.keys(obj).length === 0;
}
module.exports = OutputBuilder;