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 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 _
.shuffle(this.inheritances
).forEach( i
=> {
242 // TODO: node shape = triangle fill yellow. See
243 // https://merise.developpez.com/faq/?page=MCD#CIF-ou-dependance-fonctionnelle-de-A-a-Z
244 // https://merise.developpez.com/faq/?page=MLD#Comment-transformer-un-MCD-en-MLD
245 // https://www.developpez.net/forums/d1088964/general-developpement/alm/modelisation/structure-agregation-l-association-d-association/
246 _
.shuffle(i
.children
).forEach( c
=> {
247 if (Math
.random() < 0.5)
248 mcdDot
+= '"' + c
+ '":name -- "' + i
.parent
;
250 mcdDot
+= '"' + i
.parent
+ '":name -- "' + c
;
251 mcdDot
+= '":name [dir="forward", arrowhead="vee", style="dashed"];\n';
255 let assoceCounter
= 0;
256 _
.shuffle(this.associations
).forEach( a
=> {
257 let name
= !!a
.name
&& a
.name
.length
> 0
259 : '_assoce' + assoceCounter
++;
260 mcdDot
+= '"' + name
+ '" [shape="diamond", style="filled", color="lightgrey", label="' + name
+ '"';
262 mcdDot
+= ', peripheries=2';
264 _
.shuffle(a
.entities
).forEach( e
=> {
265 if (Math
.random() < 0.5)
266 mcdDot
+= '"' + e
.name
+ '":name -- "' + name
+ '"';
268 mcdDot
+= '"' + name
+ '" -- "' + e
.name
+ '":name';
269 mcdDot
+= '[label="' + ErDiags
.CARDINAL
[e
.card
] + '"];\n';
273 a
.attributes
.forEach( attr
=> {
274 let label
= (attr
.isKey
? '#' : '') + attr
.name
;
275 mcdDot
+= '"' + name
+ '_' + attr
.name
+ '" [shape=ellipse, label="' + label
+ '"];\n';
276 let attrName
= name
+ '_' + attr
.name
;
277 if (Math
.random() < 0.5)
278 mcdDot
+= '"' + attrName
+ '" -- "' + name
+ '";\n';
280 mcdDot
+= '"' + name
+ '" -- "' + attrName
+ '";\n';
286 ErDiags
.AjaxGet(mcdDot
, graphSvg
=> {
287 this.mcdGraph
= graphSvg
;
288 element
.innerHTML
= graphSvg
;
292 // "Modèle logique des données"
293 // TODO: this one should draw links from foreign keys to keys (port=... in <TD>)
296 let element
= document
.getElementById(id
);
297 if (this.mldGraph
.length
> 0)
299 element
.innerHTML
= this.mcdGraph
;
302 // Build dot graph input
303 let mldDot
= 'graph {\n';
305 Object
.keys(this.entities
).forEach( name
=> {
306 //mld. ... --> devient table
310 this.associations
.forEach( a
=> {
311 a
.entities
.forEach( e
=> { // e.card e.name ...
312 // Pass 1 : entites deviennent tables
313 // Pass 2 : sur les assoces
314 // multi-arite : sub-loop si 0,1 ou 1,1 : aspiré comme attribut de l'association (phase 1)
315 // ensuite, que du 0,n ou 1,n : si == 1, OK une table
316 // si 2 ou + : n tables + 1 pour l'assoce, avec attrs clés étrangères
317 // clé étrangère NOT NULL si 1,1
320 // this.graphMld = ...
321 //console.log(mldDot);
322 ErDiags
.AjaxGet(mldDot
, graphSvg
=> {
323 this.mldGraph
= graphSvg
;
324 element
.innerHTML
= graphSvg
;
330 let element
= document
.getElementById(id
);
331 if (this.sqlText
.length
> 0)
333 element
.innerHTML
= this.sqlText
;
336 //UNIMPLEMENTED (should be straightforward from MLD)