<commit_before>/**
#-------------------------------------------------------------------------------
# Copyright 2011 The fangorn project
# 
#        Author: <NAME>
# 
#        Licensed to the Apache Software Foundation (ASF) under one
#        or more contributor license agreements.  See the NOTICE file
#        distributed with this work for additional information
#        regarding copyright ownership.  The ASF licenses this file
#        to you under the Apache License, Version 2.0 (the
#        "License"); you may not use this file except in compliance
#        with the License.  You may obtain a copy of the License at
# 
#          http://www.apache.org/licenses/LICENSE-2.0
# 
#        Unless required by applicable law or agreed to in writing,
#        software distributed under the License is distributed on an
#        "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
#        KIND, either express or implied.  See the License for the
#        specific language governing permissions and limitations
#        under the License.
#-------------------------------------------------------------------------------
**
 * This QueryTree class is capable of rendering json objects as
 * expandable-collapsible SVG trees
 * @author <NAME>
 * 
 * @param data
 *            json object
 * @param objid
 *            id of the object wrapper
 * @param varname
 *            name of the javascript variable which holds the reference to a tree instance; used to generate callbacks in html
 * @return
 */

function QueryTree(data, varname, sentnum, queryDomId, treeDomId, matchedPairs, queryString) {
	this.lastXOffset = 0;
	this.tree = data; // the actual tree
	this.varname = varname; // used to fire callbacks from dom elements
	this.sentnum = sentnum;
	this.matches = new QueryTree.prototype.Matches(matchedPairs);
	this.queryDomId = queryDomId;
	this.treeDomId = treeDomId;

	QueryTree.prototype.XSPACE = 15;
	QueryTree.prototype.YSPACE = 45;
	QueryTree.prototype.TEXT_HEIGHT = 12;
	QueryTree.prototype.WIDTH_OF_CHAR = 9;
	QueryTree.prototype.WIDTH_OF_NARROW_CHAR = 7;
	QueryTree.prototype.YPADDING = 2;
	QueryTree.prototype.TRIANGLE_OFFSET = 2;
	QueryTree.prototype.TRIANGLE_HEIGHT = 18;
	QueryTree.prototype.TRIANGLE_WIDTH_2 = 5;

	QueryTree.prototype.SVG_PADDING = 12;
	
	QueryTree.prototype.LINE_COLOUR = "rgb(150,150,150)";
	QueryTree.prototype.HIGHLIGHT_TEXT_COLOUR = "rgb(34,139,34)";
	QueryTree.prototype.RED_LINE_COLOUR = "rgb(200,0,0)";
	QueryTree.prototype.BLUE_LINE_COLOUR = "rgb(0,0,200)";
	QueryTree.prototype.narrowRegex = /[a-z0-9]/g;

	QueryTree.prototype.AXIS_OPRS = [{'name':'descendant', 'sym':'//'}];
	QueryTree.prototype.AXIS_OPRS.push({'name':'ancestor', 'sym':'\\\\'});
	QueryTree.prototype.AXIS_OPRS.push({'name':'child', 'sym':'/'});
	QueryTree.prototype.AXIS_OPRS.push({'name':'parent', 'sym':'\\'});
	QueryTree.prototype.AXIS_OPRS.push({'name':'following-sibling', 'sym':'==>'});
	QueryTree.prototype.AXIS_OPRS.push({'name':'preceding-sibling', 'sym':'<=='});
	QueryTree.prototype.AXIS_OPRS.push({'name':'immediate-following-sibling', 'sym':'=>'});
	QueryTree.prototype.AXIS_OPRS.push({'name':'immediate-preceding-sibling', 'sym':'<='}); 
	QueryTree.prototype.AXIS_OPRS.push({'name':'following', 'sym':'-->'});
	QueryTree.prototype.AXIS_OPRS.push({'name':'preceding', 'sym':'<--'});
	QueryTree.prototype.AXIS_OPRS.push({'name':'immediate-following', 'sym':'->'});
	QueryTree.prototype.AXIS_OPRS.push({'name':'immediate-preceding', 'sym':'<-'});

	this.setParentOnTreeNode(this.tree, null);
	this.root = this.buildQueryTree(queryString);
	this.treeNodeByPosition = {};
	this.indexByPosition(this.tree);
	this.selectedState = this.noneSelected;
}

String.prototype.trim = function() {
	return this.toString().replace(/^ +/, '').replace(/ +$/, '');
}

QueryTree.prototype.indexByPosition = function(node) {
	this.treeNodeByPosition[node["i"]] = node;
	for ( var i = 0; i < node["c"].length; i++) {
		this.indexByPosition(node["c"][i]);
	}
}

QueryTree.prototype.noneSelected = function() {
	this.mouseoverNode = function() {
	},
	this.mouseoutNode = function() {
	};
}

QueryTree.prototype.highlightedSelected = function() {
	this.mouseoverNode = function() {

	},
	this.mouseoutNode = function() {

	};
}

QueryTree.prototype.noMatchesNoneSelected = function() {
	
}

QueryTree.prototype.setParentOnTreeNode = function(node, parent) {
	node["p"] = parent;
	for ( var i = 0; i < node["c"].length; i++) {
		this.setParentOnTreeNode(node["c"][i], node);
	}
}

QueryTree.prototype.draw = function() {
	this.drawQuery();
	this.calcPositionAndDrawSVG();
}

