1 // ER diagram description parser
4 constructor(description
)
7 this.inheritances
= [];
8 this.associations
= [];
9 this.txt2json(description
);
10 // Cache SVG graphs returned by server (in addition to server cache = good perfs)
18 // SQLite types without null (TODO: be more general)
19 return ["integer","real","text","blob"];
36 // Parse a textual description into a json object
39 let lines
= text
.split("\n");
40 lines
.push(""); //easier parsing: always empty line at the end
42 for (let i
=0; i
< lines
.length
; i
++)
44 lines
[i
] = lines
[i
].trim();
46 if (lines
[i
].length
== 0)
48 if (start
>= 0) //there is some group of lines to parse
50 this.parseThing(lines
, start
, i
);
54 else //not empty line: just register starting point
62 // Parse a group of lines into entity, association, ...
63 parseThing(lines
, start
, end
) //start included, end excluded
65 switch (lines
[start
].charAt(0))
68 // Entity = { name: { attributes, [weak] } }
69 let name
= lines
[start
].match(/\w+/)[0];
70 let entity
= { attributes: this.parseAttributes(lines
, start
+1, end
) };
71 if (lines
[start
].charAt(1) == '[')
73 this.entities
[name
] = entity
;
75 case 'i': //inheritance (arrows)
76 this.inheritances
= this.inheritances
.concat(this.parseInheritance(lines
, start
+1, end
));
78 case '{': //association
79 // Association = { [name], [attributes], [weak], entities: ArrayOf entity indices }
80 let relationship
= { };
81 let nameRes
= lines
[start
].match(/\w+/);
83 relationship
.name
= nameRes
[0];
84 if (lines
[start
].charAt(1) == '{')
85 relationship
.weak
= true;
86 this.associations
.push(Object
.assign({}, relationship
, this.parseAssociation(lines
, start
+1, end
)));
91 // attributes: ArrayOf {name, [isKey], [type], [qualifiers]}
92 parseAttributes(lines
, start
, end
)
95 for (let i
=start
; i
<end
; i
++)
97 let field
= { name: lines
[i
].match(/\w+/)[0] };
98 if (lines
[i
].charAt(0) == '#')
100 let parenthesis
= lines
[i
].match(/\((.+)\)/);
101 if (parenthesis
!== null)
103 let sqlClues
= parenthesis
[1];
104 let qualifiers
= sqlClues
;
105 let firstWord
= sqlClues
.match(/\w+/)[0];
106 if (ErDiags
.TYPES
.includes(firstWord
))
108 field
.type
= firstWord
;
109 qualifiers
= sqlClues
.substring(firstWord
.length
).trim();
111 field
.qualifiers
= qualifiers
;
113 attributes
.push(field
);
118 // GroupOf Inheritance: { parent, children: ArrayOf entity indices }
119 parseInheritance(lines
, start
, end
)
121 let inheritance
= [];
122 for (let i
=start
; i
<end
; i
++)
124 let lineParts
= lines
[i
].split(" ");
126 for (let j
=1; j
<lineParts
.length
; j
++)
127 children
.push(lineParts
[j
]);
128 inheritance
.push({ parent:lineParts
[0], children: children
});
133 // Association (parsed here): { entities: ArrayOf entity names + cardinality, [attributes: ArrayOf {name, [isKey], [type], [qualifiers]}] }
134 parseAssociation(lines
, start
, end
)
141 if (lines
[i
].charAt(0) == '-')
143 assoce
.attributes
= this.parseAttributes(lines
, i
+1, end
);
148 // Read entity name + cardinality
149 let lineParts
= lines
[i
].split(" ");
150 entities
.push({ name:lineParts
[0], card:lineParts
[1] });
154 assoce
.entities
= entities
;
162 static AjaxGet(dotInput
, callback
)
164 let xhr
= new XMLHttpRequest();
165 xhr
.onreadystatechange = function() {
166 if (this.readyState
== 4 && this.status
== 200)
167 callback(this.responseText
);
169 xhr
.open("GET", "scripts/getGraphSvg.php?dot=" + encodeURIComponent(dotInput
), true);
173 // "Modèle conceptuel des données". TODO: option for graph size
174 drawMcd(id
, mcdStyle
) //mcdStyle: bubble, or compact
176 let element
= document
.getElementById(id
);
177 mcdStyle
= mcdStyle
|| "compact";
178 if (this.mcdGraph
.length
> 0)
180 element
.innerHTML
= this.mcdGraph
;
183 // Build dot graph input
184 let mcdDot
= 'graph {\n';
186 Object
.keys(this.entities
).forEach( name
=> {
187 if (mcdStyle
== "bubble")
189 mcdDot
+= name
+ '[shape=rectangle, label="' + name
+ '"';
190 if (this.entities
[name
].weak
)
191 mcdDot
+= ', peripheries=2';
193 if (!!this.entities
[name
].attributes
)
195 this.entities
[name
].attributes
.forEach( a
=> {
196 let label
= (a
.isKey
? '#' : '') + a
.name
;
197 mcdDot
+= name
+ '_' + a
.name
+ '[shape=ellipse, label="' + label
+ '"];\n';
198 mcdDot
+= name
+ '_' + a
.name
+ ' -- ' + name
+ ';\n';
204 mcdDot
+= name
+ '[shape=plaintext, label=<';
205 if (this.entities
[name
].weak
)
207 mcdDot
+= '<table port="name" BORDER="1" ALIGN="LEFT" CELLPADDING="0" CELLSPACING="3" CELLBORDER="0">' +
208 '<tr><td><table BORDER="1" ALIGN="LEFT" CELLPADDING="5" CELLSPACING="0">\n';
211 mcdDot
+= '<table port="name" BORDER="1" ALIGN="LEFT" CELLPADDING="5" CELLSPACING="0">\n';
212 mcdDot
+= '<tr><td BGCOLOR="#ae7d4e" BORDER="0"><font COLOR="#FFFFFF">' + name
+ '</font></td></tr>\n';
213 if (!!this.entities
[name
].attributes
)
215 this.entities
[name
].attributes
.forEach( a
=> {
216 let label
= (a
.isKey
? '<u>' : '') + a
.name
+ (a
.isKey
? '</u>' : '');
217 mcdDot
+= '<tr><td BGCOLOR="#FFFFFF" BORDER="0" ALIGN="LEFT"><font COLOR="#000000" >' + label
+ '</font></td></tr>\n';
220 mcdDot
+= '</table>';
221 if (this.entities
[name
].weak
)
222 mcdDot
+= '</td></tr></table>';
227 this.inheritances
.forEach( i
=> {
228 i
.children
.forEach( c
=> {
229 mcdDot
+= c
+ ':name -- ' + i
.parent
+ ':name [len="1.00", dir="forward", arrowhead="vee", style="dashed"];\n';
233 let assoceCounter
= 0;
234 this.associations
.forEach( a
=> {
235 let name
= !!a
.name
&& a
.name
.length
> 0
237 : '_assoce' + assoceCounter
++;
238 mcdDot
+= name
+ '[shape="diamond", style="filled", color="lightgrey", label="' + (!!a
.name
? a
.name : '') + '"';
240 mcdDot
+= ', peripheries=2';
242 a
.entities
.forEach( e
=> {
243 mcdDot
+= e
.name
+ ':name -- ' + name
+ '[len="1.00", label="' + ErDiags
.CARDINAL
[e
.card
] + '"];\n';
247 a
.attributes
.forEach( attr
=> {
248 let label
= (attr
.isKey
? '#' : '') + attr
.name
;
249 mcdDot
+= name
+ '_' + attr
.name
+ '[len="1.00", shape=ellipse, label="' + label
+ '"];\n';
250 mcdDot
+= name
+ '_' + attr
.name
+ ' -- ' + name
+ ';\n';
255 //console.log(mcdDot);
256 ErDiags
.AjaxGet(mcdDot
, graphSvg
=> {
257 this.mcdGraph
= graphSvg
;
258 element
.innerHTML
= graphSvg
;
262 // "Modèle logique des données"
265 let element
= document
.getElementById(id
);
266 if (this.mldGraph
.length
> 0)
268 element
.innerHTML
= this.mcdGraph
;
272 // TODO: analyze cardinalities (eat attributes, create new tables...)
274 // this.graphMld = ...
279 let element
= document
.getElementById(id
);
280 if (this.sqlText
.length
> 0)
282 element
.innerHTML
= this.sqlText
;
285 //UNIMPLEMENTED (should be straightforward from MLD)