Subversion Repositories public iLand

Rev

Rev 1221 | Blame | Compare with Previous | Last modification | View Log | RSS feed

/********************************************************************************************
**    iLand - an individual based forest landscape and disturbance model
**    http://iland.boku.ac.at
**    Copyright (C) 2009-  Werner Rammer, Rupert Seidl
**
**    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 3 of the License, or
**    (at your option) any later version.
**
**    This program is distributed in the hope that it will be useful,
**    but WITHOUT ANY WARRANTY; without even the implied warranty of
**    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
**    GNU General Public License for more details.
**
**    You should have received a copy of the GNU General Public License
**    along with this program.  If not, see <http://www.gnu.org/licenses/>.
********************************************************************************************/


#include "xmlhelper.h"
#include "helper.h"
#include "exception.h"


/** @class XmlHelper
  XmlHelper wraps a XML file and provides some convenient functions to
  retrieve values. Internally XmlHelper uses a QDomDocument (the full structure is
  kept in memory so the size is restricted).
  Use node() to get a QDomElement or use value() to directly retrieve the node value.
  Nodes could be addressed relative to a node defined by setCurrentNode() using a ".".
  The notation is as follows:
  - a '.' character defines a hierarchy
  - [] the Nth element of the same hierarchical layer can be retrieved by [n-1]
  Use also the convenience functions valueBool() and valueDouble().
  While all the value/node etc. functions parse the DOM tree at every call, the data accessed by paramValue() - type
  functions is parsed only once during startup and stored in a QVariant array. Accessible are all nodes that are children of the
   "<parameter>"-node.

  @code
  QDomElement e,f
  e = xml.node("first.second.third"); // e points to "third"
  xml.setCurrentNode("first");
  f = xml.node(".second.third"); // e and f are equal
  e = xml.node("level1[2].level2[3].level3"); // 3rd element of level 1, ...
  int x = xml.value(".second", "0").toInt(); // node value of "second" or "0" if not found.
  if (xml.valueBool("enabled")) // use of valueBool with default value (false)
     ...
  XmlHelper xml_sec(xml.node("first.second")); // create a xml-helper with top node=first.second
  xml_sec.valueDouble("third"); // use this
  @endcode

  */


XmlHelper::XmlHelper()
{
}
XmlHelper::~XmlHelper()
{
    //qDebug() << "xml helper destroyed";
}
/** Create a XmlHelper instance with @p topNode as top node.
  The xml tree is not copied.
*/

XmlHelper::XmlHelper(QDomElement topNode)
{
    mTopNode = topNode;
    mCurrentTop = topNode;
}

void XmlHelper::loadFromFile(const QString &fileName)
{
    mDoc.clear();
    QString xmlFile = Helper::loadTextFile(fileName);

    if (!xmlFile.isEmpty()) {

    QString errMsg;
    int errLine, errCol;
        if (!mDoc.setContent(xmlFile, &errMsg, &errLine, &errCol)) {
            throw IException(QString("Error in xml-file!\nError applying xml line %1, col %2.\nMessage: %3").arg(errLine).arg(errCol).arg(errMsg));
        }
    } else {
         throw IException("xmlfile does not exist or is empty!");
    }
    mCurrentTop = mDoc.documentElement(); // top element
    mTopNode = mCurrentTop;

    // fill parameter cache
    QDomElement e = node("model.parameter");
    e = e.firstChildElement();
    mParamCache.clear();
    while (!e.isNull()) {
        mParamCache[e.nodeName()] = e.text();
        e = e.nextSiblingElement();
    }
}

/** numeric values of elements in the section <parameter> are stored in a QHash structure for faster access.
    with paramValue() these data can be accessed.
  */

double XmlHelper::paramValue(const QString &paramName, const double defaultValue) const
{
    if (mParamCache.contains(paramName))
        return mParamCache.value(paramName).toDouble();
    return defaultValue;
}
QString XmlHelper::paramValueString(const QString &paramName, const QString &defaultValue) const
{
    if (mParamCache.contains(paramName))
        return mParamCache.value(paramName);
    return defaultValue;
}

