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 types without null (TODO: be more general)
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(/\w+/)[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(/\w+/);
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
++)
98 let field
= { name: lines
[i
].match(/\w+/)[0] };
99 if (lines
[i
].charAt(0) == '#')
101 let parenthesis
= lines
[i
].match(/\((.+)\)/);
102 if (parenthesis
!== null)
104 let sqlClues
= parenthesis
[1];
105 let qualifiers
= sqlClues
;
106 let firstWord
= sqlClues
.match(/\w+/)[0];
107 if (ErDiags
.TYPES
.includes(firstWord
))
109 field
.type
= firstWord
;
110 qualifiers
= sqlClues
.substring(firstWord
.length
).trim();
112 field
.qualifiers
= qualifiers
;
114 attributes
.push(field
);
119 // GroupOf Inheritance: { parent, children: ArrayOf entity indices }
120 parseInheritance(lines
, start
, end
)
122 let inheritance
= [];
123 for (let i
=start
; i
<end
; i
++)
125 let lineParts
= lines
[i
].split(" ");
127 for (let j
=1; j
<lineParts
.length
; j
++)
128 children
.push(lineParts
[j
]);
129 inheritance
.push({ parent:lineParts
[0], children: children
});
134 // Association (parsed here): { entities: ArrayOf entity names + cardinality, [attributes: ArrayOf {name, [isKey], [type], [qualifiers]}] }
135 parseAssociation(lines
, start
, end
)
142 if (lines
[i
].charAt(0) == '-')
144 assoce
.attributes
= this.parseAttributes(lines
, i
+1, end
);
149 // Read entity name + cardinality
150 let lineParts
= lines
[i
].split(" ");
151 entities
.push({ name:lineParts
[0], card:lineParts
[1] });
155 assoce
.entities
= entities
;
163 static AjaxGet(dotInput
, callback
)
165 let xhr
= new XMLHttpRequest();
166 xhr
.onreadystatechange = function() {
167 if (this.readyState
== 4 && this.status
== 200)
168 callback(this.responseText
);
170 xhr
.open("GET", "scripts/getGraphSvg.php?dot=" + encodeURIComponent(dotInput
), true);
174 // "Modèle conceptuel des données". TODO: option for graph size
175 drawMcd(id
, mcdStyle
) //mcdStyle: bubble, or compact
177 let element
= document
.getElementById(id
);
178 mcdStyle
= mcdStyle
|| "compact";
179 if (this.mcdGraph
.length
> 0)
181 element
.innerHTML
= this.mcdGraph
;
184 // Build dot graph input
185 let mcdDot
= 'graph {\n';
187 Object
.keys(this.entities
).forEach( name
=> {
188 if (mcdStyle
== "bubble")
190 mcdDot
+= name
+ '[shape=rectangle, label="' + name
+ '"';
191 if (this.entities
[name
].weak
)
192 mcdDot
+= ', peripheries=2';
194 if (!!this.entities
[name
].attributes
)
196 this.entities
[name
].attributes
.forEach( a
=> {
197 let label
= (a
.isKey
? '#' : '') + a
.name
;
198 mcdDot
+= name
+ '_' + a
.name
+ '[shape=ellipse, label="' + label
+ '"];\n';
199 mcdDot
+= name
+ '_' + a
.name
+ ' -- ' + name
+ ';\n';
205 mcdDot
+= name
+ '[shape=plaintext, label=<';
206 if (this.entities
[name
].weak
)
208 mcdDot
+= '<table port="name" BORDER="1" ALIGN="LEFT" CELLPADDING="0" CELLSPACING="3" CELLBORDER="0">' +
209 '<tr><td><table BORDER="1" ALIGN="LEFT" CELLPADDING="5" CELLSPACING="0">\n';
212 mcdDot
+= '<table port="name" BORDER="1" ALIGN="LEFT" CELLPADDING="5" CELLSPACING="0">\n';
213 mcdDot
+= '<tr><td BGCOLOR="#ae7d4e" BORDER="0"><font COLOR="#FFFFFF">' + name
+ '</font></td></tr>\n';
214 if (!!this.entities
[name
].attributes
)
216 this.entities
[name
].attributes
.forEach( a
=> {
217 let label
= (a
.isKey
? '<u>' : '') + a
.name
+ (a
.isKey
? '</u>' : '');
218 mcdDot
+= '<tr><td BGCOLOR="#FFFFFF" BORDER="0" ALIGN="LEFT"><font COLOR="#000000" >' + label
+ '</font></td></tr>\n';
221 mcdDot
+= '</table>';
222 if (this.entities
[name
].weak
)
223 mcdDot
+= '</td></tr></table>';
228 this.inheritances
.forEach( i
=> {
229 i
.children
.forEach( c
=> {
230 mcdDot
+= c
+ ':name -- ' + i
.parent
+ ':name [len="1.00", dir="forward", arrowhead="vee", style="dashed"];\n';
234 let assoceCounter
= 0;
235 this.associations
.forEach( a
=> {
236 let name
= !!a
.name
&& a
.name
.length
> 0
238 : '_assoce' + assoceCounter
++;
239 mcdDot
+= name
+ '[shape="diamond", style="filled", color="lightgrey", label="' + (!!a
.name
? a
.name : '') + '"';
241 mcdDot
+= ', peripheries=2';
243 a
.entities
.forEach( e
=> {
244 mcdDot
+= e
.name
+ ':name -- ' + name
+ '[len="1.00", label="' + ErDiags
.CARDINAL
[e
.card
] + '"];\n';
248 a
.attributes
.forEach( attr
=> {
249 let label
= (attr
.isKey
? '#' : '') + attr
.name
;
250 mcdDot
+= name
+ '_' + attr
.name
+ '[len="1.00", shape=ellipse, label="' + label
+ '"];\n';
251 mcdDot
+= name
+ '_' + attr
.name
+ ' -- ' + name
+ ';\n';
256 //console.log(mcdDot);
257 ErDiags
.AjaxGet(mcdDot
, graphSvg
=> {
258 this.mcdGraph
= graphSvg
;
259 element
.innerHTML
= graphSvg
;
263 // "Modèle logique des données"
266 let element
= document
.getElementById(id
);
267 if (this.mldGraph
.length
> 0)
269 element
.innerHTML
= this.mcdGraph
;
272 // Build dot graph input
273 let mldDot
= 'graph {\n';
275 Object
.keys(this.entities
).forEach( name
=> {
276 //mld. ... --> devient table
280 this.associations
.forEach( a
=> {
281 a
.entities
.forEach( e
=> { // e.card e.name ...
282 // Pass 1 : entites deviennent tables
283 // Pass 2 : sur les assoces
284 // multi-arite : sub-loop si 0,1 ou 1,1 : aspiré comme attribut de l'association (phase 1)
285 // ensuite, que du 0,n ou 1,n : si == 1, OK une table
286 // si 2 ou + : n tables + 1 pour l'assoce, avec attrs clés étrangères
287 // clé étrangère NOT NULL si 1,1
290 // this.graphMld = ...
295 let element
= document
.getElementById(id
);
296 if (this.sqlText
.length
> 0)
298 element
.innerHTML
= this.sqlText
;
301 //UNIMPLEMENTED (should be straightforward from MLD)