51a05e0815f2b9d60c29b2f104f610d2bab86f9d
1 // ER diagram description parser
4 constructor(description
)
7 this.inheritances
= [];
8 this.associations
= [];
9 this.txt2json(description
);
11 // Cache SVG graphs returned by server (in addition to server cache = good perfs)
19 // SQLite storage classes without null
20 return ["integer","real","text","blob"];
37 // Parse a textual description into a json object
40 let lines
= text
.split("\n");
41 lines
.push(""); //easier parsing: always empty line at the end
43 for (let i
=0; i
< lines
.length
; i
++)
45 lines
[i
] = lines
[i
].trim();
47 if (lines
[i
].length
== 0)
49 if (start
>= 0) //there is some group of lines to parse
51 this.parseThing(lines
, start
, i
);
55 else //not empty line: just register starting point
63 // Parse a group of lines into entity, association, ...
64 parseThing(lines
, start
, end
) //start included, end excluded
66 switch (lines
[start
].charAt(0))
69 // Entity = { name: { attributes, [weak] } }
70 let name
= lines
[start
].match(/[^\[\]\s]+/)[0];
71 let entity
= { attributes: this.parseAttributes(lines
, start
+1, end
) };
72 if (lines
[start
].charAt(1) == '[')
74 this.entities
[name
] = entity
;
76 case 'i': //inheritance (arrows)
77 this.inheritances
= this.inheritances
.concat(this.parseInheritance(lines
, start
+1, end
));
79 case '{': //association
80 // Association = { [name], [attributes], [weak], entities: ArrayOf entity indices }
81 let relationship
= { };
82 let nameRes
= lines
[start
].match(/[^{}\s]+/);
84 relationship
.name
= nameRes
[0];
85 if (lines
[start
].charAt(1) == '{')
86 relationship
.weak
= true;
87 this.associations
.push(Object
.assign({}, relationship
, this.parseAssociation(lines
, start
+1, end
)));
92 // attributes: ArrayOf {name, [isKey], [type], [qualifiers]}
93 parseAttributes(lines
, start
, end
)
96 for (let i
=start
; i
<end
; i
++)
100 if (line
.charAt(0) == '#')
103 line
= line
.slice(1);
105 field
.name
= line
.match(/[^()\s]+/)[0];
106 let parenthesis
= line
.match(/\((.+)\)/);
107 if (parenthesis
!== null)
109 let sqlClues
= parenthesis
[1];
110 let qualifiers
= sqlClues
;
111 let firstWord
= sqlClues
.match(/[^\s]+/)[0];
112 if (ErDiags
.TYPES
.includes(firstWord
))
114 field
.type
= firstWord
;
115 qualifiers
= sqlClues
.substring(firstWord
.length
).trim();
117 field
.qualifiers
= qualifiers
;
119 attributes
.push(field
);
124 // GroupOf Inheritance: { parent, children: ArrayOf entity indices }
125 parseInheritance(lines
, start
, end
)
127 let inheritance
= [];
128 for (let i
=start
; i
<end
; i
++)
130 let lineParts
= lines
[i
].split(" ");
132 for (let j
=1; j
<lineParts
.length
; j
++)
133 children
.push(lineParts
[j
]);
134 inheritance
.push({ parent:lineParts
[0], children: children
});
139 // Association (parsed here): { entities: ArrayOf entity names + cardinality, [attributes: ArrayOf {name, [isKey], [type], [qualifiers]}] }
140 parseAssociation(lines
, start
, end
)
147 if (lines
[i
].charAt(0) == '-')
149 assoce
.attributes
= this.parseAttributes(lines
, i
+1, end
);
154 // Read entity name + cardinality
155 let lineParts
= lines
[i
].split(" ");
156 entities
.push({ name:lineParts
[0], card:lineParts
[1] });
160 assoce
.entities
= entities
;
168 static AjaxGet(dotInput
, callback
)
170 let xhr
= new XMLHttpRequest();
171 xhr
.onreadystatechange = function() {
172 if (this.readyState
== 4 && this.status
== 200)
173 callback(this.responseText
);
175 xhr
.open("GET", "scripts/getGraphSvg.php?dot=" + encodeURIComponent(dotInput
), true);
179 // "Modèle conceptuel des données". TODO: option for graph size
180 // NOTE: randomizing helps to obtain better graphs (sometimes)
181 drawMcd(id
, mcdStyle
) //mcdStyle: bubble, or compact
183 let element
= document
.getElementById(id
);
184 mcdStyle
= mcdStyle
|| "compact";
185 if (this.mcdGraph
.length
> 0)
187 element
.innerHTML
= this.mcdGraph
;
190 // Build dot graph input
191 let mcdDot
= 'graph {\n';
192 mcdDot
+= 'rankdir="LR";\n';
194 if (mcdStyle
== "compact")
195 mcdDot
+= "node [shape=plaintext];\n";
196 _
.shuffle(Object
.keys(this.entities
)).forEach( name
=> {
197 if (mcdStyle
== "bubble")
199 mcdDot
+= name
+ '[shape=rectangle, label="' + name
+ '"';
200 if (this.entities
[name
].weak
)
201 mcdDot
+= ', peripheries=2';
203 if (!!this.entities
[name
].attributes
)
205 _
.shuffle(this.entities
[name
].attributes
).forEach( a
=> {
206 let label
= (a
.isKey
? '#' : '') + a
.name
;
207 let attrName
= name
+ '_' + a
.name
;
208 mcdDot
+= attrName
+ '[shape=ellipse, label="' + label
+ '"];\n';
209 if (Math
.random() < 0.5)
210 mcdDot
+= attrName
+ ' -- ' + name
+ ';\n';
212 mcdDot
+= name
+ ' -- ' + attrName
+ ';\n';
218 mcdDot
+= name
+ '[label=<';
219 if (this.entities
[name
].weak
)
221 mcdDot
+= '<table port="name" BORDER="1" ALIGN="LEFT" CELLPADDING="0" CELLSPACING="3" CELLBORDER="0">' +
222 '<tr><td><table BORDER="1" ALIGN="LEFT" CELLPADDING="5" CELLSPACING="0">\n';
225 mcdDot
+= '<table port="name" BORDER="1" ALIGN="LEFT" CELLPADDING="5" CELLSPACING="0">\n';
226 mcdDot
+= '<tr><td BGCOLOR="#ae7d4e" BORDER="0"><font COLOR="#FFFFFF">' + name
+ '</font></td></tr>\n';
227 if (!!this.entities
[name
].attributes
)
229 this.entities
[name
].attributes
.forEach( a
=> {
230 let label
= (a
.isKey
? '<u>' : '') + a
.name
+ (a
.isKey
? '</u>' : '');
231 mcdDot
+= '<tr><td BGCOLOR="#FFFFFF" BORDER="0" ALIGN="LEFT"><font COLOR="#000000" >' + label
+ '</font></td></tr>\n';
234 mcdDot
+= '</table>';
235 if (this.entities
[name
].weak
)
236 mcdDot
+= '</td></tr></table>';
241 this.inheritances
.forEach( i
=> {
242 _
.shuffle(i
.children
).forEach( c
=> {
243 if (Math
.random() < 0.5)
244 mcdDot
+= c
+ ':name -- ' + i
.parent
;
246 mcdDot
+= i
.parent
+ ':name -- ' + c
;
247 mcdDot
+= ':name [dir="forward", arrowhead="vee", style="dashed"];\n';
251 let assoceCounter
= 0;
252 _
.shuffle(this.associations
).forEach( a
=> {
253 let name
= !!a
.name
&& a
.name
.length
> 0
255 : '_assoce' + assoceCounter
++;
256 mcdDot
+= name
+ '[shape="diamond", style="filled", color="lightgrey", label="' + (!!a
.name
? a
.name : '') + '"';
258 mcdDot
+= ', peripheries=2';
260 _
.shuffle(a
.entities
).forEach( e
=> {
261 if (Math
.random() < 0.5)
262 mcdDot
+= e
.name
+ ':name -- ' + name
;
264 mcdDot
+= name
+ ' -- ' + e
.name
+ ':name';
265 mcdDot
+= '[label="' + ErDiags
.CARDINAL
[e
.card
] + '"];\n';
269 _
.shuffle(a
.attributes
).forEach( attr
=> {
270 let label
= (attr
.isKey
? '#' : '') + attr
.name
;
271 mcdDot
+= name
+ '_' + attr
.name
+ '[shape=ellipse, label="' + label
+ '"];\n';
272 let attrName
= name
+ '_' + attr
.name
;
273 if (Math
.random() < 0.5)
274 mcdDot
+= attrName
+ ' -- ' + name
+ ';\n';
276 mcdDot
+= name
+ ' -- ' + attrName
+ ';\n';
282 ErDiags
.AjaxGet(mcdDot
, graphSvg
=> {
283 this.mcdGraph
= graphSvg
;
284 element
.innerHTML
= graphSvg
;
288 // "Modèle logique des données"
289 // TODO: this one should draw links from foreign keys to keys (port=... in <TD>)
292 let element
= document
.getElementById(id
);
293 if (this.mldGraph
.length
> 0)
295 element
.innerHTML
= this.mcdGraph
;
298 // Build dot graph input
299 let mldDot
= 'graph {\n';
301 Object
.keys(this.entities
).forEach( name
=> {
302 //mld. ... --> devient table
306 this.associations
.forEach( a
=> {
307 a
.entities
.forEach( e
=> { // e.card e.name ...
308 // Pass 1 : entites deviennent tables
309 // Pass 2 : sur les assoces
310 // multi-arite : sub-loop si 0,1 ou 1,1 : aspiré comme attribut de l'association (phase 1)
311 // ensuite, que du 0,n ou 1,n : si == 1, OK une table
312 // si 2 ou + : n tables + 1 pour l'assoce, avec attrs clés étrangères
313 // clé étrangère NOT NULL si 1,1
316 // this.graphMld = ...
317 //console.log(mldDot);
318 ErDiags
.AjaxGet(mldDot
, graphSvg
=> {
319 this.mldGraph
= graphSvg
;
320 element
.innerHTML
= graphSvg
;
326 let element
= document
.getElementById(id
);
327 if (this.sqlText
.length
> 0)
329 element
.innerHTML
= this.sqlText
;
332 //UNIMPLEMENTED (should be straightforward from MLD)