Skip to content

Instantly share code, notes, and snippets.

@sremy
Created February 27, 2020 00:31
Show Gist options
  • Select an option

  • Save sremy/2b68d5c8370eac3f145aac1ef8b5811e to your computer and use it in GitHub Desktop.

Select an option

Save sremy/2b68d5c8370eac3f145aac1ef8b5811e to your computer and use it in GitHub Desktop.
File -> Parser -> Graph!
function dropHandler(ev) {
ev.target.classList.remove("over_zone");
// Prevent default behavior (Prevent file from being opened)
ev.preventDefault();
if (ev.dataTransfer.items) {
// Use DataTransferItemList interface to access the file(s)
// console.log(ev.dataTransfer)
for (var i = 0; i < ev.dataTransfer.items.length; i++) {
// If dropped items aren't files, reject them
if (ev.dataTransfer.items[i].kind === 'file') {
var file = ev.dataTransfer.items[i].getAsFile();
console.log('... file[' + i + '].name = ' + file.name + ' ' + file.size + ' bytes');
readFile(file);
}
}
} else {
// Use DataTransfer interface to access the file(s)
for (var i = 0; i < ev.dataTransfer.files.length; i++) {
console.log('... file[' + i + '].name = ' + ev.dataTransfer.files[i].name + ' ' + file.size + ' bytes');
readFile(file);
}
}
}
function readFile(file) {
var reader = new FileReader();
var content = document.getElementById('drop_zone');
reader.onload = function (event) {
clearGraph();
fileContent = event.target.result
content.innerHTML = 'Filename: ' + file.name;
content.innerHTML += '<br/>Size: ' + fileContent.length + '';
parseHiera(fileContent)
};
reader.readAsText(file);
}
function parseHiera(hieratxt) {
console.log('Parsing text of length ' + hieratxt.length)
let myReader = new Reader();
let racineNode = myReader.readFile(hieratxt)['racine'];
displayGraph(racineNode);
}
function dragOverHandler(ev) {
ev.dataTransfer.dropEffect = 'copy';
ev.target.classList.add("over_zone");
ev.preventDefault();
}
function dragLeaveHandler(ev) {
ev.target.classList.remove("over_zone");
}
var treemap = d3.treemap();
//d3 = require("d3@5", "d3-array@2")
dx = 30
dy = 160
// width = 954
tree = d3.tree().nodeSize([dx, dy])
treeLink = d3.linkHorizontal().x(d => d.y).y(d => d.x)
function graph(root, {
label = d => d.data.label + ' (' + (d.data.price ? d.data.price : '') + ')',
highlight = () => false,
marginLeft = 100
} = {}) {
root = tree(root);
console.log('root.descendants()=' + root.descendants())
console.log(root.descendants())
let x0 = Infinity;
let x1 = -x0;
root.each(d => {
if (d.x > x1) x1 = d.x; // max
if (d.x < x0) x0 = d.x; // min
});
let y0 = Infinity;
let y1 = -y0;
root.each(d => {
if (d.y > y1) y1 = d.y; // max
if (d.y < y0) y0 = d.y; // min
});
// console.log("x1 " + x1 + " x0 " + x0);
// console.log("y1 " + y1 + " y0 " + y0);
width = y1 - y0 + dy * 2;
height = x1 - x0 + dx * 2;
const svg = d3.select("svg")
.attr("width", width)
.attr("height", height)
// .attr("viewBox", [0, 0, width, x1 - x0 + dx * 2])
.style("overflow", "visible");
const g = svg.append("g")
.attr("font-family", "sans-serif")
.attr("font-size", 14)
.attr("transform", `translate(${marginLeft}, ${dx - x0})`);
const link = g.append("g")
.attr("fill", "none")
.attr("stroke", "#555")
.attr("stroke-opacity", 0.5)
.attr("stroke-width", 1.5)
.selectAll("path")
.data(root.links())
.join("path")
.attr("stroke", d => highlight(d.source) && highlight(d.target) ? "red" : null)
.attr("stroke-opacity", d => highlight(d.source) && highlight(d.target) ? 1 : null)
.attr("d", treeLink);
const node = g.append("g")
.attr("stroke-linejoin", "round")
.attr("stroke-width", 3)
.selectAll("g")
.data(root.descendants())
.join("g")
.attr("transform", d => `translate(${d.y},${d.x})`);
node.append("circle")
.attr("fill", d => highlight(d) ? "red" : d.children ? "#955" : "#559")
.attr("r", 2.5);
textlabel = node.append("text")
.attr("fill", d => highlight(d) ? "red" : null)
.attr("dy", "0.31em")
.attr("x", d => d.children ? -6 : 6)
.attr("text-anchor", d => d.children ? "end" : "start")
.text(label);
textlabel.clone(true).lower()
.attr("stroke", "white")
.attr("stroke-width", "5");
textlabel.append("tspan")
.attr("x", d => d.children ? -6 : 6)
.attr("dy", "1.2em")
.attr("fill", "blue")
.text(d => d.data.price)
return svg.node();
}
//familyChart = graph(hiera, {label: d => d.data.name, highlight: (d) => d.name === "Abel" || d.name === "Eve"})
// ---------------------------------------------
function contractsChildrenFunction(node) {
return node.contract_list;
}
function displayGraph(racineNode) {
console.log(racineNode);
hiera = d3.hierarchy(racineNode, contractsChildrenFunction);
console.log(hiera);
hieraChart = graph(hiera);
}
function clearGraph() {
document.getElementById("graph-hiera").innerHTML = '';
}
function testGraph() {
let racineNode = testReader();
displayGraph(racineNode);
}
// testGraph();
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Parser page</title>
<!--link rel="stylesheet" href="style.css"-->
<script src="./parser.js"></script>
<script src="./reader.js"></script>
<script src="https://d3js.org/d3.v5.min.js"></script>
<style>
.drop_zone {
border: 3px solid rgb(173, 173, 224);
/* box-sizing: border-box; */
z-index: -1;
position: fixed;
width: -3;
height: -3;
top: 0;
left: 0;
right: 0;
bottom: 0;
font: caption;
font-size: 14px;
}
.over_zone {
border: 6px solid darkblue;
}
</style>
</head>
<body>
<!-- page content -->
<svg id="graph-hiera" ></svg>
<div id="drop_zone" class="drop_zone" ondrop="dropHandler(event);" ondragover="dragOverHandler(event);" ondragleave="dragLeaveHandler(event)">
<p>Drag one file to draw hierarchy tree...</p>
</div>
</body>
<script src="./hiera.js"></script>
<script src="./dropfile.js"></script>
</html>
class Token {
chaine = '';
tokenType = null;
constructor(type, chaine) {
this.tokenType = type;
this.chaine = chaine;
}
}
class Parser {
curToken = Parser.nodes.START;
cursor = 0;
constructor(texte) {
this.texte = texte;
}
parse() {
if(this.isEnd()) {
this.curToken = Parser.nodes.END;
return new Token(Parser.nodes.END, '');
}
switch(this.curToken) {
case Parser.nodes.STRINGEND: // eat ';'
case Parser.nodes.NUMBEREND:
case Parser.nodes.VALUECLOSEARRAY:
this.consumeSemiColon();
case Parser.nodes.START:
return new Token(Parser.nodes.LABEL1, this.consumeLabel());
case Parser.nodes.LABEL1:
return new Token(Parser.nodes.LABEL1, this.consumeLabel());
case Parser.nodes.LABEL2:
return new Token(Parser.nodes.LABEL2, this.consumeLabel());
case Parser.nodes.FIELDNAME:
return new Token(Parser.nodes.FIELDNAME, this.consumeLabel());
case Parser.nodes.AFFECTATION:
return this.parseValue();
case Parser.nodes.VALUEOPENARRAY:
return this.parseArray();
case Parser.nodes.STRINGARRAY:
case Parser.nodes.NUMBERARRAY:
return this.parseInsideArray();
case Parser.nodes.END:
return new Token(Parser.nodes.END, '');
}
return Parser.nodes.START;
}
isEnd() {
return this.cursor >= this.texte.length;
}
consumeLabel() {
let chaine = '';
while(!this.isEnd()) {
if(this.skipSpaceAndEOL() || this.detectComments()) continue;
else if(this.texte[this.cursor] === ':' && this.cursor+1 < this.texte.length && this.texte[this.cursor+1] === '=') { // WARN check size buffer
this.curToken = Parser.nodes.AFFECTATION;
this.cursor+=2;
return chaine;
}
else if(this.texte[this.cursor] === '.') {
this.curToken = Parser.nodes.LABEL2;
this.cursor++;
return chaine;
}
else if(this.texte[this.cursor] === '/') {
this.curToken = Parser.nodes.FIELDNAME;
this.cursor++;
return chaine;
}
else {
chaine += this.texte[this.cursor];
this.cursor++;
}
}
this.curToken = Parser.nodes.END;
return chaine;
}
consumeString() {
let chaine = '';
while(!this.isEnd()) {
if(this.texte[this.cursor] === '"') {
this.cursor++;
this.curToken = Parser.nodes.STRINGEND;
return chaine;
}
else if(this.texte[this.cursor] === '\\' && this.cursor+1 < this.texte.length && this.texte[this.cursor+1] === '"') {
chaine += '"';
this.cursor += 2;
}
else {
chaine += this.texte[this.cursor];
this.cursor++;
}
}
this.curToken = Parser.nodes.END;
return chaine;
}
consumeNumber() {
let chaine = '';
while(!this.isEnd()) {
if(this.texte[this.cursor] <= '9' && this.texte[this.cursor] >= '0') {
chaine += this.texte[this.cursor++];
}
else if(this.texte[this.cursor] === '.' || this.texte[this.cursor] === 'e' ||
this.texte[this.cursor] === 'E' || this.texte[this.cursor] === '-') {
chaine += this.texte[this.cursor++];
}
else if(this.texte[this.cursor] === ' ') {
this.cursor++;
this.curToken = Parser.nodes.NUMBEREND;
return chaine;
}
else { // especially ';'
this.curToken = Parser.nodes.NUMBEREND;
return chaine;
}
}
this.curToken = Parser.nodes.END;
return chaine;
}
parseValue() {
let chaine = '';
while(!this.isEnd()) {
if(this.skipSpaceAndEOL() || this.detectComments()) continue;
if(this.texte[this.cursor] === '"') {
this.cursor++;
return new Token(Parser.nodes.VALUESTRING, this.consumeString());
}
else if((this.texte[this.cursor] <= '9' && this.texte[this.cursor] >= '0')
|| (this.texte[this.cursor] === '-' && this.texte[this.cursor+1] !== '-')) {
return new Token(Parser.nodes.VALUENUMBER, this.consumeNumber());
}
else if(this.texte[this.cursor] === '[' || this.texte[this.cursor] === '(') {
this.curToken = Parser.nodes.VALUEOPENARRAY;
return new Token(Parser.nodes.VALUEOPENARRAY, this.texte[this.cursor++]);
}
else {
console.warn('Unexpected character [' + this.texte[this.cursor] + '], value expected');
this.cursor++;
}
}
this.curToken = Parser.nodes.END;
return chaine;
}
// After parsing '[' character
parseArray() {
let chaine = '';
while(!this.isEnd()) {
if(this.skipSpaceAndEOL() || this.detectComments()) continue;
if(this.texte[this.cursor] === '"') {
this.cursor++;
const stringToken = new Token(Parser.nodes.VALUESTRING, this.consumeString());
this.curToken = Parser.nodes.STRINGARRAY;
return stringToken;
}
else if((this.texte[this.cursor] <= '9' && this.texte[this.cursor] >= '0')
|| (this.texte[this.cursor] === '-' && this.texte[this.cursor+1] !== '-')) {
const numberToken = new Token(Parser.nodes.VALUENUMBER, this.consumeNumber());
this.curToken = Parser.nodes.NUMBERARRAY;
return numberToken;
}
else if(this.texte[this.cursor] === ']' || this.texte[this.cursor] === ')') {
this.curToken = Parser.nodes.VALUECLOSEARRAY;
return new Token(Parser.nodes.VALUECLOSEARRAY, this.texte[this.cursor++]);
}
else {
console.warn('Unexpected character [' + this.texte[this.cursor] + '] ' + this.texte.charCodeAt(this.cursor) + ', array element expected');
this.cursor++;
}
}
this.curToken = Parser.nodes.END;
return chaine;
}
// After parsing an element in array, expect: ',' or ']'
parseInsideArray() {
while(!this.isEnd()) {
if(this.skipSpaceAndEOL() || this.detectComments()) continue;
if(this.texte[this.cursor] === ',') {
this.cursor++;
this.curToken = Parser.nodes.VALUEOPENARRAY;
return this.parseArray();
}
else if(this.texte[this.cursor] === ']' || this.texte[this.cursor] === ')') {
this.curToken = Parser.nodes.VALUECLOSEARRAY;
return new Token(Parser.nodes.VALUECLOSEARRAY, this.texte[this.cursor++]);
}
else {
console.warn('Unexpected character: ' + this.texte[this.cursor]);
this.cursor++;
}
}
this.curToken = Parser.nodes.END;
return '';
}
// End of statement, Nothing else than semicolon, comment and spaces is expected
consumeSemiColon() {
let chaine = '';
while(!this.isEnd()) {
if(this.skipSpaceAndEOL() || this.detectComments()) continue;
if(this.texte[this.cursor] === ';') {
this.curToken = Parser.nodes.START;
this.cursor++;
return ';';
}
else {
console.warn('Unexpected character after statement: [' + this.texte[this.cursor] + '] expected ";"')
chaine += this.texte[this.cursor];
this.cursor++;
}
}
this.curToken = Parser.nodes.END;
return chaine;
}
skipSpaceAndEOL() {
if(this.texte[this.cursor] === ' ' || this.texte[this.cursor] === '\r'
|| this.texte[this.cursor] === '\n' || this.texte[this.cursor] === '\t') {
this.cursor++;
return true;
}
}
detectComments() {
if(this.texte[this.cursor] === '-' && this.cursor+1 < this.texte.length && this.texte[this.cursor+1] === '-') {
this.cursor+=2;
this.eatComments();
return true;
}
}
eatComments() {
while(!this.isEnd()) {
if(this.texte[this.cursor] === '\r' && this.cursor+1 < this.texte.length && this.texte[this.cursor+1] === '\n') {
this.curToken = Parser.nodes.START;
this.cursor+=2;
return;
}
else if(this.texte[this.cursor] === '\n') {
this.curToken = Parser.nodes.START;
this.cursor++;
return;
}
else {
this.cursor++;
}
}
this.curToken = Parser.nodes.END;
return;
}
}
Parser.nodes = {
START: 'START',
LABEL1: 'LABEL1',
LABEL2: 'LABEL2',
FIELDNAME: 'FIELDNAME',
AFFECTATION: 'AFFECTATION',
VALUESTRING: 'VALUESTRING',
STRINGEND: 'STRINGEND',
VALUENUMBER: 'VALUENUMBER',
NUMBEREND: 'NUMBEREND',
VALUEOPENARRAY: 'VALUEOPENARRAY',
STRINGARRAY: 'STRINGARRAY',
NUMBERARRAY: 'NUMBERARRAY',
VALUECLOSEARRAY: 'VALUECLOSEARRAY',
COMMENT: 'COMMENT',
END: 'END'
};
function testParser() {
let myparser = new Parser(`result := "racine" ; -- plop
racine.contract_list := ["contrat1", "contrat2", -- this is a comment in array
"contrat4", "contrat10" ] ;
racine.contract_size := 4;
racine.contrat1/contract_list := [ "subcontrat11" ];
racine.contrat2/contract_list := [15, 0.123, -7894, 1.5e3];`);
// let myparser = new Parser('result := "racine" ; \
// \
// racine.contract_size := 4;\
// racine.number := 0.123 ;\
// racine.contrat1/contract_list := "a sub contrat11"; \
// racine.contrat2/contract_list := "subcontrat21";');
let token = null;
let i = 0;
while((token === null || token.tokenType !== 'end') && i < 50) {
token = myparser.parse();
console.log(token.tokenType + '[' + token.chaine + '] ' + ' @' + myparser.cursor + ' -> ' + myparser.curToken);
i++;
}
}
//testParser();
class Reader {
setValueInDico(value) {
if(this.fieldName !== '') {
const key = this.label + '.' + this.label2;
if(this.dico[key] === undefined) {
this.dico[key] = {};
}
this.dico[key][this.fieldName] = value;
}
else if (this.label2 !== '') {
if(this.dico[this.label] === undefined) {
this.dico[this.label] = {};
}
this.dico[this.label][this.label2] = value;
}
else {
this.dico[this.label] = value;
}
this.label = this.label2 = this.fieldName = '';
}
dico = {};
label = '';
label2 = '';
fieldName = '';
arrayValue = null;
init() {
this.dico = {};
this.label = '';
this.label2 = '';
this.fieldName = '';
this.arrayValue = null;
}
readFile(texte) {
this.init();
let myparser = new Parser(texte);
let token = null;
let i = 0;
while((token === null || token.tokenType !== Parser.nodes.END) && i++ < texte.length) {
token = myparser.parse();
// console.log(token.tokenType + '[' + token.chaine + '] ' + ' @' + myparser.cursor + ' -> ' + myparser.curToken);
switch(token.tokenType) {
case Parser.nodes.LABEL1:
this.label = token.chaine;
break;
case Parser.nodes.LABEL2:
this.label2 = token.chaine;
break;
case Parser.nodes.FIELDNAME:
this.fieldName = token.chaine;
break;
case Parser.nodes.VALUENUMBER:
if(this.arrayValue === null) {
this.setValueInDico(token.chaine);
}
else {
this.arrayValue.push(token.chaine);
}
break;
case Parser.nodes.VALUESTRING:
if(this.arrayValue === null) {
this.setValueInDico(token.chaine);
}
else {
this.arrayValue.push(token.chaine);
}
break;
case Parser.nodes.VALUEOPENARRAY:
this.arrayValue = [];
break;
case Parser.nodes.NUMBERARRAY: // not used
this.arrayValue.push(token.chaine);
console.log('NUMBERARRAY arrayValue = ' + this.arrayValue);
break;
case Parser.nodes.STRINGARRAY: // not used
this.arrayValue.push(token.chaine);
console.log('STRINGARRAY arrayValue = ' + this.arrayValue);
break;
case Parser.nodes.VALUECLOSEARRAY:
this.setValueInDico(this.arrayValue);
this.arrayValue = null;
break;
case Parser.nodes.END:
break;
default:
console.warn('Unexpected token ' + token.tokenType + ': [' + token.chaine + ']')
}
}
this.traverse(this.dico['racine']);
// console.log(this.dico);
return this.dico;
}
// unused
process(key, value) {
if(key === 'contract_list') {
//console.log(key + " : " + value);
value.forEach(c => console.log(this.dico['racine.' + c]));
}
}
traverse(obj) {
for (let prop in obj) {
// func.apply(this, [prop, obj[prop]]);
if(prop === 'contract_list') {
// console.log(obj);
let liste = obj[prop];
for(let i = 0; i < liste.length; i++) {
let referenceName = liste[i];
if(typeof(referenceName) === 'string') {
// console.log('key: ' + 'racine.' + referenceName);
liste[i] = this.dico['racine.' + referenceName];
}
}
}
// TODO restrict to list or contract_list ?
if (obj[prop] !== null && typeof (obj[prop]) == "object") {
this.traverse(obj[prop]); // recursive call
}
}
}
}
function testReader() {
const contractHieraTexte = `result := "racine" ; -- plop
racine.contract_list := ["contrat1", "contrat2", -- this is a comment in array
"contrat4", "contrat9" ] ;
racine.label := "The Racine";
racine.contract_size := 4;
racine.totalprice := 12.123;
racine.product/label := "a quality product";
racine.product/price := 9565.90;
racine.product/origin := "Finistère";
racine.contrat1/price_list := [15, 0.123, -7894, 1.5e3];
racine.contrat1/contract_list := [ "subcontrat11" ];
racine.contrat2/contract_list := ["subcontrat21"];
racine.contrat4/contract_list := ["subcontrat41"];
racine.contrat9/contract_list := ["subcontrat91"];
racine.contrat1/label := "contrat 1";
racine.contrat2/label := "contrat 2";
racine.contrat3/label := "contrat 3";
racine.contrat4/label := "contrat 4";
racine.contrat9/label := "contrat 9";
racine.contrat1/price := "9.9€";
racine.contrat2/price := "11€";
racine.contrat3/price := "12€";
racine.contrat4/price := "13€";
racine.contrat9/price := "20€";
racine.subcontrat11/label := "label contrat 11";
racine.subcontrat11/name := "name contrat 11";
racine.subcontrat11/price := "111$";
racine.subcontrat21/label := "contrat 21";
racine.subcontrat21/contract_list := ["subsubcontrat211"];
racine.subcontrat41/label := "contrat 41";
racine.subcontrat91/label := "contrat 91";
racine.subcontrat91/price := "321£";
racine.subsubcontrat211/label := "contrat 211";
racine.subsubcontrat211/contract_list := ["contrat_xxx"];
racine.contrat_xxx/label := "contrat XXX";
racine.contrat_xxx/price := "xxx";
`;
let myReader = new Reader();
let tree = myReader.readFile(contractHieraTexte)['racine'];
console.log(tree);
return tree;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment