You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
tdesdk/umbrello/umbrello/import_rose.cpp

392 lines
13 KiB

/***************************************************************************
* *
* 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);
}
}