You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
392 lines
13 KiB
C++
392 lines
13 KiB
C++
/***************************************************************************
|
|
* *
|
|
* 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 <tdelocale.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);
|
|
}
|
|
|
|
}
|
|
|