/*************************************************************************** * * * 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 * ***************************************************************************/ // own header #include "import_rose.h" // qt includes #include #include #include #include #include #include #include #include #include // app includes #include "petalnode.h" #include "petaltree2uml.h" #include "umlnamespace.h" // only for the KDE4 compatibility macros namespace Import_Rose { typedef TQPtrList 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); } }