#include "uscxml/Common.h" #include "XPathDataModel.h" #include "uscxml/Message.h" #include #ifdef BUILD_AS_PLUGINS #include #endif namespace uscxml { using namespace Arabica::XPath; using namespace Arabica::DOM; #ifdef BUILD_AS_PLUGINS PLUMA_CONNECTOR bool connect(pluma::Host& host) { host.add( new XPathDataModelProvider() ); return true; } #endif XPathDataModel::XPathDataModel() { } boost::shared_ptr XPathDataModel::create(InterpreterImpl* interpreter) { boost::shared_ptr dm = boost::shared_ptr(new XPathDataModel()); dm->_interpreter = interpreter; // dm->_xpath->setVariableCompileTimeResolver(_varCTResolver); // dm->_xpath->setNamespaceContext(interpreter->getNSContext()); dm->_funcResolver.setInterpreter(interpreter); dm->_xpath.setFunctionResolver(dm->_funcResolver); dm->_xpath.setVariableResolver(dm->_varResolver); dm->_domFactory = Arabica::SimpleDOM::DOMImplementation::getDOMImplementation(); dm->_doc = dm->_domFactory.createDocument("http://www.w3.org/2005/07/scxml", "", 0); dm->_datamodel = dm->_doc.createElement("datamodel"); dm->_doc.appendChild(dm->_datamodel); Element ioProcElem = dm->_doc.createElement("data"); ioProcElem.setAttribute("id", "_ioprocessors"); std::map::const_iterator ioProcIter = interpreter->getIOProcessors().begin(); while(ioProcIter != interpreter->getIOProcessors().end()) { Element ioProc = dm->_doc.createElement("processor"); ioProc.setAttribute("name", ioProcIter->first); Data ioProcData = ioProcIter->second.getDataModelVariables(); Element ioProcLoc = dm->_doc.createElement("location"); Text ioProcLocText = dm->_doc.createTextNode(ioProcData.compound["location"].atom); ioProcLoc.appendChild(ioProcLocText); ioProc.appendChild(ioProcLoc); ioProcElem.appendChild(ioProc); ioProcIter++; } dm->_datamodel.appendChild(ioProcElem); NodeSet ioProcNodeSet; ioProcNodeSet.push_back(ioProcElem); dm->_varResolver.setVariable("_ioprocessors", ioProcNodeSet); Element sessIdElem = dm->_doc.createElement("data"); sessIdElem.setAttribute("id", "_sessionid"); Text sessIdText = dm->_doc.createTextNode(interpreter->getSessionId()); sessIdElem.appendChild(sessIdText); dm->_datamodel.appendChild(sessIdElem); NodeSet sessIdNodeSet; sessIdNodeSet.push_back(sessIdText); dm->_varResolver.setVariable("_sessionid", sessIdNodeSet); Element nameElem = dm->_doc.createElement("data"); nameElem.setAttribute("id", "_name"); Text nameText = dm->_doc.createTextNode(interpreter->getName()); nameElem.appendChild(nameText); dm->_datamodel.appendChild(nameElem); NodeSet nameNodeSet; nameNodeSet.push_back(nameText); dm->_varResolver.setVariable("_name", nameNodeSet); return dm; } XPathDataModel::~XPathDataModel() { } void XPathDataModel::pushContext() { } void XPathDataModel::popContext() { } void XPathDataModel::initialize() { } void XPathDataModel::setEvent(const Event& event) { Element eventElem = _doc.createElement("data"); eventElem.setAttribute("id", "_event"); Element eventDataElem = _doc.createElement("data"); NodeSet eventNodeSet; { // -- name Element eventNameElem = _doc.createElement("name"); Text eventName = _doc.createTextNode(event.name.c_str()); eventNameElem.appendChild(eventName); eventElem.appendChild(eventNameElem); } { // -- origin Element eventOriginElem = _doc.createElement("origin"); Text eventOrigin = _doc.createTextNode(event.origin.c_str()); eventOriginElem.appendChild(eventOrigin); eventElem.appendChild(eventOriginElem); } { // -- type Element eventTypeElem = _doc.createElement("type"); Text eventType; switch (event.type) { case Event::INTERNAL: eventType = _doc.createTextNode("internal"); break; case Event::EXTERNAL: eventType = _doc.createTextNode("external"); break; case Event::PLATFORM: eventType = _doc.createTextNode("platform"); break; } eventTypeElem.appendChild(eventType); eventElem.appendChild(eventTypeElem); } if (event.params.size() > 0) { std::multimap::const_iterator paramIter = event.params.begin(); while(paramIter != event.params.end()) { Element eventParamElem = _doc.createElement("data"); Text eventParamText = _doc.createTextNode(paramIter->second); eventParamElem.setAttribute("id", paramIter->first); eventParamElem.appendChild(eventParamText); eventDataElem.appendChild(eventParamElem); paramIter++; } } if (event.namelist.size() > 0) { std::map::const_iterator namelistIter = event.namelist.begin(); while(namelistIter != event.namelist.end()) { Element eventNamelistElem = _doc.createElement("data"); Text eventNamelistText = _doc.createTextNode(namelistIter->second); eventNamelistElem.setAttribute("id", namelistIter->first); eventNamelistElem.appendChild(eventNamelistText); eventDataElem.appendChild(eventNamelistElem); namelistIter++; } } if (event.raw.size() > 0) { Element eventRawElem = _doc.createElement("raw"); Text textNode = _doc.createTextNode(event.raw.c_str()); eventRawElem.appendChild(textNode); eventElem.appendChild(eventRawElem); } if (event.content.size() > 0) { Text textNode = _doc.createTextNode(Interpreter::spaceNormalize(event.content).c_str()); eventDataElem.appendChild(textNode); } if (event.dom) { Node importedNode = _doc.importNode(event.getFirstDOMElement(), true); eventDataElem.appendChild(importedNode); } eventElem.appendChild(eventDataElem); eventNodeSet.push_back(eventElem); // do we need to replace an existing event? Node oldEventElem = _datamodel.getFirstChild(); while(oldEventElem) { if (oldEventElem.getNodeType() == Node_base::ELEMENT_NODE) { if (HAS_ATTR(oldEventElem, "id") && boost::iequals(ATTR(oldEventElem, "id"), "_event")) break; } oldEventElem = oldEventElem.getNextSibling(); } if (oldEventElem) { _datamodel.replaceChild(eventElem, oldEventElem); } else { _datamodel.appendChild(eventElem); } _varResolver.setVariable("_event", eventNodeSet); } Data XPathDataModel::getStringAsData(const std::string& content) { Data data; return data; } bool XPathDataModel::validate(const std::string& location, const std::string& schema) { return true; } uint32_t XPathDataModel::getLength(const std::string& expr) { // std::cout << _datamodel << std::endl; XPathValue result = _xpath.evaluate_expr(expr, _doc); switch(result.type()) { case NUMBER: return result.asNumber(); break; case NODE_SET: return result.asNodeSet().size(); break; default: Event exceptionEvent("error.execution", Event::PLATFORM); exceptionEvent.data.compound["exception"] = Data("'" + expr + "' does not evaluate to an array.", Data::VERBATIM); throw(exceptionEvent); } return 0; } void XPathDataModel::setForeach(const std::string& item, const std::string& array, const std::string& index, uint32_t iteration) { XPathValue arrayResult = _xpath.evaluate_expr(array, _doc); assert(arrayResult.type() == NODE_SET); #if 0 std::cout << "Array Size: " << arrayResult.asNodeSet().size() << std::endl; for (int i = 0; i < arrayResult.asNodeSet().size(); i++) { std::cout << arrayResult.asNodeSet()[i] << std::endl; } #endif assert(arrayResult.asNodeSet().size() >= iteration); NodeSet arrayNodeSet; arrayNodeSet.push_back(arrayResult.asNodeSet()[iteration]); if (!isDeclared(item)) { if (!isValidIdentifier(item)) throw Event("error.execution", Event::PLATFORM); Element container = _doc.createElement("data"); container.setAttribute("id", item); container.appendChild(arrayResult.asNodeSet()[iteration].cloneNode(true)); _datamodel.appendChild(container); _varResolver.setVariable(item, arrayNodeSet); } XPathValue itemResult = _varResolver.resolveVariable("", item); assign(itemResult, arrayNodeSet, Element()); if (index.length() > 0) { NodeSet indexNodeSet; Text indexElem = _doc.createTextNode(toStr(iteration)); indexNodeSet.push_back(indexElem); if (!isDeclared(index)) { Element container = _doc.createElement("data"); container.setAttribute("id", index); container.appendChild(indexElem); _datamodel.appendChild(container); NodeSet indexVarNodeSet; indexVarNodeSet.push_back(container); _varResolver.setVariable(index, indexVarNodeSet); } XPathValue indexResult = _varResolver.resolveVariable("", index); assign(indexResult, indexNodeSet, Element()); } #if 0 std::cout << _datamodel << std::endl << std::endl; std::cout << "Index: " << indexResult.asNodeSet().size() << std::endl; for (int i = 0; i < indexResult.asNodeSet().size(); i++) { std::cout << indexResult.asNodeSet()[i] << std::endl; } std::cout << std::endl; #endif } bool XPathDataModel::isValidIdentifier(const std::string& identifier) { if(boost::starts_with(identifier, ".")) return false; return true; } void XPathDataModel::eval(const Arabica::DOM::Element& scriptElem, const std::string& expr) { XPathValue result = _xpath.evaluate_expr(expr, _doc); } bool XPathDataModel::isDeclared(const std::string& expr) { try { return _varResolver.isDeclared(expr) || evalAsBool(expr); } catch(...) { return false; } } bool XPathDataModel::evalAsBool(const std::string& expr) { // std::cout << std::endl << evalAsString(expr); XPathValue result; try { result = _xpath.evaluate_expr(expr, _doc); } catch(SyntaxException e) { throw Event("error.execution", Event::PLATFORM); } catch(std::runtime_error e) { throw Event("error.execution", Event::PLATFORM); } return result.asBool(); } std::string XPathDataModel::evalAsString(const std::string& expr) { XPathValue result; try { result = _xpath.evaluate_expr(expr, _doc); } catch(SyntaxException e) { throw Event("error.execution", Event::PLATFORM); } catch(std::runtime_error e) { throw Event("error.execution", Event::PLATFORM); } switch (result.type()) { case STRING: return result.asString(); break; case Arabica::XPath::BOOL: // MSVC croaks with ambiguous symbol without qualified name return (result.asBool() ? "true" : "false"); break; case NUMBER: return toStr(result.asNumber()); break; case NODE_SET: { NodeSet nodeSet = result.asNodeSet(); std::stringstream ss; for (int i = 0; i < nodeSet.size(); i++) { ss << nodeSet[i]; if (nodeSet[i].getNodeType() != Node_base::TEXT_NODE) { ss << std::endl; } } return ss.str(); break; } case ANY: throw Event("error.execution", Event::PLATFORM); break; } return "undefined"; } double XPathDataModel::evalAsNumber(const std::string& expr) { XPathValue result = _xpath.evaluate_expr(expr, _doc); return result.asNumber(); } void XPathDataModel::assign(const Element& assignElem, const Document& doc, const std::string& content) { std::string location; if (HAS_ATTR(assignElem, "id")) { location = ATTR(assignElem, "id"); } else if (HAS_ATTR(assignElem, "location")) { location = ATTR(assignElem, "location"); } // test 326ff XPathValue key = _xpath.evaluate_expr(location, _doc); #if 0 if (key.type() == NODE_SET) { try { for (int i = 0; i < key.asNodeSet().size(); i++) { Node node = key.asNodeSet()[i]; if (node == _varResolver.resolveVariable("", "_ioprocessors").asNodeSet()[0]) throw Event("error.execution", Event::PLATFORM); if (node == _varResolver.resolveVariable("", "_sessionid").asNodeSet()[0]) throw Event("error.execution", Event::PLATFORM); if (node == _varResolver.resolveVariable("", "_name").asNodeSet()[0]) throw Event("error.execution", Event::PLATFORM); if (node == _varResolver.resolveVariable("", "_event").asNodeSet()[0]) throw Event("error.execution", Event::PLATFORM); } } catch (Event e) {} } #endif NodeSet nodeSet; if (doc) { if (doc.getDocumentElement()) { Node data = doc.getDocumentElement().getFirstChild(); while (data) { // do not add empty text as a node if (data.getNodeType() == Node_base::TEXT_NODE) { std::string trimmed = data.getNodeValue(); boost::trim(trimmed); if (trimmed.length() == 0) { data = data.getNextSibling(); continue; } } nodeSet.push_back(data); data = data.getNextSibling(); } } assign(key, nodeSet, assignElem); } else if (content.length() > 0) { Text textNode = _doc.createTextNode(Interpreter::spaceNormalize(content)); nodeSet.push_back(textNode); assign(key, nodeSet, assignElem); } else if (HAS_ATTR(assignElem, "expr")) { XPathValue value = _xpath.evaluate_expr(ATTR(assignElem, "expr"), _doc); assign(key, value, assignElem); } else { LOG(ERROR) << "assign element has no content"; } // std::cout << _datamodel << std::endl; } void XPathDataModel::assign(const std::string& location, const Data& data) { XPathValue locationResult = _xpath.evaluate_expr(location, _doc); NodeSet dataNodeSet = dataToNodeSet(data); assign(locationResult, dataNodeSet, Element()); // std::cout << _datamodel << std::endl; } NodeSet XPathDataModel::dataToNodeSet(const Data& data) { NodeSet dataNodeSet; if (data.atom.length() > 0) { dataNodeSet.push_back(_doc.createTextNode(data.atom)); } return dataNodeSet; } void XPathDataModel::init(const Element& dataElem, const Document& doc, const std::string& content) { std::string location; if (HAS_ATTR(dataElem, "id")) { location = ATTR(dataElem, "id"); } else if (HAS_ATTR(dataElem, "location")) { location = ATTR(dataElem, "location"); } Element container = _doc.createElement("data"); container.setAttribute("id", location); if (doc) { if (doc.getDocumentElement()) { Node data = doc.getDocumentElement().getFirstChild(); while (data) { Node dataClone = _doc.importNode(data, true); container.appendChild(dataClone); data = data.getNextSibling(); } } } else if (content.length() > 0) { Text textNode = _doc.createTextNode(Interpreter::spaceNormalize(content)); container.appendChild(textNode); } else if (HAS_ATTR(dataElem, "expr")) { try { XPathValue expr = _xpath.evaluate_expr(ATTR(dataElem, "expr"), _doc); switch (expr.type()) { case NODE_SET: { for (int i = 0; i < expr.asNodeSet().size(); i++) { container.appendChild(expr.asNodeSet()[i].cloneNode(true)); } break; } case STRING: container.appendChild(_doc.createTextNode(expr.asString())); break; case NUMBER: { container.appendChild(_doc.createTextNode(toStr(expr.asNumber()))); break; } case Arabica::XPath::BOOL: case ANY: throw Event("error.execution", Event::PLATFORM); } } catch (SyntaxException e) { throw Event("error.execution", Event::PLATFORM); } } else { LOG(ERROR) << "data element has no content"; } _datamodel.appendChild(container); // put data element into nodeset and bind to xpath variable NodeSet nodeSet; #if 0 nodeSet.push_back(container); #else Node node = container.getFirstChild(); while(node) { nodeSet.push_back(node); node = node.getNextSibling(); } #endif _varResolver.setVariable(location, nodeSet); // std::cout << _datamodel << std::endl; } void XPathDataModel::init(const std::string& location, const Data& data) { NodeSet nodeSet; _varResolver.setVariable(location, nodeSet); } void XPathDataModel::assign(const XPathValue& key, const XPathValue& value, const Element& assignElem) { switch (key.type()) { case NODE_SET: { if (key.asNodeSet().size() == 0) { throw Event("error.execution", Event::PLATFORM); } switch (value.type()) { case STRING: assign(key.asNodeSet(), value.asString(), assignElem); break; case Arabica::XPath::BOOL: assign(key.asNodeSet(), value.asBool(), assignElem); break; case NUMBER: assign(key.asNodeSet(), value.asNumber(), assignElem); break; case NODE_SET: assign(key.asNodeSet(), value.asNodeSet(), assignElem); break; case ANY: throw Event("error.execution", Event::PLATFORM); } break; } case STRING: case Arabica::XPath::BOOL: case NUMBER: case ANY: throw Event("error.execution", Event::PLATFORM); } } void XPathDataModel::assign(const XPathValue& key, const NodeSet& value, const Element& assignElem) { if (value.size() == 0 || !value[0]) return; switch (key.type()) { case NODE_SET: { assign(key.asNodeSet(), value, assignElem); break; } case STRING: case Arabica::XPath::BOOL: case NUMBER: case ANY: throw Event("error.execution", Event::PLATFORM); } } void XPathDataModel::assign(const NodeSet& key, const std::string& value, const Element& assignElem) { if (key.size() == 0) return; for (int i = 0; i < key.size(); i++) { Node node = key[i]; switch (node.getNodeType()) { case Node_base::ATTRIBUTE_NODE: { Attr attr(node); attr.setValue(value); break; } case Node_base::TEXT_NODE: { Text text(node); text.setNodeValue(value); break; } case Node_base::ELEMENT_NODE: { Element element(node); if (HAS_ATTR(assignElem, "type") && boost::iequals(ATTR(assignElem, "type"), "addattribute")) { // addattribute: Add an attribute with the name specified by 'attr' // and value specified by 'expr' to the node specified by 'location'. if (!HAS_ATTR(assignElem, "attr")) throw Event("error.execution", Event::PLATFORM); element.setAttribute(ATTR(assignElem, "attr"), value); } else { /// test 547 while(element.hasChildNodes()) element.removeChild(element.getChildNodes().item(0)); Text text = _doc.createTextNode(value); element.appendChild(text); } break; } default: throw Event("error.execution", Event::PLATFORM); break; } } } void XPathDataModel::assign(const NodeSet& key, const double value, const Element& assignElem) { assign(key, toStr(value), assignElem); } void XPathDataModel::assign(const NodeSet& key, const bool value, const Element& assignElem) { } void XPathDataModel::assign(const NodeSet& key, const NodeSet& value, const Element& assignElem) { if (key.size() == 0) return; if (value.size() == 0 || !value[0]) return; for (int i = 0; i < key.size(); i++) { switch (key[i].getNodeType()) { case Node_base::ELEMENT_NODE: { assign(Element(key[i]), value, assignElem); break; } default: // std::cout << key[i].getNodeType() << std::endl; throw Event("error.execution", Event::PLATFORM); break; } } } void XPathDataModel::assign(const Element& key, const NodeSet& value, const Element& assignElem) { Element element(key); if (value.size() == 0 || !value[0]) return; if (false) { } else if (assignElem && HAS_ATTR(assignElem, "type") && boost::iequals(ATTR(assignElem, "type"), "firstchild")) { // firstchild: Insert the value specified by 'expr' before all of the children at 'location'. for (int i = value.size(); i; i--) { Node importedNode = (value[i-1].getOwnerDocument() == _doc ? value[i-1].cloneNode(true) : _doc.importNode(value[i-1], true)); element.insertBefore(importedNode, element.getFirstChild()); } } else if (assignElem && HAS_ATTR(assignElem, "type") && boost::iequals(ATTR(assignElem, "type"), "lastchild")) { // lastchild: Insert the value specified by 'expr' after all of the children at 'location'. for (int i = 0; i < value.size(); i++) { Node importedNode = (value[i].getOwnerDocument() == _doc ? value[i].cloneNode(true) : _doc.importNode(value[i], true)); element.appendChild(importedNode); } } else if (assignElem && HAS_ATTR(assignElem, "type") && boost::iequals(ATTR(assignElem, "type"), "previoussibling")) { // previoussibling: Insert the value specified by 'expr' before the // node specified by 'location', keeping the same parent. Node parent = element.getParentNode(); if (!parent) throw Event("error.execution", Event::PLATFORM); for (int i = 0; i < value.size(); i++) { Node importedNode = (value[i].getOwnerDocument() == _doc ? value[i].cloneNode(true) : _doc.importNode(value[i], true)); parent.insertBefore(importedNode, element); } } else if (assignElem && HAS_ATTR(assignElem, "type") && boost::iequals(ATTR(assignElem, "type"), "nextsibling")) { // nextsibling: Insert the value specified by 'expr' after the node // specified by 'location', keeping the same parent. Node parent = element.getParentNode(); if (!parent) throw Event("error.execution", Event::PLATFORM); for (int i = value.size(); i; i--) { Node importedNode = (value[i-1].getOwnerDocument() == _doc ? value[i-1].cloneNode(true) : _doc.importNode(value[i-1], true)); Node nextSibling = element.getNextSibling(); if (nextSibling) { parent.insertBefore(importedNode, element.getNextSibling()); } else { parent.appendChild(importedNode); } } } else if (assignElem && HAS_ATTR(assignElem, "type") && boost::iequals(ATTR(assignElem, "type"), "replace")) { // replace: Replace the node specified by 'location' by the value specified by 'expr'. Node parent = element.getParentNode(); if (!parent) throw Event("error.execution", Event::PLATFORM); if (value.size() != 1) throw Event("error.execution", Event::PLATFORM); Node importedNode = (value[0].getOwnerDocument() == _doc ? value[0].cloneNode(true) : _doc.importNode(value[0], true)); parent.replaceChild(importedNode, element); } else if (assignElem && HAS_ATTR(assignElem, "type") && boost::iequals(ATTR(assignElem, "type"), "delete")) { // delete: Delete the node specified by 'location'. ('expr' is ignored.). Node parent = element.getParentNode(); if (!parent) throw Event("error.execution", Event::PLATFORM); parent.removeChild(element); } else { // replacechildren: Replace all the children at 'location' with the value specified by 'expr'. while(element.hasChildNodes()) element.removeChild(element.getChildNodes().item(0)); for (int i = 0; i < value.size(); i++) { Node importedNode = (value[i].getOwnerDocument() == _doc ? value[i].cloneNode(true) : _doc.importNode(value[i], true)); element.appendChild(importedNode); } } } XPathValue NodeSetVariableResolver::resolveVariable(const std::string& namepaceUri, const std::string& name) const { std::map >::const_iterator n = _variables.find(name); if(n == _variables.end()) { throw Event("error.execution"); } #if VERBOSE std::cout << std::endl << "Getting " << name << ":" << std::endl; for (int i = 0; i < n->second.size(); i++) { std::cout << n->second[i].getNodeType() << " | " << n->second[i] << std::endl; } std::cout << std::endl; #endif return XPathValue(new NodeSetValue(n->second)); } void NodeSetVariableResolver::setVariable(const std::string& name, const NodeSet& value) { #if VERBOSE std::cout << std::endl << "Setting " << name << ":" << std::endl; for (int i = 0; i < value.size(); i++) { std::cout << value[i].getNodeType() << " | " << value[i] << std::endl; } std::cout << std::endl; #endif _variables[name] = value; } bool NodeSetVariableResolver::isDeclared(const std::string& name) { return _variables.find(name) != _variables.end(); } XPathFunction* XPathFunctionResolver::resolveFunction(const std::string& namespace_uri, const std::string& name, const std::vector >& argExprs) const { if (boost::iequals(name, "in")) { return new XPathFunctionIn(1, -1, argExprs, _interpreter); } return _xpathFuncRes.resolveFunction(namespace_uri, name, argExprs); } std::vector > XPathFunctionResolver::validNames() const { std::vector > names = _xpathFuncRes.validNames(); names.push_back(std::make_pair("", "In")); return names; } bool XPathFunctionIn::doEvaluate(const Node& context, const ExecutionContext& executionContext) const { for (int i = 0; i < argCount(); i++) { XPathValue stateName = arg(i, context, executionContext); if (stateName.type() == STRING) { if (!Interpreter::isMember(_interpreter->getState(stateName.asString()), _interpreter->getConfiguration())) { return false; } } } return true; } }