QueryTree.prototype.drawQuery = function() {
	var obj = document.getElementById(this.queryDomId);
	this.clearDom(obj);
	this.root.addToDom(obj);
}

QueryTree.prototype.calcPositionAndDrawSVG = function() {
	var obj = document.getElementById(this.treeDomId);
	this.addXAndYPositions();
	var svg = this.toSVG(this.treeDomId);
	this.clearDom(obj);
	var xy = {x:0, y:0};
	xy = this.maxXY(this.tree, xy);
	obj.setAttribute("width", xy.x + 2 * QueryTree.prototype.SVG_PADDING);
	obj.setAttribute("height", xy.y + QueryTree.prototype.SVG_PADDING);
	obj.appendChild(svg);
};

QueryTree.prototype.clearDom = function(obj) {
	if (obj.childNodes.length > 0) {
		while (obj.childNodes.length > 0) {
			obj.removeChild(obj.firstChild);
		}
	}
}

QueryTree.prototype.addXAndYPositions = function() {
	this.lastXOffset = 0;
	this.recAddXAndYPositions(this.tree);
}

QueryTree.prototype.recAddXAndYPositions = function(t) {
	var c = t["c"];
	var narrow = this.mainlyNarrowChars(t["n"]);
	var lengthOfTLabel = t["n"].length * ((narrow) ? QueryTree.prototype.WIDTH_OF_NARROW_CHAR : QueryTree.prototype.WIDTH_OF_CHAR);
	t["s"] = (narrow) ? true : false; 
	if (t["e"] && c.length > 0) {
		for ( var i = 0; i < c.length; i++) {
			this.recAddXAndYPositions(c[i]);
		}
		mid = (c[0]["x"] + c[0]["x-end"] + c[c.length - 1]["x"] + c[c.length - 1]["x-end"]) / 4;
		t["x"] = mid - (lengthOfTLabel / 2);
		if (t["x"] < c[0]["x"] || t["x"] < 0) {
			var dist = (t["x"] < 0) ? 0 - t["x"] : c[0]["x"] - t["x"];
			for (var i = 0; i < c.length; i++) {
				this.recShiftX(c[i], dist);
			}
			t["x"] = t["x"] + dist;
		}
	} else {
		t["x"] = this.lastXOffset + QueryTree.prototype.XSPACE;
	}
	var newXOffset = t["x"] + lengthOfTLabel;
	if (newXOffset > this.lastXOffset) {
		this.lastXOffset = newXOffset;
	}
	t["x-end"] = t["x"] + lengthOfTLabel;
	t["y"] = t["depth"] * QueryTree.prototype.YSPACE;
}

QueryTree.prototype.recShiftX = function(t, x) {
	t["x"] = t["x"] + x;
	t["x-end"] = t["x-end"] + x;
	if (t["e"] && t["c"].length > 0) {
		for (var i = 0; i < t["c"].length; i++) {
			this.recShiftX(t["c"][i], x);
		}
	}
}

QueryTree.prototype.mainlyNarrowChars = function(name) {
	var narrowChars = name.match(QueryTree.prototype.narrowRegex);
	if (narrowChars == null) return false;
	if (narrowChars.length >= name.length / 2) return true;
	return false;
}

QueryTree.prototype.toSVG = function(objid) {
	var svg = document.createElementNS('http://www.w3.org/2000/svg',
			'svg:svg');
	this.recToSVG(svg, objid, this.tree);
	this.drawHighlightPaths(svg)
	var m = this.maxXY(this.tree, {x:0, y:0});
	svg.setAttribute("width", m.x + 2 * QueryTree.prototype.SVG_PADDING);
	svg.setAttribute("height", m.y + QueryTree.prototype.SVG_PADDING);
	return svg;
}

QueryTree.prototype.drawHighlightPaths = function(svgElement) {
	for ( var i = 0; i < this.matches.pairs.length; i++) {
		var p = this.matches.pairs[i];
		if (p["s"] == "") { continue; }
		/**
		 * The numerical comparisons are based on TreeAxis's constants
		 */
		var opr = p["o"];
		var singleLine = (opr > 3 && opr < 8) ? false : true;
		var dashedLine = (opr == 0 || opr == 1 || opr == 4 || opr == 5 || opr == 8 || opr == 9) ? true : false;
		var colour = (opr < 4) ? QueryTree.prototype.BLUE_LINE_COLOUR : QueryTree.prototype.RED_LINE_COLOUR;
		var lineStyle = "stroke:" + colour + ";stroke-width:2;" + ((dashedLine) ? "stroke-dasharray:9,5;" : "");
		var transStyle = "stroke-width:9;stroke-opacity:0.001;stroke:white;";//used to create a near transparent line to help click on the line
		//All the preceding axis codes are odd numbers 
		var b = this.treeNodeByPosition[(opr % 2 == 1) ? p["e"] : p["s"]];
		var a = this.treeNodeByPosition[(opr % 2 == 1) ? p["s"] : p["e"]];
		var width = (b["s"]) ? QueryTree.prototype.WIDTH_OF_NARROW_CHAR : QueryTree.prototype.WIDTH_OF_CHAR;
		if (opr < 4) { // vertical lines
			var x1 = b["x"] + (b["n"].length * width) / 2;
			var y1 = b["y"] + 2 * QueryTree.prototype.YPADDING
			width = (a["s"]) ? QueryTree.prototype.WIDTH_OF_NARROW_CHAR : QueryTree.prototype.WIDTH_OF_CHAR;
			var x2 = a["x"] + (a["n"].length * width) / 2;
			var y2 = a["y"] - QueryTree.prototype.TEXT_HEIGHT - QueryTree.prototype.YPADDING;
			svgElement.appendChild(this.getClickableSVGLine(x1, y1, x2, y2, transStyle, i, 0));
			svgElement.appendChild(this.getClickableSVGLine(x1, y1, x2, y2, lineStyle, i, 1));
		} else { // horizontal lines
			var x1 = b["x"] + (b["n"].length * width) + 5;
			var y1 = b["y"] - QueryTree.prototype.TEXT_HEIGHT / 2 + 1;
			var x2 = a["x"] - 5;
			var y2 = a["y"] - QueryTree.prototype.TEXT_HEIGHT / 2 + 1;
			if (singleLine) {
				svgElement.appendChild(this.getClickableSVGLine(x1, y1, x2, y2, transStyle, i, 0));
				svgElement.appendChild(this.getClickableSVGLine(x1, y1, x2, y2, lineStyle, i, 1));
			} else {
				svgElement.appendChild(this.getClickableSVGLine(x1, y1, x2, y2, transStyle, i, 0));
				svgElement.appendChild(this.getClickableSVGLine(x1, y1 - 2, x2, y2 - 2, lineStyle, i, 1));
				svgElement.appendChild(this.getClickableSVGLine(x1, y1 + 2, x2, y2 + 2, lineStyle, i, 2));
			}
		}
	}
}

