X-Git-Url: https://git.auder.net/variants/Checkered/style.css?a=blobdiff_plain;ds=sidebyside;f=parser.js;h=db70f7f282e074c415a990ac1ad422b6c2683624;hb=20bccb5369dd9394e47a92664339d405664274c7;hp=6caf0a9f5a3f0dfdc4f153a5d8e4ffd54dba02df;hpb=006d95a3942660083d2c957afea5338c2de8642d;p=erdiag.git diff --git a/parser.js b/parser.js index 6caf0a9..db70f7f 100644 --- a/parser.js +++ b/parser.js @@ -3,39 +3,39 @@ class ErDiags { constructor(description) { - this.entities = {}; - this.inheritances = []; - this.associations = []; - this.txt2json(description); - this.tables = []; + this.entities = { }; + this.inheritances = [ ]; + this.associations = [ ]; + this.tables = { }; + this.mcdParsing(description); + this.mldParsing(); + + console.log(this.tables); + // Cache SVG graphs returned by server (in addition to server cache = good perfs) this.mcdGraph = ""; this.mldGraph = ""; this.sqlText = ""; } - static get TYPES() - { - // SQLite storage classes without null - return ["integer","real","text","blob"]; - } - static get CARDINAL() { return { "*": "0,n", "+": "1,n", "?": "0,1", - "1": "1,1" + "1": "1,1", + "?R": "(0,1)", + "1R": "(1,1)", }; } - ////////////////// - // PARSING STAGE 1 - ////////////////// + /////////////////////////////// + // PARSING STAGE 1: text to MCD + /////////////////////////////// // Parse a textual description into a json object - txt2json(text) + mcdParsing(text) { let lines = text.split("\n"); lines.push(""); //easier parsing: always empty line at the end @@ -107,14 +107,8 @@ class ErDiags if (parenthesis !== null) { let sqlClues = parenthesis[1]; - let qualifiers = sqlClues; - let firstWord = sqlClues.match(/[^\s]+/)[0]; - if (ErDiags.TYPES.includes(firstWord)) - { - field.type = firstWord; - qualifiers = sqlClues.substring(firstWord.length).trim(); - } - field.qualifiers = qualifiers; + field.type = sqlClues.match(/[^\s]+/)[0]; //type is always the first indication (mandatory) + field.qualifiers = sqlClues.substring(field.type.length).trim(); } attributes.push(field); } @@ -136,7 +130,10 @@ class ErDiags return inheritance; } - // Association (parsed here): { entities: ArrayOf entity names + cardinality, [attributes: ArrayOf {name, [isKey], [type], [qualifiers]}] } + // Association (parsed here): { + // entities: ArrayOf entity names + cardinality, + // [attributes: ArrayOf {name, [isKey], [type], [qualifiers]}] + // } parseAssociation(lines, start, end) { let assoce = { }; @@ -161,9 +158,95 @@ class ErDiags return assoce; } - ////////////////// - // PARSING STAGE 2 - ////////////////// + ////////////////////////////// + // PARSING STAGE 2: MCD to MLD + ////////////////////////////// + + // From entities + relationships to tables + mldParsing() + { + // Pass 1: initialize tables + Object.keys(this.entities).forEach( name => { + let newTable = [ ]; //array of fields + this.entities[name].attributes.forEach( attr => { + newTable.push({ + name: attr.name, + type: attr.type, + isKey: attr.isKey, + qualifiers: attr.qualifiers, + }); + }); + this.tables[name] = newTable; + }); + // Pass 2: parse associations, add foreign keys when cardinality is 0,1 or 1,1 + this.associations.forEach( a => { + let newTableAttrs = [ ]; + a.entities.forEach( e => { + if (['?','1'].includes(e.card[0])) + { + // Foreign key apparition (for each entity in association minus current one, for each identifying attribute) + a.entities.forEach( e2 => { + if (e2.name == e.name) + return; + e2.attributes.forEach( attr => { + if (attr.isKey) + { + this.tables[e.name].push({ + isKey: e.card.length >= 2 && e.card[1] == 'R', //"weak tables" foreign keys become part of the key + name: "#" + e2.name + "_" + attr.name, + type: attr.type, + qualifiers: "foreign key references " + e2.name + " " + (e.card[0]=='1' ? "not null" : ""), + ref: e2.name, //easier drawMld function (fewer regexps) + }); + } + }); + }); + } + else + { + // Add all keys in current entity + let fields = this.entities[e.name].attributes.filter( attr => { return attr.isKey; }); + newTableAttrs.push({ + fields: fields, + entity: e.name, + }); + } + }); + if (newTableAttrs.length > 1) + { + // Ok, really create a new table + let newTable = { + name: a.name || newTableAttrs.map( item => { return item.entity; }).join("_"), + fields: [ ], + }; + newTableAttrs.forEach( item => { + item.fields.forEach( f => { + newTable.fields.push({ + name: item.entity + "_" + f.name, + isKey: true, + type: f.type, + qualifiers: (f.qualifiers+" " || "") + "foreign key references " + item.entity + " not null", + ref: item.entity, + }); + }); + }); + // Add relationship potential own attributes + a.attributes.forEach( attr => { + newTable.fields.push({ + name: attr.name, + isKey: false, + type: attr.type, + qualifiers: attr.qualifiers, + }); + }); + this.tables[newTable.name] = newTable.fields; + } + }); + } + + ///////////////////////////////// + // DRAWING + GET SQL FROM PARSING + ///////////////////////////////// static AjaxGet(dotInput, callback) { @@ -192,7 +275,7 @@ class ErDiags mcdDot += 'rankdir="LR";\n'; // Nodes: if (mcdStyle == "compact") - mcdDot += "node [shape=plaintext];\n"; + mcdDot += 'node [shape=plaintext];\n'; _.shuffle(Object.keys(this.entities)).forEach( name => { if (mcdStyle == "bubble") { @@ -202,7 +285,7 @@ class ErDiags mcdDot += '];\n'; if (!!this.entities[name].attributes) { - _.shuffle(this.entities[name].attributes).forEach( a => { + this.entities[name].attributes.forEach( a => { let label = (a.isKey ? '#' : '') + a.name; let attrName = name + '_' + a.name; mcdDot += '"' + attrName + '" [shape=ellipse, label="' + label + '"];\n'; @@ -226,7 +309,7 @@ class ErDiags mcdDot += '
' + name + ' |
' + label + ' |
" + sqlText + "
";
}
}