diff --git a/doomsday/libdeng2/include/de/c_wrapper.h b/doomsday/libdeng2/include/de/c_wrapper.h index 307c868698..b36381b69a 100644 --- a/doomsday/libdeng2/include/de/c_wrapper.h +++ b/doomsday/libdeng2/include/de/c_wrapper.h @@ -96,6 +96,16 @@ DENG2_PUBLIC void LegacyNetwork_SocketSet_Add(int set, int socket); DENG2_PUBLIC void LegacyNetwork_SocketSet_Remove(int set, int socket); DENG2_PUBLIC int LegacyNetwork_SocketSet_Activity(int set); +/* + * Info + */ +DENG2_OPAQUE(Info) + +DENG2_PUBLIC Info* Info_NewFromString(const char* utf8text); +DENG2_PUBLIC Info* Info_NewFromFile(const char* nativePath); +DENG2_PUBLIC void Info_Delete(Info* info); +DENG2_PUBLIC int Info_FindValue(Info* info, const char* path, char* buffer, size_t bufSize); + #ifdef __cplusplus } // extern "C" #endif diff --git a/doomsday/libdeng2/include/de/data/info.h b/doomsday/libdeng2/include/de/data/info.h index 0fce313a10..933a07eef4 100644 --- a/doomsday/libdeng2/include/de/data/info.h +++ b/doomsday/libdeng2/include/de/data/info.h @@ -96,9 +96,7 @@ class Info class ListElement : public Element { public: ListElement(const String& name) : Element(List, name) {} - void add(const String& v) { _values << v; } - QStringList values() const { return _values; } private: @@ -117,13 +115,21 @@ class Info typedef QHash Contents; typedef QList ContentsInOrder; + public: BlockElement(const String& bType, const String& name) : Element(Block, name) { setBlockType(bType); } ~BlockElement(); + /** + * The root block is the only one that does not have a block type. + */ + bool isRootBlock() const { return _blockType.isEmpty(); } + const String& blockType() const { return _blockType; } + const ContentsInOrder& contentsInOrder() const { return _contentsInOrder; } + const Contents& contents() const { return _contents; } QStringList values() const { @@ -132,10 +138,13 @@ class Info } int size() const { return _contents.size(); } + bool contains(const String& name) { return _contents.contains(name); } void setBlockType(const String& bType) { _blockType = bType.toLower(); } + void clear(); + void add(Element* elem) { DENG2_ASSERT(elem != 0); _contentsInOrder.append(elem); @@ -160,6 +169,17 @@ class Info return static_cast(e)->value(); } + /** + * Looks for an element based on a path where a colon ':' is used to + * separate element names. Whitespace before and after a separator is + * ignored. + * + * @param path Name path. + * + * @return The located element, or @c NULL. + */ + Element* findByPath(const String& path) const; + private: String _blockType; Contents _contents; @@ -172,18 +192,19 @@ class Info public: Info(); + ~Info(); /** - * Deserializes the key/value data from text. + * Parses the Info contents from a text string. * * @param infoSource Info text. */ - Info(const String& infoSource); - - ~Info(); + void parse(const String& infoSource); const BlockElement& root() const; + const Element* findByPath(const String& path) const; + private: struct Instance; Instance* d; diff --git a/doomsday/libdeng2/src/c_wrapper.cpp b/doomsday/libdeng2/src/c_wrapper.cpp index ceca9e1b07..eaa70ab132 100644 --- a/doomsday/libdeng2/src/c_wrapper.cpp +++ b/doomsday/libdeng2/src/c_wrapper.cpp @@ -24,6 +24,8 @@ #include "de/ByteRefArray" #include "de/Block" #include "de/LogBuffer" +#include "de/Info" +#include #include #include @@ -39,7 +41,8 @@ void LegacyCore_Delete(LegacyCore* lc) if(lc) { // It gets stopped automatically. - delete reinterpret_cast(lc); + DENG2_SELF(LegacyCore, lc); + delete self; } } @@ -252,3 +255,57 @@ int LegacyNetwork_SocketSet_Activity(int set) if(!set) return 0; return DENG2_LEGACYNETWORK().checkSetForActivity(set); } + +Info* Info_NewFromString(const char* utf8text) +{ + try + { + QScopedPointer self(new de::Info); + self->parse(QString::fromUtf8(utf8text)); + return reinterpret_cast(self.take()); + } + catch(de::Error& er) + { + LOG_WARNING(er.asText()); + } + return 0; +} + +Info* Info_NewFromFile(const char* nativePath) +{ + QFile file(nativePath); + if(file.open(QFile::ReadOnly | QFile::Text)) + { + return Info_NewFromString(file.readAll().constData()); + } + return 0; +} + +void Info_Delete(Info* info) +{ + if(info) + { + DENG2_SELF(Info, info); + delete self; + } +} + +int Info_FindValue(Info* info, const char* path, char* buffer, size_t bufSize) +{ + if(!info) return false; + + DENG2_SELF(Info, info); + const de::Info::Element* element = self->findByPath(path); + if(!element->isKey()) return false; + QString value = static_cast(element)->value(); + if(buffer) + { + qstrncpy(buffer, value.toUtf8().constData(), bufSize); + return true; + } + else + { + // Just return the size of the value. + return value.size(); + } +} diff --git a/doomsday/libdeng2/src/data/info.cpp b/doomsday/libdeng2/src/data/info.cpp index 5deda1cbac..9178b0572b 100644 --- a/doomsday/libdeng2/src/data/info.cpp +++ b/doomsday/libdeng2/src/data/info.cpp @@ -450,10 +450,37 @@ void Info::BlockElement::clear() _contentsInOrder.clear(); } -Info::Info(const String& infoSource) +Info::Element* Info::BlockElement::findByPath(const String &path) const +{ + String name; + String remainder; + int pos = path.indexOf(':'); + if(pos >= 0) + { + name = path.left(pos); + remainder = path.right(pos + 1); + } + else + { + name = path; + } + name.trimmed(); + + // Does this element exist? + Element* e = find(name); + if(!e) return 0; + + if(e->isBlock()) + { + // Descend into sub-blocks. + return static_cast(e)->findByPath(remainder); + } + return e; +} + +Info::Info() { d = new Instance; - d->parse(infoSource); } Info::~Info() @@ -461,7 +488,18 @@ Info::~Info() delete d; } -const Info::BlockElement &Info::root() const +void Info::parse(const String &infoSource) +{ + d->parse(infoSource); +} + +const Info::BlockElement& Info::root() const { return d->rootBlock; } + +const Info::Element* Info::findByPath(const String &path) const +{ + if(path.isEmpty()) return &d->rootBlock; + return d->rootBlock.findByPath(path); +}