#include "ofConstants.h"
#include "ofxXmlPoco.h"
#include "Poco/AutoPtr.h"
using namespace std;
ofxXmlPoco::~ofxXmlPoco(){
releaseAll();
}
ofxXmlPoco::ofxXmlPoco(const string & path){
document = new Poco::XML::Document();
element = document->documentElement();
load(path);
}
ofxXmlPoco::ofxXmlPoco(const ofxXmlPoco & rhs){
document = new Poco::XML::Document();
Poco::XML::Node *n = document->importNode(rhs.getPocoDocument()->documentElement(), true);
document->appendChild(n);
element = document->documentElement();
}
const ofxXmlPoco & ofxXmlPoco::operator=(const ofxXmlPoco & rhs){
if(&rhs == this){
return *this;
}
releaseAll();
document = (Poco::XML::Document *)rhs.document->cloneNode(true);
element = document->documentElement();
return *this;
}
ofxXmlPoco::ofxXmlPoco(){
document = new Poco::XML::Document();
element = document->documentElement();
}
bool ofxXmlPoco::load(const std::filesystem::path & path){
ofFile file(path, ofFile::ReadOnly);
if(!file.exists()){
ofLogError("ofxXmlPoco") << "couldn't load, \"" << file.getFileName() << "\" not found";
return false;
}
ofBuffer xmlBuffer(file);
return loadFromBuffer(xmlBuffer);
}
bool ofxXmlPoco::save(const std::filesystem::path & path){
ofBuffer buffer;
buffer.set(toString());
ofFile file(path, ofFile::WriteOnly);
return file.writeFromBuffer(buffer);
}
int ofxXmlPoco::getNumChildren() const{
if(!element){
return 0;
}
int numberOfChildren = 0;
Poco::XML::NodeList *list = element->childNodes();
for(unsigned long i=0; i < list->length(); i++){
if(list->item(i) && list->item(i)->nodeType() == Poco::XML::Node::ELEMENT_NODE){
numberOfChildren++;
}
}
return numberOfChildren;
}
int ofxXmlPoco::getNumChildren(const string& path) const{
if(!element){
return 0;
}
int numberOfChildren = 0;
Poco::XML::NodeList *list = element->childNodes();
for(unsigned long i=0; i < list->length(); i++){
if(list->item(i) && list->item(i)->nodeType() == Poco::XML::Node::ELEMENT_NODE){
string nodeName = list->item(i)->localName();
if(path.compare(nodeName) == 0){
numberOfChildren++;
}
}
}
return numberOfChildren;
}
string ofxXmlPoco::toString() const{
ostringstream stream;
Poco::XML::DOMWriter writer;
writer.setOptions(Poco::XML::XMLWriter::PRETTY_PRINT);
if(document){
try{
writer.writeNode( stream, getPocoDocument() );
}catch( exception & e ){
ofLogError("ofxXmlPoco") << "toString(): " << e.what();
}
} else if(element){
element->normalize();
writer.writeNode( stream, element );
}
string tmp = stream.str();
ofStringReplace(tmp, "<#text>", "");
ofStringReplace(tmp, "</#text>", "");
return tmp;
}
void ofxXmlPoco::addXml(ofxXmlPoco& xml, bool copyAll){
Poco::XML::Node *n = NULL;
if(copyAll){
n = document->importNode(xml.getPocoDocument()->documentElement(), true);
}else{
if(xml.getPocoElement() == 0 || xml.getPocoElement() == xml.getPocoDocument()->documentElement()){
n = document->importNode(xml.getPocoDocument()->documentElement(), true);
}else{
n = document->importNode( xml.getPocoElement(), true);
}
}
if(element){
element->appendChild(n);
}else{
document->appendChild(n);
}
}
bool ofxXmlPoco::addChild(const string& path){
vector<string> tokens;
if(path.find('/') != string::npos){
tokens = tokenize(path, "/");
}
if(tokens.size() > 1){
Poco::XML::Element *el = element;
vector<Poco::XML::Element*> toBeReleased;
for(std::size_t i = 0; i < tokens.size(); i++){
Poco::XML::Element *pe = getPocoDocument()->createElement(tokens.at(i));
el->appendChild(pe);
toBeReleased.push_back(pe);
el = pe;
}
if(element){
element->appendChild(el);
}else{
element = el;
}
return true;
}else{
Poco::XML::Element* pe = getPocoDocument()->createElement(path);
if(element){
element->appendChild(pe);
}else{
document->appendChild(pe);
element = document->documentElement();
}
}
return true;
}
string ofxXmlPoco::getValue() const{
if(!element){
return "";
}
if(NULL == element->firstChild()){
return "";
}
if(element->firstChild()->nodeType() == Poco::XML::Node::TEXT_NODE) {
return element->innerText();
}
return "";
}
string ofxXmlPoco::getValue(const string & path) const{
return getValue <string>(path, "");
}
int ofxXmlPoco::getIntValue() const {
return ofToInt(getValue());
}
int ofxXmlPoco::getIntValue(const string & path) const {
return getValue <int>(path, 0);
}
float ofxXmlPoco::getFloatValue() const {
return ofToFloat(getValue());
}
float ofxXmlPoco::getFloatValue(const string & path) const {
return getValue <float>(path, 0.0);
}
bool ofxXmlPoco::getBoolValue() const {
return ofToBool(getValue());
}
bool ofxXmlPoco::getBoolValue(const string & path) const {
return getValue <bool>(path, false);
}
int64_t ofxXmlPoco::getInt64Value() const {
return ofToInt64(getValue());
}
int64_t ofxXmlPoco::getInt64Value(const string & path) const {
return getValue <int64_t>(path, 0);
}
bool ofxXmlPoco::reset(){
if(element){
element = document->documentElement();
return true;
}
ofLogWarning("ofxXmlPoco") << "reset(): no element set yet";
return false;
}
bool ofxXmlPoco::setToChild(unsigned long index){
if(!element){
if((Poco::XML::Element*) document->documentElement()->firstChild()){
element = (Poco::XML::Element*) document->documentElement()->firstChild();
}else{
ofLogWarning("ofxXmlPoco") << "setToChild(): no element created yet";
return false;
}
}
unsigned long numberOfChildren = 0;
Poco::XML::NodeList *list = element->childNodes();
for(unsigned long i=0; i < list->length() && numberOfChildren < index + 1; i++){
if(list->item(i) && list->item(i)->nodeType() == Poco::XML::Node::ELEMENT_NODE){
if(numberOfChildren == index){
element = (Poco::XML::Element*) list->item(i);
return true;
}
numberOfChildren++;
}
}
return false;
}
bool ofxXmlPoco::setToParent(){
if(element->parentNode()){
element = (Poco::XML::Element*) element->parentNode();
}else{
ofLogWarning("ofxXmlPoco") << "setToParent(): current element has no parent";
return false;
}
return true;
}
bool ofxXmlPoco::setToParent(int numLevelsUp){
if(element){
int i = 0;
while( i < numLevelsUp ){
if(element->parentNode()){
element = (Poco::XML::Element*) element->parentNode();
}else{
ofLogWarning("ofxXmlPoco") << "setToParent(): too many parents: " << numLevelsUp;
return false;
}
i++;
}
return true;
}
ofLogWarning("ofxXmlPoco") << "setToParent(): no element set yet";
return false;
}
bool ofxXmlPoco::setToSibling(){
Poco::XML::Element *node;
if(element){
node = (Poco::XML::Element*) element->nextSibling();
}else{
ofLogWarning("ofxXmlPoco") << "setToSibling() << no element set yet";
return false;
}
while(NULL != node){
if((node->nodeType() == Poco::XML::Node::TEXT_NODE)
|| (node->nodeType() == Poco::XML::Node::COMMENT_NODE)){
node = (Poco::XML::Element*) node->nextSibling();
}else{
break;
}
}
if(NULL == node){
return false;
}
element = node;
return true;
}
bool ofxXmlPoco::setToPrevSibling(){
Poco::XML::Element *node;
if(element){
node = (Poco::XML::Element*) element->previousSibling();
}else{
ofLogWarning("ofxXmlPoco") << "setToPrevSibling(): no element set yet";
return false;
}
while(node && node->nodeType() == Poco::XML::Node::TEXT_NODE){
node = (Poco::XML::Element*) node->previousSibling();
}
if(!node || node->nodeType() == Poco::XML::Node::TEXT_NODE){
return false;
}
element = node;
return true;
}
bool ofxXmlPoco::setValue(const string& path, const string& value){
Poco::XML::Element *e;
if(element){
e = (Poco::XML::Element*) element->getNodeByPath(path);
}else{
ofLogWarning("ofxXmlPoco") << "setValue(): no element set yet";
return false;
}
if(!e){
ofLogWarning("ofxXmlPoco") << "setValue(): path \"" + path + "\" doesn't exist";
return false;
}
if(!e->firstChild()){
Poco::XML::Text *node = getPocoDocument()->createTextNode(ofToString(value));
e->appendChild(node);
node->release();
return true;
}
if(e->firstChild()->nodeType() == Poco::XML::Node::TEXT_NODE){
Poco::XML::Text *node = getPocoDocument()->createTextNode(ofToString(value));
e->replaceChild( (Poco::XML::Node*) node, e->firstChild());
node->release();
return true;
}else{
return false;
}
}
string ofxXmlPoco::getAttribute(const string& path) const{
Poco::XML::Node *e;
if(element){
if(path.find("[@") == string::npos){
string attributePath = "[@" + path + "]";
e = element->getNodeByPath(attributePath);
}else{
e = element->getNodeByPath(path);
}
}else{
ofLogWarning("ofxXmlPoco") << "getAttribute(): no element set yet";
return "";
}
if(e){
return e->getNodeValue();
}
return "";
}
bool ofxXmlPoco::removeAttribute(const string& path){
string attributeName, pathToAttribute;
Poco::XML::Element *e;
if(element){
bool hasPath = false;
if(path.find("[@") != string::npos){
int attrBegin = path.find("[@");
int start = attrBegin + 2;
int end = path.find("]", start);
attributeName = path.substr( start, end - start );
pathToAttribute = path.substr(0, attrBegin);
hasPath = true;
}else{
attributeName = path;
}
if(hasPath){
e = (Poco::XML::Element*) element->getNodeByPath(pathToAttribute);
}else{
e = element;
}
}else{
ofLogWarning("ofxXmlPoco") << "clearAttributes(): no element set yet";
return false;
}
if(e){
Poco::XML::NamedNodeMap *map = e->attributes();
for(unsigned long i = 0; i < map->length(); i++){
if(map->item(i)->nodeName() == attributeName){
e->removeAttribute(map->item(i)->nodeName());
}
}
map->release();
return true;
}
return false;
}
bool ofxXmlPoco::removeAttributes(const string& path){
Poco::XML::Element *e;
if(element){
if(path.find("[@") == string::npos){
string attributePath = "[@" + path + "]";
e = (Poco::XML::Element*) element->getNodeByPath(attributePath);
}else{
e = (Poco::XML::Element*) element->getNodeByPath(path);
}
}else{
ofLogWarning("ofxXmlPoco") << "clearAttributes(): no element set yet";
return false;
}
if(e){
Poco::XML::NamedNodeMap *map = e->attributes();
for(unsigned long i = 0; i < map->length(); i++){
e->removeAttribute(map->item(i)->nodeName());
}
map->release();
return true;
}
return false;
}
bool ofxXmlPoco::removeAttributes(){
if(element){
Poco::XML::NamedNodeMap *map = element->attributes();
for(unsigned long i = 0; i < map->length(); i++){
element->removeAttribute(map->item(i)->nodeName());
}
map->release();
return true;
}
ofLogWarning("ofxXmlPoco") << "clearAttributes(): no element set yet";
return false;
}
bool ofxXmlPoco::removeContents(){
if(element && element->hasChildNodes()){
Poco::XML::Node* swap;
Poco::XML::Node* n = element->firstChild();
while(n->nextSibling() != nullptr){
swap = n->nextSibling();
element->removeChild(n);
n = swap;
}
return true;
}
return false;
}
bool ofxXmlPoco::removeContents(const string& path){
Poco::XML::Element *e;
if(element){
e = (Poco::XML::Element*) element->getNodeByPath(path);
}else{
ofLogWarning("ofxXmlPoco") << "clearContents(): no element set yet";
return false;
}
if(e){
Poco::XML::NodeList *list = e->childNodes();
for(unsigned long i = 0; i < list->length(); i++) {
element->removeChild(list->item(i));
}
list->release();
return true;
}
return false;
}
void ofxXmlPoco::clear(){
releaseAll();
document = new Poco::XML::Document();
element = document->documentElement();
}
void ofxXmlPoco::releaseAll(){
if(document){
document->release();
document = NULL;
}
element = NULL;
}
bool ofxXmlPoco::remove(const string & path){
Poco::XML::Node * node;
if(element){
node = element->getNodeByPath(path);
}else{
ofLogWarning("ofxXmlPoco") << "remove(): no element set yet";
return false;
}
if(node){
Poco::XML::Node * n = node->parentNode()->removeChild(node);
n->release();
return true;
}
return false;
}
void ofxXmlPoco::remove(){
Poco::XML::Node * parent = element->parentNode();
if(parent){
parent->removeChild(element);
element->release();
element = (Poco::XML::Element *)parent;
}else{
clear();
}
}
bool ofxXmlPoco::exists(const string & path) const{
Poco::XML::Node * node;
if(element){
node = element->getNodeByPath(path);
}else{
return false;
}
if(node){
return true;
}
return false;
}
map<string, string> ofxXmlPoco::getAttributes() const{
map<string, string> attrMap;
if(element){
Poco::AutoPtr<Poco::XML::NamedNodeMap> attr = element->attributes();
for(unsigned long i = 0; i < attr->length(); i++){
attrMap[attr->item(i)->nodeName()] = attr->item(i)->nodeValue();
}
}else{
ofLogWarning("ofxXmlPoco") << "getAttribute(): no element set";
}
return attrMap;
}
bool ofxXmlPoco::setAttribute(const string& path, const string& value){
string attributeName, pathToAttribute;
bool hasPath = false;
if(path.find("[@") != string::npos){
size_t attrBegin = path.find("[@");
size_t start = attrBegin + 2;
size_t end = path.find("]", start);
attributeName = path.substr( start, end - start );
pathToAttribute = path.substr(0, attrBegin);
hasPath = true;
}else{
attributeName = path;
}
Poco::AutoPtr<Poco::XML::Attr> attr = getPocoDocument()->createAttribute(attributeName);
attr->setValue(value);
if(!hasPath){
Poco::AutoPtr<Poco::XML::NamedNodeMap> map = element->attributes();
map->setNamedItem(attr);
return true;
}
Poco::XML::Element* curElement = getPocoElement(pathToAttribute);
if(!curElement){
vector<string> tokens;
if(path.find('/') != string::npos){
tokens = tokenize(pathToAttribute, "/");
}
if(tokens.size() > 1){
curElement = element;
size_t lastExistingTag = 0;
for(vector<string>::iterator it = tokens.end(); it != tokens.begin(); it--){
string empty = "";
string concat = accumulate(tokens.begin(), it, std::string());
Poco::XML::Element* testElement = getPocoElement(concat);
if(testElement){
lastExistingTag++;
curElement = testElement;
break;
}
}
for(size_t i = lastExistingTag; i < tokens.size(); i++){
Poco::XML::Element *newElement = getPocoDocument()->createElement(tokens.at(i));
curElement->appendChild(newElement);
curElement = newElement;
}
curElement->setAttribute(attributeName, value);
return true;
}else{
Poco::XML::Element* testElement = getPocoElement(pathToAttribute);
if(testElement){
curElement = testElement;
}else{
Poco::XML::Element *newElement = getPocoDocument()->createElement(pathToAttribute);
curElement->appendChild(newElement);
curElement = newElement;
}
curElement->setAttribute(attributeName, value);
return true;
}
}
return false;
}
bool ofxXmlPoco::loadFromBuffer(const string & buffer){
Poco::XML::DOMParser parser;
if(document){
document->release();
}
try{
document = parser.parseString(buffer);
element = (Poco::XML::Element *)document->firstChild();
document->normalize();
return true;
}
catch(const Poco::XML::SAXException & e){
ofLogError("ofxXmlPoco") << "parse error: " << e.message();
document = new Poco::XML::Document;
element = document->documentElement();
return false;
}
catch(const exception & e){
short msg = atoi(e.what());
ofLogError("ofxXmlPoco") << "parse error: " << DOMErrorMessage(msg);
document = new Poco::XML::Document;
element = document->documentElement();
return false;
}
}
string ofxXmlPoco::getName() const {
if(element){
return element->nodeName();
}
return "";
}
bool ofxXmlPoco::setTo(const string& path){
if(!element){
if(document->documentElement()) {
element = document->documentElement();
}else{
ofLogWarning("ofxXmlPoco") << "setTo(): empty document";
return false;
}
}
if(element == document->documentElement() && element->nodeName() == path ){
return true;
}
if(path.find("../") != string::npos){
Poco::XML::Element* prev = element;
Poco::XML::Element* parent = nullptr;
size_t count = 0;
size_t offset;
for (offset = path.find("../");
offset != std::string::npos;
offset = path.find("../", offset + 3)){
if(count == 0){
parent = (Poco::XML::Element*) element->parentNode();
}else{
parent = (Poco::XML::Element*) parent->parentNode();
}
++count;
}
if( (count * 3) > path.size() - 1 ){
element = parent;
return true;
}else if (parent){
string remainingPath = path.substr((count * 3), path.size() - (count * 3));
element = (Poco::XML::Element*) parent->getNodeByPath(remainingPath);
if(!element){
element = prev;
ofLogWarning("ofxXmlPoco") << "setCurrentElement(): passed invalid path \"" << remainingPath << "\"";
return false;
}
}else{
ofLogWarning("ofxXmlPoco") << "setCurrentElement(): parent is nullptr.";
return false;
}
}else if(path.find("//") != string::npos){
Poco::XML::Element* prev = element;
element = (Poco::XML::Element*) document->getNodeByPath(path);
if(!element){
element = prev;
ofLogWarning("ofxXmlPoco") << "setCurrentElement(): passed invalid path \"" << path << "\"";
return false;
}
}else{
Poco::XML::Element* prev = element;
element = (Poco::XML::Element*) element->getNodeByPath(path);
if(!element){
element = prev;
ofLogWarning("ofxXmlPoco") << "setCurrentElement(): passed invalid path \"" << path << "\"";
return false;
}
}
return true;
}
const Poco::XML::Element * ofxXmlPoco::getPocoElement() const {
return element;
}
Poco::XML::Element * ofxXmlPoco::getPocoElement(){
return element;
}
Poco::XML::Element* ofxXmlPoco::getPocoElement(const string& path){
string copy = path;
std::size_t ind = copy.find("[@");
if(ind != string::npos){
copy = path.substr(0, ind);
}
if(element){
return (Poco::XML::Element*) element->getNodeByPath(copy);
}else{
ofLogWarning("ofxXmlPoco") << "getPocoElement(): no element to get yet ";
return nullptr;
}
}
const Poco::XML::Element* ofxXmlPoco::getPocoElement(const string& path) const{
string copy = path;
std::size_t ind = copy.find("[@");
if(ind != string::npos){
copy = path.substr(0, ind);
}
if(element){
return (Poco::XML::Element*) element->getNodeByPath(copy);
}else{
ofLogWarning("ofxXmlPoco") << "getPocoElement(): no element to get yet ";
return nullptr;
}
}
Poco::XML::Document * ofxXmlPoco::getPocoDocument(){
return document;
}
const Poco::XML::Document * ofxXmlPoco::getPocoDocument() const {
return document;
}
string ofxXmlPoco::DOMErrorMessage(short msg){
switch(msg){
case 1:
return "INDEX_SIZE_ERR";
break;
case 2:
return "DOMSTRING_SIZE_ERR";
break;
case 3:
return "HIERARCHY_REQUEST_ERR";
break;
case 4:
return "WRONG_DOCUMENT_ERR";
break;
case 5:
return "INVALID_CHARACTER_ERR";
break;
case 6:
return "NO_DATA_ALLOWED_ERR";
break;
case 7:
return "NO_MODIFICATION_ALLOWED_ERR";
break;
case 8:
return "NOT_FOUND_ERR";
break;
case 9:
return "NOT_SUPPORTED_ERR";
break;
case 10:
return "INUSE_ATTRIBUTE_ERR";
break;
case 11:
return "INVALID_STATE_ERR";
break;
case 12:
return "SYNTAX_ERR";
break;
case 13:
return "INVALID_MODIFICATION_ERR";
break;
case 14:
return "NAMESPACE_ERR";
break;
case 15:
return "INVALID_ACCESS_ERR";
break;
}
return "DOM ERROR";
}
void ofSerialize(ofxXmlPoco & xml, const ofAbstractParameter & parameter){
if(!parameter.isSerializable()){
return;
}
string name = parameter.getEscapedName();
if(name == ""){
name = "UnknownName";
}
if(parameter.type() == typeid(ofParameterGroup).name()){
const ofParameterGroup & group = static_cast <const ofParameterGroup &>(parameter);
if(!xml.exists(name)){
xml.addChild(name);
ofLogVerbose("ofxXmlPoco") << "creating group " << name;
}
xml.setTo(name);
ofLogVerbose("ofxXmlPoco") << "group " << name;
for(auto & p: group){
ofSerialize(xml, *p);
}
ofLogVerbose("ofxXmlPoco") << "end group " << name;
xml.setToParent();
}else{
string value = parameter.toString();
if(!xml.exists(name)){
xml.addChild(name);
ofLogVerbose("ofxXmlPoco") << "creating tag " << name;
}
ofLogVerbose("ofxXmlPoco") << "setting tag " << name << ": " << value;
xml.setValue(name, value);
}
}
void ofDeserialize(const ofxXmlPoco & xml, ofAbstractParameter & parameter){
if(!parameter.isSerializable()){
return;
}
string name = parameter.getEscapedName();
if(parameter.type() == typeid(ofParameterGroup).name()){
ofParameterGroup & group = static_cast <ofParameterGroup &>(parameter);
if(const_cast<ofxXmlPoco&>(xml).setTo(name)){
for(auto & p: group){
ofDeserialize(xml, *p);
}
const_cast<ofxXmlPoco&>(xml).setToParent();
}
}else{
if(xml.exists(name)){
if(parameter.type() == typeid(ofParameter <int> ).name()){
parameter.cast <int>() = xml.getIntValue(name);
}else if(parameter.type() == typeid(ofParameter <float> ).name()){
parameter.cast <float>() = xml.getFloatValue(name);
}else if(parameter.type() == typeid(ofParameter <bool> ).name()){
parameter.cast <bool>() = xml.getBoolValue(name);
}else if(parameter.type() == typeid(ofParameter <int64_t> ).name()){
parameter.cast <int64_t>() = xml.getInt64Value(name);
}else if(parameter.type() == typeid(ofParameter <string> ).name()){
parameter.cast <string>() = xml.getValue(name);
}else{
parameter.fromString(xml.getValue(name));
}
}
}
}
Comments