8fc65edc3330eedf29ac02c7d9a9e957ab217a3f
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"];
39 // Parse a textual description into a json object
42 let lines
= text
.split("\n");
43 lines
.push(""); //easier parsing: always empty line at the end
45 for (let i
=0; i
< lines
.length
; i
++)
47 lines
[i
] = lines
[i
].trim();
49 if (lines
[i
].length
== 0)
51 if (start
>= 0) //there is some group of lines to parse
53 this.parseThing(lines
, start
, i
);
57 else //not empty line: just register starting point
65 // Parse a group of lines into entity, association, ...
66 parseThing(lines
, start
, end
) //start included, end excluded
68 switch (lines
[start
].charAt(0))
71 // Entity = { name: { attributes, [weak] } }
72 let name
= lines
[start
].match(/[^\[\]"\s]+/)[0];
73 let entity
= { attributes: this.parseAttributes(lines
, start
+1, end
) };
74 if (lines
[start
].charAt(1) == '[')
76 this.entities
[name
] = entity
;
78 case 'i': //inheritance (arrows)
79 this.inheritances
= this.inheritances
.concat(this.parseInheritance(lines
, start
+1, end
));
81 case '{': //association
82 // Association = { [name], [attributes], [weak], entities: ArrayOf entity indices }
83 let relationship
= { };
84 let nameRes
= lines
[start
].match(/[^{}"\s]+/);
86 relationship
.name
= nameRes
[0];
87 if (lines
[start
].charAt(1) == '{')
88 relationship
.weak
= true;
89 this.associations
.push(Object
.assign({}, relationship
, this.parseAssociation(lines
, start
+1, end
)));
94 // attributes: ArrayOf {name, [isKey], [type], [qualifiers]}
95 parseAttributes(lines
, start
, end
)
98 for (let i
=start
; i
<end
; i
++)
102 if (line
.charAt(0) == '#')
105 line
= line
.slice(1);
107 field
.name
= line
.match(/[^()"\s]+/)[0];
108 let parenthesis
= line
.match(/\((.+)\)/);
109 if (parenthesis
!== null)
111 let sqlClues
= parenthesis
[1];
112 let qualifiers
= sqlClues
;
113 let firstWord
= sqlClues
.match(/[^\s]+/)[0];
114 if (ErDiags
.TYPES
.includes(firstWord
))
116 field
.type
= firstWord
;
117 qualifiers
= sqlClues
.substring(firstWord
.length
).trim();
119 field
.qualifiers
= qualifiers
;
121 attributes
.push(field
);
126 // GroupOf Inheritance: { parent, children: ArrayOf entity indices }
127 parseInheritance(lines
, start
, end
)
129 let inheritance
= [];
130 for (let i
=start
; i
<end
; i
++)
132 let lineParts
= lines
[i
].split(" ");
134 for (let j
=1; j
<lineParts
.length
; j
++)
135 children
.push(lineParts
[j
]);
136 inheritance
.push({ parent:lineParts
[0], children: children
});
141 // Association (parsed here): {
142 // entities: ArrayOf entity names + cardinality,
143 // [attributes: ArrayOf {name, [isKey], [type], [qualifiers]}]
145 parseAssociation(lines
, start
, end
)
152 if (lines
[i
].charAt(0) == '-')
154 assoce
.attributes
= this.parseAttributes(lines
, i
+1, end
);
159 // Read entity name + cardinality
160 let lineParts
= lines
[i
].split(" ");
161 entities
.push({ name:lineParts
[0], card:lineParts
[1] });
165 assoce
.entities
= entities
;
173 static AjaxGet(dotInput
, callback
)
175 let xhr
= new XMLHttpRequest();
176 xhr
.onreadystatechange = function() {
177 if (this.readyState
== 4 && this.status
== 200)
178 callback(this.responseText
);
180 xhr
.open("GET", "scripts/getGraphSvg.php?dot=" + encodeURIComponent(dotInput
), true);
184 // "Modèle conceptuel des données". TODO: option for graph size
185 // NOTE: randomizing helps to obtain better graphs (sometimes)
186 drawMcd(id
, mcdStyle
) //mcdStyle: bubble, or compact
188 let element
= document
.getElementById(id
);
189 mcdStyle
= mcdStyle
|| "compact";
190 if (this.mcdGraph
.length
> 0)
192 element
.innerHTML
= this.mcdGraph
;
195 // Build dot graph input
196 let mcdDot
= 'graph {\n';
197 mcdDot
+= 'rankdir="LR";\n';
199 if (mcdStyle
== "compact")
200 mcdDot
+= 'node [shape=plaintext];\n';
201 _
.shuffle(Object
.keys(this.entities
)).forEach( name
=> {
202 if (mcdStyle
== "bubble")
204 mcdDot
+= '"' + name
+ '" [shape=rectangle, label="' + name
+ '"';
205 if (this.entities
[name
].weak
)
206 mcdDot
+= ', peripheries=2';
208 if (!!this.entities
[name
].attributes
)
210 this.entities
[name
].attributes
.forEach( a
=> {
211 let label
= (a
.isKey
? '#' : '') + a
.name
;
212 let attrName
= name
+ '_' + a
.name
;
213 mcdDot
+= '"' + attrName
+ '" [shape=ellipse, label="' + label
+ '"];\n';
214 if (Math
.random() < 0.5)
215 mcdDot
+= '"' + attrName
+ '" -- "' + name
+ '";\n';
217 mcdDot
+= '"' + name
+ '" -- "' + attrName
+ '";\n';
223 mcdDot
+= '"' + name
+ '" [label=<';
224 if (this.entities
[name
].weak
)
226 mcdDot
+= '<table port="name" BORDER="1" ALIGN="LEFT" CELLPADDING="0" CELLSPACING="3" CELLBORDER="0">' +
227 '<tr><td><table BORDER="1" ALIGN="LEFT" CELLPADDING="5" CELLSPACING="0">\n';
230 mcdDot
+= '<table port="name" BORDER="1" ALIGN="LEFT" CELLPADDING="5" CELLSPACING="0">\n';
231 mcdDot
+= '<tr><td BGCOLOR="#ae7d4e" BORDER="0"><font COLOR="#FFFFFF">' + name
+ '</font></td></tr>\n';
232 if (!!this.entities
[name
].attributes
)
234 this.entities
[name
].attributes
.forEach( a
=> {
235 let label
= (a
.isKey
? '<u>' : '') + a
.name
+ (a
.isKey
? '</u>' : '');
236 mcdDot
+= '<tr><td BGCOLOR="#FFFFFF" BORDER="0" ALIGN="LEFT"><font COLOR="#000000" >' + label
+ '</font></td></tr>\n';
239 mcdDot
+= '</table>';
240 if (this.entities
[name
].weak
)
241 mcdDot
+= '</td></tr></table>';
246 _
.shuffle(this.inheritances
).forEach( i
=> {
247 // TODO: node shape = triangle fill yellow. See
248 // https://merise.developpez.com/faq/?page=MCD#CIF-ou-dependance-fonctionnelle-de-A-a-Z
249 // https://merise.developpez.com/faq/?page=MLD#Comment-transformer-un-MCD-en-MLD
250 // https://www.developpez.net/forums/d1088964/general-developpement/alm/modelisation/structure-agregation-l-association-d-association/
251 _
.shuffle(i
.children
).forEach( c
=> {
252 if (Math
.random() < 0.5)
253 mcdDot
+= '"' + c
+ '":name -- "' + i
.parent
;
255 mcdDot
+= '"' + i
.parent
+ '":name -- "' + c
;
256 mcdDot
+= '":name [dir="forward", arrowhead="vee", style="dashed"];\n';
260 if (mcdStyle
== "compact")
261 mcdDot
+= 'node [shape=rectangle, style=rounded];\n';
262 let assoceCounter
= 0;
263 _
.shuffle(this.associations
).forEach( a
=> {
264 let name
= !!a
.name
&& a
.name
.length
> 0
266 : '_assoce' + assoceCounter
++;
267 if (mcdStyle
== "bubble")
269 mcdDot
+= '"' + name
+ '" [shape="diamond", style="filled", color="lightgrey", label="' + name
+ '"';
271 mcdDot
+= ', peripheries=2';
275 a
.attributes
.forEach( attr
=> {
276 let label
= (attr
.isKey
? '#' : '') + attr
.name
;
277 mcdDot
+= '"' + name
+ '_' + attr
.name
+ '" [shape=ellipse, label="' + label
+ '"];\n';
278 let attrName
= name
+ '_' + attr
.name
;
279 if (Math
.random() < 0.5)
280 mcdDot
+= '"' + attrName
+ '" -- "' + name
+ '";\n';
282 mcdDot
+= '"' + name
+ '" -- "' + attrName
+ '";\n';
291 a
.attributes
.forEach( attr
=> {
292 let attrLabel
= (attr
.isKey
? '#' : '') + attr
.name
;
293 label
+= '\\n<' + attrLabel
+ '>';
296 mcdDot
+= '"' + name
+ '" [color="lightgrey", label="' + label
+ '"';
298 mcdDot
+= ', peripheries=2';
301 _
.shuffle(a
.entities
).forEach( e
=> {
302 if (Math
.random() < 0.5)
303 mcdDot
+= '"' + e
.name
+ '":name -- "' + name
+ '"';
305 mcdDot
+= '"' + name
+ '" -- "' + e
.name
+ '":name';
306 mcdDot
+= '[label="' + ErDiags
.CARDINAL
[e
.card
] + '"];\n';
311 ErDiags
.AjaxGet(mcdDot
, graphSvg
=> {
312 this.mcdGraph
= graphSvg
;
313 element
.innerHTML
= graphSvg
;
317 // "Modèle logique des données"
318 // TODO: this one should draw links from foreign keys to keys (port=... in <TD>)
321 let element
= document
.getElementById(id
);
322 if (this.mldGraph
.length
> 0)
324 element
.innerHTML
= this.mcdGraph
;
327 // Build dot graph input
328 let mldDot
= 'graph {\n';
330 Object
.keys(this.entities
).forEach( name
=> {
331 //mld. ... --> devient table
335 this.associations
.forEach( a
=> {
336 a
.entities
.forEach( e
=> { // e.card e.name ...
337 // Pass 1 : entites deviennent tables
338 // Pass 2 : sur les assoces
339 // multi-arite : sub-loop si 0,1 ou 1,1 : aspiré comme attribut de l'association (phase 1)
340 // ensuite, que du 0,n ou 1,n : si == 1, OK une table
341 // si 2 ou + : n tables + 1 pour l'assoce, avec attrs clés étrangères
342 // clé étrangère NOT NULL si 1,1
345 // this.graphMld = ...
346 //console.log(mldDot);
347 ErDiags
.AjaxGet(mldDot
, graphSvg
=> {
348 this.mldGraph
= graphSvg
;
349 element
.innerHTML
= graphSvg
;
355 let element
= document
.getElementById(id
);
356 if (this.sqlText
.length
> 0)
358 element
.innerHTML
= this.sqlText
;
361 //UNIMPLEMENTED (should be straightforward from MLD)