summaryrefslogtreecommitdiffstats
path: root/umbrello/umbrello/import_rose.cpp
blob: ff26e653213e844dca8c298e28990a713cef5f53 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
/***************************************************************************
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   copyright (C) 2006                                                    *
 *   Umbrello UML Modeller Authors <uml-devel@uml.sf.net>                  *
 ***************************************************************************/

// own header
#include "import_rose.h"

// qt includes
#include <tqstring.h>
#include <tqtextstream.h>
#include <tqptrlist.h>
#include <tqstringlist.h>
#include <tqregexp.h>
#include <tqmessagebox.h>
#include <klocale.h>
#include <tdeapplication.h>
#include <kdebug.h>
// app includes
#include "petalnode.h"
#include "petaltree2uml.h"
#include "umlnamespace.h"  // only for the KDE4 compatibility macros

namespace Import_Rose {

typedef TQPtrList<PetalNode> PetalNodeList;

uint nClosures; // Multiple closing parentheses may appear on a single
                // line. The parsing is done line-by-line and using
                // recursive descent. This means that we can only handle
                // _one_ closing parenthesis at a time, i.e. the closing
                // of the currently parsed node. Since we may see more
                // closing parentheses than we can handle, we need a
                // counter indicating how many additional node closings
                // have been seen.

uint linum;  // line number
TQString g_methodName;
void methodName(const TQString& m) {
    g_methodName = m;
}
/**
 * Auxiliary function for diagnostics: Return current location.
 */
TQString loc() {
    return "Import_Rose::" + g_methodName + " line " + TQString::number(linum) + ": ";
}

/**
 * Split a line into lexemes.
 */
TQStringList scan(const TQString& lin) {
    TQStringList result;
    TQString line = lin.stripWhiteSpace();
    if (line.isEmpty())
        return result;  // empty
    TQString lexeme;
    const uint len = line.length();
    bool inString = false;
    for (uint i = 0; i < len; i++) {
        TQChar c = line[i];
        if (c == '"') {
            lexeme += c;
            if (inString) {
                result.append(lexeme);
                lexeme = TQString();
            }
            inString = !inString;
        } else if (inString ||
                   c.isLetterOrNumber() || c == '_' || c == '@') {
            lexeme += c;
        } else {
            if (!lexeme.isEmpty()) {
                result.append(lexeme);
                lexeme = TQString();
            }
            if (! c.isSpace()) {
                result.append(TQString(c));
            }
        }
    }
    if (!lexeme.isEmpty())
        result.append(lexeme);
    return result;
}

/**
 * Emulate perl shift().
 */
TQString shift(TQStringList& l) {
    TQString first = l.first();
    l.pop_front();
    return first;
}

/**
 * Check for closing of one or more scopes.
 */
bool checkClosing(TQStringList& tokens) {
    if (tokens.count() == 0)
        return false;
    if (tokens.last() == ")") {
        // For a single closing parenthesis, we just return true.
        // But if there are more closing parentheses, we need to increment
        // nClosures for each scope.
        tokens.pop_back();
        while (tokens.count() && tokens.last() == ")") {
            nClosures++;
            tokens.pop_back();
        }
        return true;
    }
    return false;
}

/**
 * Immediate values are numbers or quoted strings.
 * @return  True if the given text is a natural or negative number
 *          or a quoted string.
 */
bool isImmediateValue(TQString s) {
    return s.contains(TQRegExp("^[\\d\\-\"]"));
}

/**
 * Extract immediate values out of `l'.
 * Examples of immediate value lists:
 *   number list:     ( 123 , 456 )
 *   string list:     ( "SomeText" 888 )
 * Any enclosing parentheses are removed.
 * All extracted items are also removed from `l'.
 * For the example given above the following is returned:
 *   "123 456"
 * or
 *   "\"SomeText\" 888"
 */
TQString extractImmediateValues(TQStringList& l) {
    if (l.count() == 0)
        return TQString();
    if (l.first() == "(")
        l.pop_front();
    TQString result;
    bool start = true;
    while (l.count() && isImmediateValue(l.first())) {
        if (start)
            start = false;
        else
            result += ' ';
        result += shift(l);
        if (l.first() == ",")
            l.pop_front();
    }
    if (l.first() == ")")
        l.pop_front();
    while (l.count() && l.first() == ")") {
        nClosures++;
        l.pop_front();
    }
    return result;
}

TQString collectVerbatimText(TQTextStream& stream) {
    TQString result;
    TQString line;
    methodName("collectVerbatimText");
    while (!(line = stream.readLine()).isNull()) {
        linum++;
        line = line.stripWhiteSpace();
        if (line.isEmpty() || line.startsWith(")"))
            break;
        if (line[0] != '|') {
            kError() << loc() << "expecting '|' at start of verbatim text" << endl;
            return TQString();
        } else {
            result += line.mid(1) + '\n';
        }
    }
    if (line.isNull()) {
        kError() << loc() << "premature EOF" << endl;
        return TQString();
    }
    if (! line.isEmpty()) {
        for (uint i = 0; i < line.length(); i++) {
            const TQChar& clParenth = line[i];
            if (clParenth != ')') {
                kError() << loc() << "expected ')', found: " << clParenth << endl;
                return TQString();
            }
            nClosures++;
        }
    }
    return result;
}

/**
 * Extract the stripped down value from a (value ...) element.
 * Example: for the input
 *            (value Text "This is some text")
 *          the following is extracted:
 *            "This is some text"
 * Extracted elements and syntactic sugar of the value element are
 * removed from the input list.
 * The stream is passed into this method because it may be necessary
 * to read new lines - in the case of verbatim text.
 * The format of verbatim text in petal files is as follows:
 *
 *         (value Text
 * |This is the first line of verbatim text.
 * |This is another line of verbatim text.
 *         )
 * (The '|' character is supposed to be in the first column of the line)
 * In this case the two lines are extracted without the leading '|'.
 * The line ending '\n' of each line is preserved.
 */
TQString extractValue(TQStringList& l, TQTextStream& stream) {
    methodName("extractValue");
    if (l.count() == 0)
        return TQString();
    if (l.first() == "(")
        l.pop_front();
    if (l.first() != "value")
        return TQString();
    l.pop_front();  // remove "value"
    l.pop_front();  // remove the value type: could be e.g. "Text" or "cardinality"
    TQString result;
    if (l.count() == 0) {  // expect verbatim text to follow on subsequent lines
        TQString text = collectVerbatimText(stream);
        nClosures--;  // expect own closure
        return text;
    } else {
        result = shift(l);
        if (l.first() != ")") {
            kError() << loc() << "expecting closing parenthesis" << endl;
            return result;
        }
        l.pop_front();
    }
    while (l.count() && l.first() == ")") {
        nClosures++;
        l.pop_front();
    }
    return result;
}

/**
 * Read attributes of a node.
 * @param initialArgs  Tokens on the line of the opening "(" of the node
 *                   but with leading whitespace and the opening "(" removed.
 * @param stream     The TQTextStream from which to read following lines.
 * @return           Pointer to the created PetalNode or NULL on error.
 */
PetalNode *readAttributes(TQStringList initialArgs, TQTextStream& stream) {
    methodName("readAttributes");
    if (initialArgs.count() == 0) {
        kError() << loc() << "initialArgs is empty" << endl;
        return NULL;
    }
    PetalNode::NodeType nt;
    TQString type = shift(initialArgs);
    if (type == "object")
        nt = PetalNode::nt_object;
    else if (type == "list")
        nt = PetalNode::nt_list;
    else {
        kError() << loc() << "unknown node type " << type << endl;
        return NULL;
    }
    PetalNode *node = new PetalNode(nt);
    bool seenClosing = checkClosing(initialArgs);
    node->setInitialArgs(initialArgs);
    if (seenClosing)
        return node;
    PetalNode::NameValueList attrs;
    TQString line;
    while (!(line = stream.readLine()).isNull()) {
        linum++;
        line = line.stripWhiteSpace();
        if (line.isEmpty())
            continue;
        TQStringList tokens = scan(line);
        TQString stringOrNodeOpener = shift(tokens);
        TQString name;
        if (nt == PetalNode::nt_object && !stringOrNodeOpener.contains(TQRegExp("^[A-Za-z]"))) {
            kError() << loc() << "unexpected line " << line << endl;
            return NULL;
        }
        PetalNode::StringOrNode value;
        if (nt == PetalNode::nt_object) {
            name = stringOrNodeOpener;
            if (tokens.count() == 0) {  // expect verbatim text to follow on subsequent lines
                value.string = collectVerbatimText(stream);
                PetalNode::NameValue attr(name, value);
                attrs.append(attr);
                if (nClosures) {
                    // Decrement nClosures exactly once, namely for the own scope.
                    // Each recursion of readAttributes() is only responsible for
                    // its own scope. I.e. each further scope closing is handled by
                    // an outer recursion in case of multiple closing parentheses.
                    nClosures--;
                    break;
                }
                continue;
            }
            stringOrNodeOpener = shift(tokens);
        } else if (stringOrNodeOpener != "(") {
            value.string = stringOrNodeOpener;
            PetalNode::NameValue attr;
            attr.second = value;
            attrs.append(attr);
            if (tokens.count() && tokens.first() != ")") {
                kDebug() << loc()
                    << "NYI - immediate list entry with more than one item" << endl;
            }
            if (checkClosing(tokens))
                break;
            continue;
        }
        if (stringOrNodeOpener == "(") {
            TQString nxt = tokens.first();
            if (isImmediateValue(nxt)) {
                value.string = extractImmediateValues(tokens);
            } else if (nxt == "value" || nxt.startsWith("\"")) {
                value.string = extractValue(tokens, stream);
            } else {
                kapp->processEvents();
                value.node = readAttributes(tokens, stream);
                if (value.node == NULL)
                    return NULL;
            }
            PetalNode::NameValue attr(name, value);
            attrs.append(attr);
            if (nClosures) {
                // Decrement nClosures exactly once, namely for the own scope.
                // Each recursion of readAttributes() is only responsible for
                // its own scope. I.e. each further scope closing is handled by
                // an outer recursion in case of multiple closing parentheses.
                nClosures--;
                break;
            }
        } else {
            value.string = stringOrNodeOpener;
            bool seenClosing = checkClosing(tokens);
            PetalNode::NameValue attr(name, value);
            attrs.append(attr);
            if (seenClosing) {
                break;
            }
        }
    }
    node->setAttributes(attrs);
    return node;
}

bool loadFromMDL(TQIODevice& file) {
    TQTextStream stream(&file);
    stream.setEncoding(TQTextStream::Latin1);
    TQString line;
    PetalNode *root = NULL;
    linum = 0;
    while (!(line = stream.readLine()).isNull()) {
        linum++;
        if (line.contains( TQRegExp("^\\s*\\(object Petal") )) {
            while (!(line = stream.readLine()).isNull() && !line.contains(')')) {
                linum++; // CHECK: do we need petal version info?
            }
            if (line.isNull())
                break;
        } else {
            TQRegExp objectRx("^\\s*\\(object ");
            if (line.contains(objectRx)) {
                nClosures = 0;
                TQStringList initialArgs = scan(line);
                initialArgs.pop_front();  // remove opening parenthesis
                root = readAttributes(initialArgs, stream);
            }
        }
    }
    file.close();
    if (root == NULL)
        return false;
    return petalTree2Uml(root);
}

}