QueryTree.prototype.getSVGLine = function(x1, y1, x2, y2, style) {
	var lineElem = document.createElementNS('http://www.w3.org/2000/svg', 'svg:line');
	lineElem.setAttribute("x1", x1 + "px");
	lineElem.setAttribute("y1", y1 + "px");
	lineElem.setAttribute("x2", x2 + "px");
	lineElem.setAttribute("y2", y2 + "px");
	lineElem.setAttribute("style", style);
	return lineElem;
}

QueryTree.prototype.getClickableSVGLine = function(x1, y1, x2, y2, style, matchPos, lineNum) {
	var line = this.getSVGLine(x1, y1, x2, y2, style);
	line.setAttribute("id", "t_" + matchPos + "_" + lineNum);
	line.setAttribute("onmouseover", this.varname + ".mouseoverLine('" + matchPos + "');");
	line.setAttribute("onmouseout", this.varname + ".mouseoutLine('" + matchPos + "');");
	line.setAttribute("onclick", this.varname + ".clickLine('" + matchPos + "');");
	return line;
}

QueryTree.prototype.getNodeDomId = function(position) {
	return this.sentnum + "_" + this.treeNodeByPosition[position]["i"];
}

QueryTree.prototype.recToSVG = function(svgElement, objid, t) {
	//this function is called for each displayed node
	var textElem = document.createElementNS('http://www.w3.org/2000/svg', 'svg:text');
	textElem.appendChild(document.createTextNode(t["n"]));
	textElem.setAttribute("x", t["x"] + "px");
	textElem.setAttribute("y", t["y"] + "px");
	width = (t["s"]) ? QueryTree.prototype.WIDTH_OF_NARROW_CHAR : QueryTree.prototype.WIDTH_OF_CHAR;
	var x1 = t["x"] + (t["n"].length * width) / 2;
	var y1 = t["y"] + QueryTree.prototype.YPADDING;
	textElem.setAttribute("id", this.getNodeDomId(t["i"]));
	var mouseoverFunc = this.varname + ".mouseoverNode('" + t["i"] + "', " + ((t["h"]) ? "false" : "true") + ");";
	var mouseoutFunc = this.varname + ".mouseoutNode('" + t["i"] + "', " + ((t["h"]) ? "false" : "true") + ");";
	textElem.setAttribute("onmouseover", mouseoverFunc);
	textElem.setAttribute("onmouseout", mouseoutFunc);			
	if (!t["disc"]) {
		textElem.setAttribute("onclick", this.varname + ".clickNode('" + t["i"] + "');");
	}
	if (t["h"]) {
		textElem.setAttribute("style",
				"stroke:" + QueryTree.prototype.HIGHLIGHT_TEXT_COLOUR + ";fill:" + QueryTree.prototype.HIGHLIGHT_TEXT_COLOUR + ";");
	}
	svgElement.appendChild(textElem);
	if (t["e"] && t["c"].length > 0) { //draw black lines to all child nodes
		var lineStyle = "stroke:" + QueryTree.prototype.LINE_COLOUR + ";stroke-width:1";
		for ( var i = 0; i < t["c"].length; i++) {
			width = (t["c"][i]["s"]) ?  QueryTree.prototype.WIDTH_OF_NARROW_CHAR : QueryTree.prototype.WIDTH_OF_CHAR;
			var x2 = t["c"][i]["x"] + (t["c"][i]["n"].length * width) / 2;
			var y2 = t["c"][i]["y"] - QueryTree.prototype.TEXT_HEIGHT - QueryTree.prototype.YPADDING;
			svgElement.appendChild(this.getSVGLine(x1, y1 + QueryTree.prototype.YPADDING, x2, y2, lineStyle));
			this.recToSVG(svgElement, objid, t["c"][i]);
		}
	}
	if (!t["e"] && t["c"].length > 0) { //draw a triangle below this node
		var x11 = x1 - QueryTree.prototype.TRIANGLE_OFFSET;
		var x2 = x1 - QueryTree.prototype.TRIANGLE_WIDTH_2;
		var x3 = x1 + QueryTree.prototype.TRIANGLE_WIDTH_2;
		var y2 = y1 + QueryTree.prototype.TRIANGLE_OFFSET	+ QueryTree.prototype.TRIANGLE_HEIGHT;
		var pg = document.createElementNS('http://www.w3.org/2000/svg', 'svg:polygon');
		var points_str = (x1) + "," + (y1 + 2) + " " + (x2) + "," + (y2) + " " + (x3) + "," + (y2);
		pg.setAttribute("points", points_str);
		pg.setAttribute("onmouseover", this.varname + ".mouseoverNode('" + t["i"] + "', true);");
		pg.setAttribute("onmouseout", this.varname + ".mouseoutNode('" + t["i"] + "', true);");
		pg.setAttribute("onclick", this.varname + ".expandNode('" + t["i"] + "');");
		pg.setAttribute("style", "stroke:" + QueryTree.prototype.LINE_COLOUR + ";stroke-width:2;fill:white;cursor:pointer;");
		svgElement.appendChild(pg);
	}
}

