1 /*
2 This file is a part of
3 QVGE - Qt Visual Graph Editor
4
5 (c) 2016-2021 Ars L. Masiuk (ars.masiuk@gmail.com)
6
7 It can be used freely, maintaining the information above.
8 */
9
10 #include "CFormatPlainDOT.h"
11
12 #include <QFile>
13 #include <QDebug>
14 #include <QTextStream>
15 #include <QFont>
16
17
18 // helpers
19
fromDotNodeShape(const QString & shape)20 static QString fromDotNodeShape(const QString& shape)
21 {
22 // rename to conform dot
23 if (shape == "ellipse") return "disc";
24 if (shape == "rect" || shape == "box" ) return "square";
25 if (shape == "invtriangle") return "triangle2";
26
27 // else take original
28 return shape;
29 }
30
31
fromDotNodeStyle(const QString & style,GraphAttributes & nodeAttr)32 static void fromDotNodeStyle(const QString& style, GraphAttributes& nodeAttr)
33 {
34 if (style.contains("dashed"))
35 nodeAttr["stroke.style"] = "dashed";
36 else
37 if (style.contains("dotted"))
38 nodeAttr["stroke.style"] = "dotted";
39
40 if (style.contains("invis"))
41 nodeAttr["stroke.size"] = 0;
42 else
43 if (style.contains("solid"))
44 nodeAttr["stroke.size"] = 1;
45 else
46 if (style.contains("bold"))
47 nodeAttr["stroke.size"] = 3;
48 }
49
50
51 class QStringRefsConstIterator
52 {
53 public:
QStringRefsConstIterator(const QStringList & refs)54 QStringRefsConstIterator(const QStringList& refs): m_refs(refs)
55 {
56 m_pos = (m_refs.size()) ? 0 : -1;
57 }
58
pos() const59 int pos() const
60 {
61 return m_pos;
62 }
63
restCount() const64 int restCount() const
65 {
66 return canNext() ? m_refs.count() - m_pos : 0;
67 }
68
canNext() const69 bool canNext() const
70 {
71 return (m_pos >= 0 && m_pos < m_refs.count());
72 }
73
next()74 bool next()
75 {
76 if (canNext())
77 {
78 m_pos++;
79 return true;
80 }
81
82 return false;
83 }
84
next(float & f)85 bool next(float &f)
86 {
87 if (canNext())
88 {
89 bool ok = false;
90 float tf = m_refs.at(m_pos++).toFloat(&ok);
91 if (ok)
92 {
93 f = tf;
94 return true;
95 }
96 }
97
98 return false;
99 }
100
next(int & i)101 bool next(int &i)
102 {
103 if (canNext())
104 {
105 bool ok = false;
106 int ti = m_refs.at(m_pos++).toInt(&ok);
107 if (ok)
108 {
109 i = ti;
110 return true;
111 }
112 }
113
114 return false;
115 }
116
next(QString & s)117 bool next(QString &s)
118 {
119 if (canNext())
120 {
121 s = m_refs.at(m_pos++);
122 return true;
123 }
124
125 return false;
126 }
127
next(QByteArray & s)128 bool next(QByteArray &s)
129 {
130 if (canNext())
131 {
132 s = m_refs.at(m_pos++).toUtf8();
133 return true;
134 }
135
136 return false;
137 }
138
139 private:
140 const QStringList& m_refs;
141 int m_pos = -1;
142 };
143
144
145 // parsing plain dot
146
parseLine(QTextStream & ts)147 static QStringList parseLine(QTextStream &ts)
148 {
149 QStringList tokens;
150
151 // normal mode
152 while (!ts.atEnd())
153 {
154 QString line = ts.readLine().trimmed();
155 if (line.isEmpty())
156 continue;
157
158 // add EOL
159 line += '\0';
160
161 int i = 0;
162
163 _loop:
164 // skip to next token
165 while (line[i].isSpace())
166 i++;
167 if (line[i] == '\0')
168 break;
169
170 // check for "
171 if (line[i] == '"')
172 {
173 i++;
174 QString token;
175 while (line[i] != '"' && line[i] != '\0')
176 token += line[i++];
177 tokens << token;
178
179 goto _loop;
180 }
181
182 // check for < + eol
183 if (line[i] == '<' && line[i+1] == '\0')
184 {
185 QString token;
186 while (!ts.atEnd())
187 {
188 QString l = ts.readLine();
189 QString lt = l.trimmed() + '\0';
190 if (lt[0] == '>' && (lt[1].isSpace() || lt[1] == '\0'))
191 {
192 tokens << token;
193 line = lt;
194 i = 1;
195 goto _loop;
196 }
197 else
198 {
199 token += l + "\n";
200 }
201 }
202 }
203
204 // read normal tokens
205 QString token;
206 while (line[i] != '\0' && !line[i].isSpace())
207 token += line[i++];
208
209 tokens << token;
210 goto _loop;
211 }
212
213 return tokens;
214 }
215
216
splitEdgePortIds(QByteArray & nodeId,QByteArray & portId)217 static void splitEdgePortIds(QByteArray& nodeId, QByteArray& portId)
218 {
219 int index = nodeId.indexOf(':');
220 if (index <= 0)
221 {
222 portId.clear();
223 return;
224 }
225
226 portId = nodeId.mid(index + 1);
227 nodeId = nodeId.left(index);
228 }
229
230
231 // reimp
232
load(const QString & fileName,Graph & g,QString * lastError) const233 bool CFormatPlainDOT::load(const QString& fileName, Graph& g, QString* lastError) const
234 {
235 GraphInternal gi;
236 gi.g = &g;
237
238 QFile f(fileName);
239 if (!f.open(QFile::ReadOnly))
240 {
241 if (lastError)
242 *lastError = QObject::tr("Cannot open file");
243
244 return false;
245 }
246
247 QTextStream ts(&f);
248
249 while (!ts.atEnd())
250 {
251 auto refs = parseLine(ts);
252 if (refs.first() == "stop")
253 break;
254
255 if (refs.first() == "graph")
256 {
257 parseGraph(refs, gi);
258 continue;
259 }
260
261 if (refs.first() == "node")
262 {
263 parseNode(refs, gi);
264 continue;
265 }
266
267 if (refs.first() == "edge")
268 {
269 parseEdge(refs, gi);
270 continue;
271 }
272 }
273
274 // done
275 f.close();
276
277 return true;
278 }
279
280
save(const QString & fileName,Graph & g,QString * lastError) const281 bool CFormatPlainDOT::save(const QString& fileName, Graph& g, QString* lastError) const
282 {
283 return false;
284 }
285
286
287 // privates
288
parseGraph(const QStringList & refs,GraphInternal & gi) const289 bool CFormatPlainDOT::parseGraph(const QStringList &refs, GraphInternal &gi) const
290 {
291 QStringRefsConstIterator rit(refs);
292 rit.next(); // skip header
293 rit.next(gi.g_scale);
294 rit.next(gi.g_x);
295 rit.next(gi.g_y);
296
297 return true;
298 }
299
300
parseNode(const QStringList & refs,GraphInternal & gi) const301 bool CFormatPlainDOT::parseNode(const QStringList &refs, GraphInternal &gi) const
302 {
303 QStringRefsConstIterator rit(refs);
304 rit.next(); // skip header
305
306 Node node;
307 rit.next(node.id);
308
309 QString label, style, shape, color, fillcolor;
310 float x, y, width, height;
311
312 rit.next(x);
313 rit.next(y);
314 rit.next(width);
315 rit.next(height);
316
317 rit.next(label);
318 rit.next(style);
319 rit.next(shape);
320 rit.next(color);
321 rit.next(fillcolor);
322
323 node.attrs["x"] = x * 72.0 * gi.g_scale;
324 node.attrs["y"] = y * 72.0 * gi.g_scale;
325 node.attrs["width"] = width * 72.0 * gi.g_scale;
326 node.attrs["height"] = height * 72.0 * gi.g_scale;
327
328 label = label.replace("\\n", "\n");
329 node.attrs["label"] = label;
330 node.attrs["shape"] = fromDotNodeShape(shape);
331 fromDotNodeStyle(style, node.attrs);
332 node.attrs["color"] = fillcolor;
333 node.attrs["stroke.color"] = color;
334
335 gi.g->nodes << node;
336
337 return true;
338 }
339
340
parseEdge(const QStringList & refs,GraphInternal & gi) const341 bool CFormatPlainDOT::parseEdge(const QStringList &refs, GraphInternal &gi) const
342 {
343 QStringRefsConstIterator rit(refs);
344 rit.next(); // skip header
345
346 Edge edge;
347 rit.next(edge.startNodeId);
348 rit.next(edge.endNodeId);
349
350 int jointCount = 0;
351 float x, y;
352 rit.next(jointCount);
353 if (jointCount > 0)
354 {
355 QString points;
356 for (int i = 0; i < jointCount; ++i)
357 {
358 rit.next(x);
359 rit.next(y);
360 x = x * 72.0 * gi.g_scale;
361 y = y * 72.0 * gi.g_scale;
362 points += QString("%1 %2 ").arg(x).arg(y);
363 }
364 //edge.attrs["points"] = points;
365 }
366
367
368 if (rit.restCount() > 2)
369 {
370 QString label;
371 rit.next(label);
372 label = label.replace("\\n", "\n");
373
374 rit.next(x);
375 rit.next(y);
376 edge.attrs["label"] = label;
377 edge.attrs["label.x"] = x * 72.0 * gi.g_scale;
378 edge.attrs["label.y"] = y * 72.0 * gi.g_scale;
379
380 edge.id = label.toUtf8();
381 }
382
383 if (rit.canNext())
384 {
385 QString style; rit.next(style);
386 edge.attrs["style"] = style;
387 }
388
389 if (rit.canNext())
390 {
391 QString color; rit.next(color);
392 edge.attrs["color"] = color;
393 }
394
395
396 // check id
397 if (edge.id.isEmpty())
398 {
399 edge.id = edge.startNodeId + "-" + edge.endNodeId;
400 }
401
402
403 // split ports if any
404 splitEdgePortIds(edge.startNodeId, edge.startPortId);
405 splitEdgePortIds(edge.endNodeId, edge.endPortId);
406
407
408 gi.g->edges << edge;
409
410 return true;
411 }
412
413