%module XML_Template_SWIG %include struct XmlDocPtrWrapper { ~XmlDocPtrWrapper(); }; %{ #include #include #include "../c++11/xml-template.h" struct XmlDocWrapper { ~XmlDocWrapper() { xmlFreeDoc(ptr); } xmlDocPtr ptr; }; typedef std::shared_ptr XmlDocPtrWrapper; bool is_associative_array(HashTable *ht) { if (ht->nNumOfElements == 0) { return true; } for (unsigned i = 0; i < ht->nNumOfElements; ++i) { if (!zend_hash_index_exists(ht, i)) { return true; } } return false; } Directive* convert_php_objects_to_directive(zval *obj) { switch (Z_TYPE_P(obj)) { case IS_ARRAY: { HashTable *ht = Z_ARRVAL_P(obj); if (is_associative_array(ht)) { std::unordered_map my_map; zend_string *str_key; zval *zv; ulong num_key; ZEND_HASH_FOREACH_KEY_VAL(ht, num_key, str_key, zv) { std::string key; if (str_key) { key.assign(str_key->val, str_key->len); } else { char buf[32]; sprintf(buf, "%lu", num_key); key = buf; } my_map.insert(make_pair(key, convert_php_objects_to_directive(zv))); } ZEND_HASH_FOREACH_END(); return new Substitute(my_map); } else { std::vector subdirectives; for (unsigned i = 0; i < ht->nNumOfElements; ++i) { zval *data = zend_hash_index_find(ht, i); subdirectives.push_back(convert_php_objects_to_directive(data)); } return new Clone(subdirectives); } break; } case IS_STRING: { std::string str(Z_STRVAL_P(obj), Z_STRLEN_P(obj)); return new Replace(str); } case IS_LONG: { char str[256]; snprintf(str, sizeof(str), "%ld", Z_LVAL_P(obj)); return new Replace(str); } case IS_DOUBLE: { char str[256]; snprintf(str, sizeof(str), "%f", Z_DVAL_P(obj)); return new Replace(str); } case IS_RESOURCE: { XmlDocPtrWrapper *doc; if (SWIG_ConvertPtr(obj, (void **)&doc, SWIGTYPE_p_XmlDocPtrWrapper, 0) < 0 || doc == NULL) { return NULL; } return new ReplaceInclude(xmlCopyDoc((*doc)->ptr, 1)); } case IS_REFERENCE: return convert_php_objects_to_directive(&Z_REF_P(obj)->val); case IS_NULL: return new Replace { "" }; default: printf("WARNING: Unknown type %d!\n", Z_TYPE_P(obj)); break; } return NULL; } XmlDocPtrWrapper XML_Template_process_file(const std::string &input_filename, Directive *root_directive, bool clean) { xmlDocPtr ret = process_file(input_filename, root_directive, clean); delete root_directive; return XmlDocPtrWrapper(new XmlDocWrapper { ret }); } void XML_Template_process(XmlDocPtrWrapper doc, Directive *root_directive, bool clean) { root_directive->process(xmlDocGetRootElement(doc->ptr), clean); delete root_directive; } namespace { int write_to_string(void *context, const char *buffer, int len) { std::string *str = reinterpret_cast(context); str->append(buffer, len); return len; } int close_string(void *context) { return 0; } } // namespace std::string XML_Template_convert_doc_to_string(XmlDocPtrWrapper doc, bool prettyprint) { xmlIndentTreeOutput = prettyprint; std::string ret; xmlOutputBufferPtr buf = xmlOutputBufferCreateIO(write_to_string, close_string, &ret, NULL); xmlSaveFormatFileTo(buf, doc->ptr, "UTF-8", prettyprint); return ret; } namespace { // Remove document fragments (ie. move their content up in the parent node) // and combine neighboring text nodes into one. void normalize_node(xmlNodePtr node) { xmlNode *next_child; for (xmlNode *child = node->children; child != NULL; child = next_child) { next_child = child->next; if (child->type == XML_DOCUMENT_FRAG_NODE) { while (child->children != NULL) { xmlAddPrevSibling(child, child->children); } xmlUnlinkNode(child); xmlFreeNode(child); } } // xmlAddPrevSibling merges adjacent text nodes, but many other things // (including xmlUnlinkNode) do not, so make an extra pass. for (xmlNode *child = node->children; child != NULL; child = child->next) { while (child->type == XML_TEXT_NODE && (child->next != NULL && child->next->type == XML_TEXT_NODE)) { xmlNode *next_child = child->next; xmlChar *content = xmlNodeGetContent(next_child); xmlNodeAddContent(child, content); xmlFree(content); xmlUnlinkNode(next_child); xmlFreeNode(next_child); } normalize_node(child); } } // Clean the page of non-necessary whitespace. Leaves whitespace alone if and // only if xml:space="preserve" on the element. (IOW, it doesn't parse the DTDs, // nor the CSS.) void clean_node(xmlNodePtr node, bool preserve_whitespace, bool aggressive) { if (node->type == XML_TEXT_NODE) { std::string content = reinterpret_cast(xmlNodeGetContent(node)); if (!preserve_whitespace) { unsigned dstpos = 0; for (unsigned srcpos = 0; srcpos < content.size(); ++srcpos, ++dstpos) { if (content[srcpos] == '\n' || content[srcpos] == '\t' || content[srcpos] == ' ') { content[dstpos] = ' '; // compress double spaces if (dstpos > 0 && content[dstpos - 1] == ' ') { --dstpos; } } else { content[dstpos] = content[srcpos]; } } content.resize(dstpos); } if (content.empty() || (aggressive && content == " ")) { xmlUnlinkNode(node); xmlFreeNode(node); } else { xmlNodeSetContentLen(node, reinterpret_cast(content.data()), content.size()); } } else { if (node->type == XML_ELEMENT_NODE) { xmlChar *space = xmlGetProp(node, reinterpret_cast("xml:space")); preserve_whitespace = (space != NULL && strcmp(reinterpret_cast(space), "preserve") == 0); } xmlNode *next_child; for (xmlNode *child = node->children; child != NULL; child = next_child) { next_child = child->next; clean_node(child, preserve_whitespace, aggressive); } if (node->type == XML_ELEMENT_NODE && node->children == NULL) { std::string tag = reinterpret_cast(node->name); // These are the only elements allowed in XHTML to be EMPTY, // so insert dummy nodes to prevent the output from using // the syntax where not appropriate. if (tag != "base" && tag != "meta" && tag != "link" && tag != "hr" && tag != "br" && tag != "param" && tag != "img" && tag != "area" && tag != "input" && tag != "col") { xmlNode *text = xmlNewText(reinterpret_cast("")); xmlAddChild(node, text); } } } } } // namespace void XML_Template_clean_whitespace(XmlDocPtrWrapper doc, bool aggressive) { normalize_node(xmlDocGetRootElement(doc->ptr)); clean_node(xmlDocGetRootElement(doc->ptr), false, aggressive); } %} %typemap(in) Directive* { $1 = convert_php_objects_to_directive(&$input); } XmlDocPtrWrapper XML_Template_process_file(const std::string &input_filename, Directive *root_directive, bool clean); void XML_Template_process(XmlDocPtrWrapper doc, Directive *root_directive, bool clean); void XML_Template_clean_whitespace(XmlDocPtrWrapper doc, bool aggressive); std::string XML_Template_convert_doc_to_string(XmlDocPtrWrapper doc, bool prettyprint);