QueryTree.prototype.maxXY = function(nod, xyv) {
	var maxx = xyv.x;
	if (nod["x-end"] > maxx) { maxx = nod["x-end"]; }
	var maxy = xyv.y;
	if (nod["y"] > maxy) { maxy = nod["y"]; }
	if (nod["e"] && nod["c"].length > 0) { //find max XY based on children's max
		for ( var i = 0; i < nod["c"].length; i++) {
			maxXYc = this.maxXY(nod["c"][i], {x:maxx, y:maxy});
			if (maxXYc.x > maxx) { maxx = maxXYc.x; }
			if (maxXYc.y > maxy) { maxy = maxXYc.y; }
		}
	} else if (!nod["e"] && nod["c"].length > 0) { //adding triangle height
		//shouldn't it be added only when maxy is already at max at this level?
		maxy = maxy + QueryTree.prototype.YPADDING	+ QueryTree.prototype.TRIANGLE_OFFSET + QueryTree.prototype.TRIANGLE_HEIGHT;
	}
	return {x:maxx, y:maxy};
}

QueryTree.prototype.mouseoverNode = function(pos, clickable) {
	var n = document.getElementById(this.getNodeDomId(pos));
	var te = this.treeNodeByPosition[pos];
	n.setAttribute('font-weight', 'bold');
	n.setAttribute('cursor', 'pointer');
	var qn = document.getElementById("q_" + pos);
	if (qn) { qn.setAttribute("class", "highlight"); }
}

QueryTree.prototype.mouseoutNode = function(pos, clickable) {
	var n = document.getElementById(this.getNodeDomId(pos));
	var te = this.treeNodeByPosition[pos];
	n.removeAttribute('font-weight');
	n.removeAttribute('cursor');
	var qn = document.getElementById("q_" + pos);
	if (qn) { qn.removeAttribute("class"); }
}

QueryTree.prototype.clickNode = function(pos) {
	var n = document.getElementById(this.getNodeDomId(pos));
	var te = this.treeNodeByPosition[pos];

}

QueryTree.prototype.expandNode = function(pos) {
	var n = document.getElementById(this.getNodeDomId(pos));
	var te = this.treeNodeByPosition[pos];
	if (!te["disc"] && te["c"].length > 0) {
		te["e"] = te["e"] ^ true;
		this.calcPositionAndDrawSVG();
	}
}

QueryTree.prototype.mouseoverLine = function(matchNum) {
	var l0 = document.getElementById("t_" + matchNum + "_0");
	var l1 = document.getElementById("t_" + matchNum + "_1");
	var l2 = document.getElementById("t_" + matchNum + "_2");
	if (l0) { l0.setAttribute('cursor', 'pointer');}
	if (l1) { l1.style.strokeWidth = 3; l1.setAttribute('cursor', 'pointer');}
	if (l2) { l2.style.strokeWidth = 3; l2.setAttribute('cursor', 'pointer');}
	var qn = document.getElementById("q_" + matchNum);
	if (qn) { qn.setAttribute("class", "highlight"); }
}

QueryTree.prototype.mouseoutLine = function(matchNum) {
	var l0 = document.getElementById("t_" + matchNum + "_0");
	var l1 = document.getElementById("t_" + matchNum + "_1");
	var l2 = document.getElementById("t_" + matchNum + "_2");
	if (l0) { l0.removeAttribute('cursor'); }
	if (l1) { l1.style.strokeWidth = 2; l1.removeAttribute('cursor'); }
	if (l2) { l2.style.strokeWidth = 2; l2.removeAttribute('cursor'); }
	var qn = document.getElementById("q_" + matchNum);
	if (qn) { qn.removeAttribute("class"); }
}