bool XmlHelper::paramValueBool(const QString &paramName, const bool &defaultValue) const
{
    if (mParamCache.contains(paramName)) {
        QString v = mParamCache.value(paramName).trimmed();
        bool ret = (v=="1" || v=="true");
        return ret;
    }
    return defaultValue;
}

bool XmlHelper::hasNode(const QString &path) const
{
    return !node(path).isNull();
}

QString XmlHelper::value(const QString &path, const QString &defaultValue) const
{
    QDomElement e = node(path);
    if (e.isNull()) {
        qDebug() << "Warning: xml: node" << path << "is not present.";
        return defaultValue;
    } else {
        if (e.text().isEmpty())
            return defaultValue;
        else
            return e.text();
    }
}
bool XmlHelper::valueBool(const QString &path, const bool defaultValue) const
{
    QDomElement e = node(path);
    if (e.isNull()) {
        qDebug() << "Warning: xml: node" << path << "is not present.";
        return defaultValue;
    }
    QString v = e.text();
    if (v=="true" || v=="True" || v=="1")
        return true;
    else
        return false;
}
double XmlHelper::valueDouble(const QString &path, const double defaultValue) const
{
    QDomElement e = node(path);
    if (e.isNull()) {
        qDebug() << "Warning: xml: node" << path << "is not present.";
        return defaultValue;
    } else {
        if (e.text().isEmpty())
            return defaultValue;
        else
            return e.text().toDouble();
    }
}

int XmlHelper::valueInt(const QString &path, const int defaultValue) const
{
    double dbl_val = valueDouble(path, defaultValue);
    return static_cast<int>(dbl_val);
}

/// retreives node with given @p path and a element where isNull() is true if nothing is found.
QDomElement XmlHelper::node(const QString &path) const
{
    QStringList elem = path.split('.', QString::SkipEmptyParts);
    QDomElement c;
    if (path.count()>0 && path.at(0) == '.')
        c = mCurrentTop;
    else
        c = mTopNode;
    foreach (QString level, elem) {
        if (level.indexOf('[')<0) {
            c = c.firstChildElement(level);
            if (c.isNull())
                break;
        } else {
            int pos = level.indexOf('[');
            level.chop(1); // drop closing bracket
            int ind = level.right( level.length() - pos -1).toInt();
            QString name = level.left(pos);
            c = c.firstChildElement(name);
            while (ind>0 && !c.isNull()) {
                c = c.nextSiblingElement();
                ind--;
            }
            if (c.isNull())
                break;
        }
    }
    //qDebug() << "node-request:" << path;
    return c;
}

// writers
bool XmlHelper::setNodeValue(QDomElement &node, const QString &value)
{
    if (!node.isNull() && node.hasChildNodes()) {
        node.firstChild().toText().setData(value);
        return true;
    }
    return false;
}
bool XmlHelper::setNodeValue(const QString &path, const QString &value)
{
    QDomElement e = node(path);
    if (e.isNull()) {
        qDebug() << "XML: attempting to set value of" << path << ": node not present.";
        return false;
    }
    return setNodeValue(e,value);
}

// private recursive loop
void XmlHelper::dump_rec(QDomElement c, QStringList &stack, QStringList &out)
{
    if (c.isNull())
        return;
    QDomElement ch = c.firstChildElement();
    bool hasChildren = !ch.isNull();
    bool nChildren = !ch.isNull() && !ch.nextSiblingElement().isNull();
    int child_index=-1;
    while (!ch.isNull()) {
        if (nChildren) {
            child_index++;
            stack.push_back(QString("%1[%2]").arg(ch.nodeName()).arg(child_index));
        } else
          stack.push_back(ch.nodeName());
        dump_rec(ch, stack, out);
        stack.pop_back();
        ch = ch.nextSiblingElement();
    }
    QString self;
    if (!hasChildren)
        self = c.text();
    self = QString("%1: %3").arg(stack.join("."), self);
    out.push_back(self);
}

QString XmlHelper::dump(const QString &path, int levels)
{
    Q_UNUSED(levels);
    QDomElement c = node(path);

    QStringList stack;
    stack.push_back(c.nodeName());
    QStringList result;
    dump_rec(c, stack, result);
    return result.join("\n");
}