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 drawMcd(id
, mcdStyle
) //mcdStyle: bubble, or compact
182 let element
= document
.getElementById(id
);
183 mcdStyle
= mcdStyle
|| "compact";
184 if (this.mcdGraph
.length
> 0)
186 element
.innerHTML
= this.mcdGraph
;
189 // Build dot graph input
190 let mcdDot
= 'graph {\n';
192 Object
.keys(this.entities
).forEach( name
=> {
193 if (mcdStyle
== "bubble")
195 mcdDot
+= name
+ '[shape=rectangle, label="' + name
+ '"';
196 if (this.entities
[name
].weak
)
197 mcdDot
+= ', peripheries=2';
199 if (!!this.entities
[name
].attributes
)
201 this.entities
[name
].attributes
.forEach( a
=> {
202 let label
= (a
.isKey
? '#' : '') + a
.name
;
203 mcdDot
+= name
+ '_' + a
.name
+ '[shape=ellipse, label="' + label
+ '"];\n';
204 mcdDot
+= name
+ '_' + a
.name
+ ' -- ' + name
+ ';\n';
210 mcdDot
+= name
+ '[shape=plaintext, label=<';
211 if (this.entities
[name
].weak
)
213 mcdDot
+= '<table port="name" BORDER="1" ALIGN="LEFT" CELLPADDING="0" CELLSPACING="3" CELLBORDER="0">' +
214 '<tr><td><table BORDER="1" ALIGN="LEFT" CELLPADDING="5" CELLSPACING="0">\n';
217 mcdDot
+= '<table port="name" BORDER="1" ALIGN="LEFT" CELLPADDING="5" CELLSPACING="0">\n';
218 mcdDot
+= '<tr><td BGCOLOR="#ae7d4e" BORDER="0"><font COLOR="#FFFFFF">' + name
+ '</font></td></tr>\n';
219 if (!!this.entities
[name
].attributes
)
221 this.entities
[name
].attributes
.forEach( a
=> {
222 let label
= (a
.isKey
? '<u>' : '') + a
.name
+ (a
.isKey
? '</u>' : '');
223 mcdDot
+= '<tr><td BGCOLOR="#FFFFFF" BORDER="0" ALIGN="LEFT"><font COLOR="#000000" >' + label
+ '</font></td></tr>\n';
226 mcdDot
+= '</table>';
227 if (this.entities
[name
].weak
)
228 mcdDot
+= '</td></tr></table>';
233 this.inheritances
.forEach( i
=> {
234 i
.children
.forEach( c
=> {
235 mcdDot
+= c
+ ':name -- ' + i
.parent
+ ':name [len="1.00", dir="forward", arrowhead="vee", style="dashed"];\n';
239 let assoceCounter
= 0;
240 this.associations
.forEach( a
=> {
241 let name
= !!a
.name
&& a
.name
.length
> 0
243 : '_assoce' + assoceCounter
++;
244 mcdDot
+= name
+ '[shape="diamond", style="filled", color="lightgrey", label="' + (!!a
.name
? a
.name : '') + '"';
246 mcdDot
+= ', peripheries=2';
248 a
.entities
.forEach( e
=> {
249 mcdDot
+= e
.name
+ ':name -- ' + name
+ '[len="1.00", label="' + ErDiags
.CARDINAL
[e
.card
] + '"];\n';
253 a
.attributes
.forEach( attr
=> {
254 let label
= (attr
.isKey
? '#' : '') + attr
.name
;
255 mcdDot
+= name
+ '_' + attr
.name
+ '[len="1.00", shape=ellipse, label="' + label
+ '"];\n';
256 mcdDot
+= name
+ '_' + attr
.name
+ ' -- ' + name
+ ';\n';
261 //console.log(mcdDot);
262 ErDiags
.AjaxGet(mcdDot
, graphSvg
=> {
263 this.mcdGraph
= graphSvg
;
264 element
.innerHTML
= graphSvg
;
268 // "Modèle logique des données"
271 let element
= document
.getElementById(id
);
272 if (this.mldGraph
.length
> 0)
274 element
.innerHTML
= this.mcdGraph
;
277 // Build dot graph input
278 let mldDot
= 'graph {\n';
280 Object
.keys(this.entities
).forEach( name
=> {
281 //mld. ... --> devient table
285 this.associations
.forEach( a
=> {
286 a
.entities
.forEach( e
=> { // e.card e.name ...
287 // Pass 1 : entites deviennent tables
288 // Pass 2 : sur les assoces
289 // multi-arite : sub-loop si 0,1 ou 1,1 : aspiré comme attribut de l'association (phase 1)
290 // ensuite, que du 0,n ou 1,n : si == 1, OK une table
291 // si 2 ou + : n tables + 1 pour l'assoce, avec attrs clés étrangères
292 // clé étrangère NOT NULL si 1,1
295 // this.graphMld = ...
296 //console.log(mldDot);
297 ErDiags
.AjaxGet(mldDot
, graphSvg
=> {
298 this.mldGraph
= graphSvg
;
299 element
.innerHTML
= graphSvg
;
305 let element
= document
.getElementById(id
);
306 if (this.sqlText
.length
> 0)
308 element
.innerHTML
= this.sqlText
;
311 //UNIMPLEMENTED (should be straightforward from MLD)