QueryTree.prototype.clickLine = function(matchNum) {
	var p = this.matches.pairs[matchNum];
	if (p["o"] > 4) {
}

function Node(type) {
	this.type = type;
	this.children = [];
	this.getPreString = function() {return ""};
	this.getPostString = function() {return ""};
	this.toString = function() {
		var str = this.getPreString();
		for (var i = 0; i < this.children.length; i++) {
			str += this.children[i].toString();
		}
		str += this.getPostString();<commit_msg>minor js fix<commit_after>/**
#-------------------------------------------------------------------------------
# Copyright 2011 The fangorn project
# 
#        Author: <NAME>
# 
#        Licensed to the Apache Software Foundation (ASF) under one
#        or more contributor license agreements.  See the NOTICE file
#        distributed with this work for additional information
#        regarding copyright ownership.  The ASF licenses this file
#        to you under the Apache License, Version 2.0 (the
#        "License"); you may not use this file except in compliance
#        with the License.  You may obtain a copy of the License at
# 
#          http://www.apache.org/licenses/LICENSE-2.0
# 
#        Unless required by applicable law or agreed to in writing,
#        software distributed under the License is distributed on an
#        "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
#        KIND, either express or implied.  See the License for the
#        specific language governing permissions and limitations
#        under the License.
#-------------------------------------------------------------------------------
**
 * This QueryTree class is capable of rendering json objects as
 * expandable-collapsible SVG trees
 * @author <NAME>
 * 
 * @param data
 *            json object
 * @param objid
 *            id of the object wrapper
 * @param varname
 *            name of the javascript variable which holds the reference to a tree instance; used to generate callbacks in html
 * @return
 */

function QueryTree(data, varname, sentnum, queryDomId, treeDomId, matchedPairs, queryString) {
	this.lastXOffset = 0;
	this.tree = data; // the actual tree
	this.varname = varname; // used to fire callbacks from dom elements
	this.sentnum = sentnum;
	this.matches = new QueryTree.prototype.Matches(matchedPairs);
	this.queryDomId = queryDomId;
	this.treeDomId = treeDomId;

	QueryTree.prototype.XSPACE = 15;
	QueryTree.prototype.YSPACE = 45;
	QueryTree.prototype.TEXT_HEIGHT = 12;
	QueryTree.prototype.WIDTH_OF_CHAR = 9;
	QueryTree.prototype.WIDTH_OF_NARROW_CHAR = 7;
	QueryTree.prototype.YPADDING = 2;
	QueryTree.prototype.TRIANGLE_OFFSET = 2;
	QueryTree.prototype.TRIANGLE_HEIGHT = 18;
	QueryTree.prototype.TRIANGLE_WIDTH_2 = 5;

	QueryTree.prototype.SVG_PADDING = 12;
	
	QueryTree.prototype.LINE_COLOUR = "rgb(150,150,150)";
	QueryTree.prototype.HIGHLIGHT_TEXT_COLOUR = "rgb(34,139,34)";
	QueryTree.prototype.RED_LINE_COLOUR = "rgb(200,0,0)";
	QueryTree.prototype.BLUE_LINE_COLOUR = "rgb(0,0,200)";
	QueryTree.prototype.narrowRegex = /[a-z0-9]/g;

	QueryTree.prototype.AXIS_OPRS = [{'name':'descendant', 'sym':'//'}];
	QueryTree.prototype.AXIS_OPRS.push({'name':'ancestor', 'sym':'\\\\'});
	QueryTree.prototype.AXIS_OPRS.push({'name':'child', 'sym':'/'});
	QueryTree.prototype.AXIS_OPRS.push({'name':'parent', 'sym':'\\'});
	QueryTree.prototype.AXIS_OPRS.push({'name':'following-sibling', 'sym':'==>'});
	QueryTree.prototype.AXIS_OPRS.push({'name':'preceding-sibling', 'sym':'<=='});
	QueryTree.prototype.AXIS_OPRS.push({'name':'immediate-following-sibling', 'sym':'=>'});
	QueryTree.prototype.AXIS_OPRS.push({'name':'immediate-preceding-sibling', 'sym':'<='}); 
	QueryTree.prototype.AXIS_OPRS.push({'name':'following', 'sym':'-->'});
	QueryTree.prototype.AXIS_OPRS.push({'name':'preceding', 'sym':'<--'});
	QueryTree.prototype.AXIS_OPRS.push({'name':'immediate-following', 'sym':'->'});
	QueryTree.prototype.AXIS_OPRS.push({'name':'immediate-preceding', 'sym':'<-'});

	this.setParentOnTreeNode(this.tree, null);
	this.root = this.buildQueryTree(queryString);
	this.treeNodeByPosition = {};
	this.indexByPosition(this.tree);
	this.selectedState = this.noneSelected;
}

String.prototype.trim = function() {
	return this.toString().replace(/^ +/, '').replace(/ +$/, '');
}

QueryTree.prototype.indexByPosition = function(node) {
	this.treeNodeByPosition[node["i"]] = node;
	for ( var i = 0; i < node["c"].length; i++) {
		this.indexByPosition(node["c"][i]);
	}
}

QueryTree.prototype.noneSelected = function() {
	this.mouseoverNode = function() {
	},
	this.mouseoutNode = function() {
	};
}

QueryTree.prototype.highlightedSelected = function() {
	this.mouseoverNode = function() {

	},
	this.mouseoutNode = function() {

	};
}

QueryTree.prototype.noMatchesNoneSelected = function() {
	
}

QueryTree.prototype.setParentOnTreeNode = function(node, parent) {
	node["p"] = parent;
	for ( var i = 0; i < node["c"].length; i++) {
		this.setParentOnTreeNode(node["c"][i], node);
	}
}

QueryTree.prototype.draw = function() {
	this.drawQuery();
	this.calcPositionAndDrawSVG();
}

QueryTree.prototype.drawQuery = function() {
	var obj = document.getElementById(this.queryDomId);
	this.clearDom(obj);
	this.root.addToDom(obj);
}

QueryTree.prototype.calcPositionAndDrawSVG = function() {
	var obj = document.getElementById(this.treeDomId);
	this.addXAndYPositions();
	var svg = this.toSVG(this.treeDomId);
	this.clearDom(obj);
	var xy = {x:0, y:0};
	xy = this.maxXY(this.tree, xy);
	obj.setAttribute("width", xy.x + 2 * QueryTree.prototype.SVG_PADDING);
	obj.setAttribute("height", xy.y + QueryTree.prototype.SVG_PADDING);
	obj.appendChild(svg);
};

QueryTree.prototype.clearDom = function(obj) {
	if (obj.childNodes.length > 0) {
		while (obj.childNodes.length > 0) {
			obj.removeChild(obj.firstChild);
		}
	}
}

QueryTree.prototype.addXAndYPositions = function() {
	this.lastXOffset = 0;
	this.recAddXAndYPositions(this.tree);
}

QueryTree.prototype.recAddXAndYPositions = function(t) {
	var c = t["c"];
	var narrow = this.mainlyNarrowChars(t["n"]);
	var lengthOfTLabel = t["n"].length * ((narrow) ? QueryTree.prototype.WIDTH_OF_NARROW_CHAR : QueryTree.prototype.WIDTH_OF_CHAR);
	t["s"] = (narrow) ? true : false; 
	if (t["e"] && c.length > 0) {
		for ( var i = 0; i < c.length; i++) {
			this.recAddXAndYPositions(c[i]);
		}
		mid = (c[0]["x"] + c[0]["x-end"] + c[c.length - 1]["x"] + c[c.length - 1]["x-end"]) / 4;
		t["x"] = mid - (lengthOfTLabel / 2);
		if (t["x"] < c[0]["x"] || t["x"] < 0) {
			var dist = (t["x"] < 0) ? 0 - t["x"] : c[0]["x"] - t["x"];
			for (var i = 0; i < c.length; i++) {
				this.recShiftX(c[i], dist);
			}
			t["x"] = t["x"] + dist;
		}
	} else {
		t["x"] = this.lastXOffset + QueryTree.prototype.XSPACE;
	}
	var newXOffset = t["x"] + lengthOfTLabel;
	if (newXOffset > this.lastXOffset) {
		this.lastXOffset = newXOffset;
	}
	t["x-end"] = t["x"] + lengthOfTLabel;
	t["y"] = t["depth"] * QueryTree.prototype.YSPACE;
}

QueryTree.prototype.recShiftX = function(t, x) {
	t["x"] = t["x"] + x;
	t["x-end"] = t["x-end"] + x;
	if (t["e"] && t["c"].length > 0) {
		for (var i = 0; i < t["c"].length; i++) {
			this.recShiftX(t["c"][i], x);
		}
	}
}

QueryTree.prototype.mainlyNarrowChars = function(name) {
	var narrowChars = name.match(QueryTree.prototype.narrowRegex);
	if (narrowChars == null) return false;
	if (narrowChars.length >= name.length / 2) return true;
	return false;
}

QueryTree.prototype.toSVG = function(objid) {
	var svg = document.createElementNS('http://www.w3.org/2000/svg',
			'svg:svg');
	this.recToSVG(svg, objid, this.tree);
	this.drawHighlightPaths(svg)
	var m = this.maxXY(this.tree, {x:0, y:0});
	svg.setAttribute("width", m.x + 2 * QueryTree.prototype.SVG_PADDING);
	svg.setAttribute("height", m.y + QueryTree.prototype.SVG_PADDING);
	return svg;
}

QueryTree.prototype.drawHighlightPaths = function(svgElement) {
	for ( var i = 0; i < this.matches.pairs.length; i++) {
		var p = this.matches.pairs[i];
		if (p["s"] == "") { continue; }
		/**
		 * The numerical comparisons are based on TreeAxis's constants
		 */
		var opr = p["o"];
		var singleLine = (opr > 3 && opr < 8) ? false : true;
		var dashedLine = (opr == 0 || opr == 1 || opr == 4 || opr == 5 || opr == 8 || opr == 9) ? true : false;
		var colour = (opr < 4) ? QueryTree.prototype.BLUE_LINE_COLOUR : QueryTree.prototype.RED_LINE_COLOUR;
		var lineStyle = "stroke:" + colour + ";stroke-width:2;" + ((dashedLine) ? "stroke-dasharray:9,5;" : "");
		var transStyle = "stroke-width:9;stroke-opacity:0.001;stroke:white;";//used to create a near transparent line to help click on the line
		//All the preceding axis codes are odd numbers 
		var b = this.treeNodeByPosition[(opr % 2 == 1) ? p["e"] : p["s"]];
		var a = this.treeNodeByPosition[(opr % 2 == 1) ? p["s"] : p["e"]];
		var width = (b["s"]) ? QueryTree.prototype.WIDTH_OF_NARROW_CHAR : QueryTree.prototype.WIDTH_OF_CHAR;
		if (opr < 4) { // vertical lines
			var x1 = b["x"] + (b["n"].length * width) / 2;
			var y1 = b["y"] + 2 * QueryTree.prototype.YPADDING
			width = (a["s"]) ? QueryTree.prototype.WIDTH_OF_NARROW_CHAR : QueryTree.prototype.WIDTH_OF_CHAR;
			var x2 = a["x"] + (a["n"].length * width) / 2;
			var y2 = a["y"] - QueryTree.prototype.TEXT_HEIGHT - QueryTree.prototype.YPADDING;
			svgElement.appendChild(this.getClickableSVGLine(x1, y1, x2, y2, transStyle, i, 0));
			svgElement.appendChild(this.getClickableSVGLine(x1, y1, x2, y2, lineStyle, i, 1));
		} else { // horizontal lines
			var x1 = b["x"] + (b["n"].length * width) + 5;
			var y1 = b["y"] - QueryTree.prototype.TEXT_HEIGHT / 2 + 1;
			var x2 = a["x"] - 5;
			var y2 = a["y"] - QueryTree.prototype.TEXT_HEIGHT / 2 + 1;
			if (singleLine) {
				svgElement.appendChild(this.getClickableSVGLine(x1, y1, x2, y2, transStyle, i, 0));
				svgElement.appendChild(this.getClickableSVGLine(x1, y1, x2, y2, lineStyle, i, 1));
			} else {
				svgElement.appendChild(this.getClickableSVGLine(x1, y1, x2, y2, transStyle, i, 0));
				svgElement.appendChild(this.getClickableSVGLine(x1, y1 - 2, x2, y2 - 2, lineStyle, i, 1));
				svgElement.appendChild(this.getClickableSVGLine(x1, y1 + 2, x2, y2 + 2, lineStyle, i, 2));
			}
		}
	}
}

QueryTree.prototype.getSVGLine = function(x1, y1, x2, y2, style) {
	var lineElem = document.createElementNS('http://www.w3.org/2000/svg', 'svg:line');
	lineElem.setAttribute("x1", x1 + "px");
	lineElem.setAttribute("y1", y1 + "px");
	lineElem.setAttribute("x2", x2 + "px");
	lineElem.setAttribute("y2", y2 + "px");
	lineElem.setAttribute("style", style);
	return lineElem;
}

QueryTree.prototype.getClickableSVGLine = function(x1, y1, x2, y2, style, matchPos, lineNum) {
	var line = this.getSVGLine(x1, y1, x2, y2, style);
	line.setAttribute("id", "t_" + matchPos + "_" + lineNum);
	line.setAttribute("onmouseover", this.varname + ".mouseoverLine('" + matchPos + "');");
	line.setAttribute("onmouseout", this.varname + ".mouseoutLine('" + matchPos + "');");
	line.setAttribute("onclick", this.varname + ".clickLine('" + matchPos + "');");
	return line;
}

QueryTree.prototype.getNodeDomId = function(position) {
	return this.sentnum + "_" + this.treeNodeByPosition[position]["i"];
}

QueryTree.prototype.recToSVG = function(svgElement, objid, t) {
	//this function is called for each displayed node
	var textElem = document.createElementNS('http://www.w3.org/2000/svg', 'svg:text');
	textElem.appendChild(document.createTextNode(t["n"]));
	textElem.setAttribute("x", t["x"] + "px");
	textElem.setAttribute("y", t["y"] + "px");
	width = (t["s"]) ? QueryTree.prototype.WIDTH_OF_NARROW_CHAR : QueryTree.prototype.WIDTH_OF_CHAR;
	var x1 = t["x"] + (t["n"].length * width) / 2;
	var y1 = t["y"] + QueryTree.prototype.YPADDING;
	textElem.setAttribute("id", this.getNodeDomId(t["i"]));
	var mouseoverFunc = this.varname + ".mouseoverNode('" + t["i"] + "', " + ((t["h"]) ? "false" : "true") + ");";
	var mouseoutFunc = this.varname + ".mouseoutNode('" + t["i"] + "', " + ((t["h"]) ? "false" : "true") + ");";
	textElem.setAttribute("onmouseover", mouseoverFunc);
	textElem.setAttribute("onmouseout", mouseoutFunc);			
	if (!t["disc"]) {
		textElem.setAttribute("onclick", this.varname + ".clickNode('" + t["i"] + "');");
	}
	if (t["h"]) {
		textElem.setAttribute("style",
				"stroke:" + QueryTree.prototype.HIGHLIGHT_TEXT_COLOUR + ";fill:" + QueryTree.prototype.HIGHLIGHT_TEXT_COLOUR + ";");
	}
	svgElement.appendChild(textElem);
	if (t["e"] && t["c"].length > 0) { //draw black lines to all child nodes
		var lineStyle = "stroke:" + QueryTree.prototype.LINE_COLOUR + ";stroke-width:1";
		for ( var i = 0; i < t["c"].length; i++) {
			width = (t["c"][i]["s"]) ?  QueryTree.prototype.WIDTH_OF_NARROW_CHAR : QueryTree.prototype.WIDTH_OF_CHAR;
			var x2 = t["c"][i]["x"] + (t["c"][i]["n"].length * width) / 2;
			var y2 = t["c"][i]["y"] - QueryTree.prototype.TEXT_HEIGHT - QueryTree.prototype.YPADDING;
			svgElement.appendChild(this.getSVGLine(x1, y1 + QueryTree.prototype.YPADDING, x2, y2, lineStyle));
			this.recToSVG(svgElement, objid, t["c"][i]);
		}
	}
	if (!t["e"] && t["c"].length > 0) { //draw a triangle below this node
		var x11 = x1 - QueryTree.prototype.TRIANGLE_OFFSET;
		var x2 = x1 - QueryTree.prototype.TRIANGLE_WIDTH_2;
		var x3 = x1 + QueryTree.prototype.TRIANGLE_WIDTH_2;
		var y2 = y1 + QueryTree.prototype.TRIANGLE_OFFSET	+ QueryTree.prototype.TRIANGLE_HEIGHT;
		var pg = document.createElementNS('http://www.w3.org/2000/svg', 'svg:polygon');
		var points_str = (x1) + "," + (y1 + 2) + " " + (x2) + "," + (y2) + " " + (x3) + "," + (y2);
		pg.setAttribute("points", points_str);
		pg.setAttribute("onmouseover", this.varname + ".mouseoverNode('" + t["i"] + "', true);");
		pg.setAttribute("onmouseout", this.varname + ".mouseoutNode('" + t["i"] + "', true);");
		pg.setAttribute("onclick", this.varname + ".expandNode('" + t["i"] + "');");
		pg.setAttribute("style", "stroke:" + QueryTree.prototype.LINE_COLOUR + ";stroke-width:2;fill:white;cursor:pointer;");
		svgElement.appendChild(pg);
	}
}

QueryTree.prototype.maxXY = function(nod, xyv) {
	var maxx = xyv.x;
	if (nod["x-end"] > maxx) { maxx = nod["x-end"]; }
	var maxy = xyv.y;
	if (nod["y"] > maxy) { maxy = nod["y"]; }
	if (nod["e"] && nod["c"].length > 0) { //find max XY based on children's max
		for ( var i = 0; i < nod["c"].length; i++) {
			maxXYc = this.maxXY(nod["c"][i], {x:maxx, y:maxy});
			if (maxXYc.x > maxx) { maxx = maxXYc.x; }
			if (maxXYc.y > maxy) { maxy = maxXYc.y; }
		}
	} else if (!nod["e"] && nod["c"].length > 0) { //adding triangle height
		//shouldn't it be added only when maxy is already at max at this level?
		maxy = maxy + QueryTree.prototype.YPADDING	+ QueryTree.prototype.TRIANGLE_OFFSET + QueryTree.prototype.TRIANGLE_HEIGHT;
	}
	return {x:maxx, y:maxy};
}

QueryTree.prototype.mouseoverNode = function(pos, clickable) {
	var n = document.getElementById(this.getNodeDomId(pos));
	var te = this.treeNodeByPosition[pos];
	n.setAttribute('font-weight', 'bold');
	n.setAttribute('cursor', 'pointer');
	var qn = document.getElementById("q_" + pos);
	if (qn) { qn.setAttribute("class", "highlight"); }
}

QueryTree.prototype.mouseoutNode = function(pos, clickable) {
	var n = document.getElementById(this.getNodeDomId(pos));
	var te = this.treeNodeByPosition[pos];
	n.removeAttribute('font-weight');
	n.removeAttribute('cursor');
	var qn = document.getElementById("q_" + pos);
	if (qn) { qn.removeAttribute("class"); }
}

QueryTree.prototype.clickNode = function(pos) {
	var n = document.getElementById(this.getNodeDomId(pos));
	var te = this.treeNodeByPosition[pos];

}

QueryTree.prototype.expandNode = function(pos) {
	var n = document.getElementById(this.getNodeDomId(pos));
	var te = this.treeNodeByPosition[pos];
	if (!te["disc"] && te["c"].length > 0) {
		te["e"] = te["e"] ^ true;
		this.calcPositionAndDrawSVG();
	}
}

QueryTree.prototype.mouseoverLine = function(matchNum) {
	var l0 = document.getElementById("t_" + matchNum + "_0");
	var l1 = document.getElementById("t_" + matchNum + "_1");
	var l2 = document.getElementById("t_" + matchNum + "_2");
	if (l0) { l0.setAttribute('cursor', 'pointer');}
	if (l1) { l1.style.strokeWidth = 3; l1.setAttribute('cursor', 'pointer');}
	if (l2) { l2.style.strokeWidth = 3; l2.setAttribute('cursor', 'pointer');}
	var qn = document.getElementById("q_" + matchNum);
	if (qn) { qn.setAttribute("class", "highlight"); }
}

QueryTree.prototype.mouseoutLine = function(matchNum) {
	var l0 = document.getElementById("t_" + matchNum + "_0");
	var l1 = document.getElementById("t_" + matchNum + "_1");
	var l2 = document.getElementById("t_" + matchNum + "_2");
	if (l0) { l0.removeAttribute('cursor'); }
	if (l1) { l1.style.strokeWidth = 2; l1.removeAttribute('cursor'); }
	if (l2) { l2.style.strokeWidth = 2; l2.removeAttribute('cursor'); }
	var qn = document.getElementById("q_" + matchNum);
	if (qn) { qn.removeAttribute("class"); }
}

QueryTree.prototype.clickLine = function(matchNum) {
	var p = this.matches.pairs[matchNum];
	if (p["o"] > 4) {}
}

function Node(type) {
	this.type = type;
	this.children = [];
	this.getPreString = function() {return ""};
	this.getPostString = function() {return ""};
	this.toString = function() {
		var str = this.getPreString();
		for (var i = 0; i < this.children.length; i++) {
			str += this.children[i].toString();
		}
		str += this.getPostString();
{'fraction_non_alphanumeric': 0.1952632830143825, 'fraction_numerical': 0.01610602638773327, 'mean_word_length': 4.198980380040167, 'pattern_counts': {'":': 0, '<': 45, '<?xml version=': 0, '>': 49, 'https://': 0, 'lorem ipsum': 0, 'www.': 10, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7342444', 'n_tokens_mistral': 12918, 'n_tokens_neox': 12300, 'n_words': 2457}