]> git.sesse.net Git - xml-template/blob - php5-swig/xml-template.swig
Fix a segfaulting bug in the PHP5 SWIG version; we would get confused when PHP had...
[xml-template] / php5-swig / xml-template.swig
1 %module XML_Template_SWIG
2 %include <std_string.i>
3
4 struct XmlDocPtrWrapper {
5         ~XmlDocPtrWrapper();
6 };
7
8 %{
9
10 #include <memory>
11 #include <libxml/globals.h>
12
13 #include "../c++11/xml-template.h"
14
15 struct XmlDocWrapper {
16         ~XmlDocWrapper() { xmlFreeDoc(ptr); }
17         xmlDocPtr ptr;
18 };
19 typedef std::shared_ptr<XmlDocWrapper> XmlDocPtrWrapper;
20
21 bool is_associative_array(HashTable *ht)
22 {
23         if (ht->nNumOfElements == 0) {
24                 return true;
25         }
26         for (unsigned i = 0; i < ht->nNumOfElements; ++i) {
27                 if (!zend_hash_index_exists(ht, i)) {
28                         return true;
29                 }
30         }
31         return false;
32 }
33
34 Directive* convert_php_objects_to_directive(zval *obj)
35 {
36         switch (Z_TYPE_P(obj)) {
37         case IS_ARRAY: {
38                 HashTable *ht = Z_ARRVAL_P(obj);
39                 if (is_associative_array(ht)) {
40                         std::unordered_map<std::string, Directive *> my_map;
41                         for (zend_hash_internal_pointer_reset(ht); zend_hash_has_more_elements(ht) == SUCCESS; zend_hash_move_forward(ht)) {
42                                 char *str_key;
43                                 ulong num_key;
44                                 zend_hash_get_current_key(ht, &str_key, &num_key, 0);
45
46                                 std::string key;
47                                 if (zend_hash_get_current_key_type(ht) == HASH_KEY_IS_STRING) {
48                                         key = str_key;
49                                 } else {
50                                         char buf[32];
51                                         sprintf(buf, "%lu", num_key);
52                                         key = buf;
53                                 }
54
55                                 zval **data;
56                                 zend_hash_get_current_data(ht, (void **)&data);
57                                 my_map.insert(make_pair(key, convert_php_objects_to_directive(*data)));
58                         }
59                         return new Substitute(my_map);
60                 } else {
61                         std::vector<Directive *> subdirectives;
62                         for (unsigned i = 0; i < ht->nNumOfElements; ++i) {
63                                 zval **data;
64                                 zend_hash_index_find(ht, i, (void **)&data);
65                                 subdirectives.push_back(convert_php_objects_to_directive(*data));
66                         }
67                         return new Clone(subdirectives);
68                 }
69                 break;
70         }
71         case IS_STRING: {
72                 char *str = Z_STRVAL_P(obj);
73                 return new Replace(str);
74         }
75         case IS_LONG: {
76                 char str[256];
77                 snprintf(str, sizeof(str), "%ld", Z_LVAL_P(obj));
78                 return new Replace(str);
79         }
80         case IS_DOUBLE: {
81                 char str[256];
82                 snprintf(str, sizeof(str), "%f", Z_DVAL_P(obj));
83                 return new Replace(str);
84         }
85         case IS_RESOURCE: {
86                 XmlDocPtrWrapper *doc;
87                 if (SWIG_ConvertPtr(obj, (void **)&doc, SWIGTYPE_p_XmlDocPtrWrapper, 0) < 0 || doc == NULL) {
88                         return NULL;
89                 }
90                 return new ReplaceInclude(xmlCopyDoc((*doc)->ptr, 1));
91         }
92         case IS_NULL:
93                 return new Replace { "" };
94         default:
95                 printf("WARNING: Unknown type %d!\n", Z_TYPE_P(obj));
96                 break;
97         }
98
99         return NULL;
100 }
101
102 XmlDocPtrWrapper XML_Template_process_file(const std::string &input_filename, Directive *root_directive, bool clean)
103 {
104         xmlDocPtr ret = process_file(input_filename, root_directive, clean);
105         delete root_directive;
106         return XmlDocPtrWrapper(new XmlDocWrapper { ret });
107 }
108
109 void XML_Template_process(XmlDocPtrWrapper doc, Directive *root_directive, bool clean)
110 {
111         root_directive->process(xmlDocGetRootElement(doc->ptr), clean);
112         delete root_directive;
113 }
114
115 namespace {
116
117 int write_to_string(void *context, const char *buffer, int len)
118 {
119         std::string *str = reinterpret_cast<std::string *>(context);
120         str->append(buffer, len);
121         return len;
122 }
123
124 int close_string(void *context)
125 {
126         return 0;
127 }
128
129 }  // namespace
130
131 std::string XML_Template_convert_doc_to_string(XmlDocPtrWrapper doc, bool prettyprint)
132 {
133         xmlIndentTreeOutput = prettyprint;
134         std::string ret;
135         xmlOutputBufferPtr buf = xmlOutputBufferCreateIO(write_to_string, close_string, &ret, NULL);
136         xmlSaveFormatFileTo(buf, doc->ptr, "UTF-8", prettyprint);
137         return ret;
138 }
139
140 namespace {
141
142 // Remove document fragments (ie. move their content up in the parent node)
143 // and combine neighboring text nodes into one.
144 void normalize_node(xmlNodePtr node)
145 {
146         xmlNode *next_child;
147         for (xmlNode *child = node->children; child != NULL; child = next_child) {
148                 next_child = child->next;
149                 if (child->type == XML_DOCUMENT_FRAG_NODE) {
150                         while (child->children != NULL) {
151                                 xmlAddPrevSibling(child, child->children);
152                         }
153
154                         xmlUnlinkNode(child);
155                         xmlFreeNode(child);
156                 }
157         }
158
159         // xmlAddPrevSibling merges adjacent text nodes, but many other things
160         // (including xmlUnlinkNode) do not, so make an extra pass.
161         for (xmlNode *child = node->children; child != NULL; child = child->next) {
162                 while (child->type == XML_TEXT_NODE && (child->next != NULL && child->next->type == XML_TEXT_NODE)) {
163                         xmlNode *next_child = child->next;
164
165                         xmlChar *content = xmlNodeGetContent(next_child);
166                         xmlNodeAddContent(child, content);
167                         xmlFree(content);
168
169                         xmlUnlinkNode(next_child);
170                         xmlFreeNode(next_child);
171                 }
172                 normalize_node(child);
173         }
174 }
175
176 // Clean the page of non-necessary whitespace. Leaves whitespace alone if and
177 // only if xml:space="preserve" on the element. (IOW, it doesn't parse the DTDs,
178 // nor the CSS.)
179 void clean_node(xmlNodePtr node, bool preserve_whitespace, bool aggressive)
180 {
181         if (node->type == XML_TEXT_NODE) {
182                 std::string content = reinterpret_cast<const char *>(xmlNodeGetContent(node));
183                 if (!preserve_whitespace) {
184                         unsigned dstpos = 0;
185                         for (unsigned srcpos = 0; srcpos < content.size(); ++srcpos, ++dstpos) {
186                                 if (content[srcpos] == '\n' ||
187                                     content[srcpos] == '\t' ||
188                                     content[srcpos] == ' ') {
189                                         content[dstpos] = ' ';
190
191                                         // compress double spaces
192                                         if (dstpos > 0 && content[dstpos - 1] == ' ') {
193                                                 --dstpos;
194                                         }
195                                 } else {
196                                         content[dstpos] = content[srcpos];
197                                 }
198                         }
199                         content.resize(dstpos);
200                 }
201                 if (content.empty() || (aggressive && content == " ")) {
202                         xmlUnlinkNode(node);
203                         xmlFreeNode(node);
204                 } else {
205                         xmlNodeSetContentLen(node, reinterpret_cast<const xmlChar *>(content.data()), content.size());
206                 }
207         } else {
208                 if (node->type == XML_ELEMENT_NODE) {
209                         xmlChar *space = xmlGetProp(node, reinterpret_cast<const xmlChar *>("xml:space"));
210                         preserve_whitespace = (space != NULL && strcmp(reinterpret_cast<const char *>(space), "preserve") == 0);
211                 }
212
213                 xmlNode *next_child;
214                 for (xmlNode *child = node->children; child != NULL; child = next_child) {
215                         next_child = child->next;
216                         clean_node(child, preserve_whitespace, aggressive);
217                 }
218
219                 if (node->type == XML_ELEMENT_NODE && node->children == NULL) {
220                         std::string tag = reinterpret_cast<const char *>(node->name);
221
222                         // These are the only elements allowed in XHTML to be EMPTY,
223                         // so insert dummy nodes to prevent the output from using
224                         // the <foo/> syntax where not appropriate.
225                         if (tag != "base" && tag != "meta" && tag != "link" && tag != "hr" &&
226                             tag != "br" && tag != "param" && tag != "img" && tag != "area" &&
227                             tag != "input" && tag != "col") {
228                                 xmlNode *text = xmlNewText(reinterpret_cast<const xmlChar *>(""));
229                                 xmlAddChild(node, text);
230                         }
231                 }
232         }
233 }
234
235 }  // namespace
236
237 void XML_Template_clean_whitespace(XmlDocPtrWrapper doc, bool aggressive)
238 {
239         normalize_node(xmlDocGetRootElement(doc->ptr));
240         clean_node(xmlDocGetRootElement(doc->ptr), false, aggressive);
241 }
242
243 %}
244
245 %typemap(in) Directive* {
246         $1 = convert_php_objects_to_directive(*$input);
247 }
248
249 XmlDocPtrWrapper XML_Template_process_file(const std::string &input_filename, Directive *root_directive, bool clean);
250 void XML_Template_process(XmlDocPtrWrapper doc, Directive *root_directive, bool clean);
251 void XML_Template_clean_whitespace(XmlDocPtrWrapper doc, bool aggressive);
252 std::string XML_Template_convert_doc_to_string(XmlDocPtrWrapper doc, bool prettyprint);
253