Current section
6 Versions
Jump to
Current section
6 Versions
Compare versions
23
files changed
+479
additions
-17
deletions
| @@ -6,6 +6,37 @@ Nokogiri. But like, for Elixir. | |
| 6 6 | |
| 7 7 | **DOUBLE NOTE**: C is **not** my first language. I've probably got memory leaks and just plain logic errors hanging about all over. Contributions and criticism welcome. |
| 8 8 | |
| 9 | + ## Features |
| 10 | + |
| 11 | + * XML Documents |
| 12 | + * [x] Read document from string |
| 13 | + * [ ] Read document from path |
| 14 | + * [x] Create new documents |
| 15 | + * [ ] Attribute support |
| 16 | + * [ ] Get/set for attributes by namespace |
| 17 | + * [x] Get/set without namespace |
| 18 | + * [ ] Element support |
| 19 | + * [x] Create new elements |
| 20 | + * [x] Remove elements |
| 21 | + * [x] Move elements |
| 22 | + * [ ] Copy elements |
| 23 | + * [ ] XML fragment support |
| 24 | + * [x] Set element content |
| 25 | + * [ ] Append element content |
| 26 | + * XML Schemas |
| 27 | + * [x] Load schemas from string (with path awareness for includes) |
| 28 | + * [ ] Load schemas from path |
| 29 | + * [x] In-memory document validation |
| 30 | + * [ ] Streaming document validation (not fully supported by libxml2 - may not ever be completed) |
| 31 | + * XSL Transforms |
| 32 | + * [x] Simple File transforms and parameters |
| 33 | + * [ ] Included/multi-file transform support |
| 34 | + * XPath Support |
| 35 | + * [x] Return element lists |
| 36 | + * [ ] Return non-element result sets |
| 37 | + * [x] Namespaced queries |
| 38 | + * [ ] Namespaceless queries |
| 39 | + |
| 9 40 | ## Installation |
| 10 41 | |
| 11 42 | Using [Hex](https://hex.pm), the package can be installed by adding `exogiri` to your list of dependencies in `mix.exs`: |
| @@ -13,7 +44,7 @@ Using [Hex](https://hex.pm), the package can be installed by adding `exogiri` to | |
| 13 44 | ```elixir |
| 14 45 | def deps do |
| 15 46 | [ |
| 16 | - {:exogiri, "~> 0.2.3"} |
| 47 | + {:exogiri, "~> 0.3.0"} |
| 17 48 | ] |
| 18 49 | end |
| 19 50 | ``` |
| @@ -3,24 +3,29 @@ | |
| 3 3 | #include "document.h" |
| 4 4 | #include "xpath.h" |
| 5 5 | #include "schema.h" |
| 6 | + #include "stylesheet.h" |
| 6 7 | |
| 7 8 | static int open_resource(ErlNifEnv* env) |
| 8 9 | { |
| 9 10 | const char* d_mod_str = "Elixir.Exogiri.Xml.Internal.Nif.Document"; |
| 10 11 | const char* n_mod_str = "Elixir.Exogiri.Xml.Internal.Nif.Node"; |
| 11 12 | const char* s_mod_str = "Elixir.Exogiri.Xml.Internal.Nif.Schema"; |
| 13 | + const char* ss_mod_str = "Elixir.Exogiri.Xml.Internal.Nif.StyleSheet"; |
| 12 14 | const char* d_name = "exogiri_xml_document"; |
| 13 15 | const char* n_name = "exogiri_xml_node"; |
| 14 16 | const char* s_name = "exogiri_xml_schema"; |
| 17 | + const char* ss_name = "exogiri_xml_stylesheet"; |
| 15 18 | |
| 16 19 | int flags = ERL_NIF_RT_CREATE | ERL_NIF_RT_TAKEOVER; |
| 17 20 | |
| 18 21 | EXD_RES_TYPE = enif_open_resource_type(env, d_mod_str, d_name, free_document, flags, NULL); |
| 19 22 | EXN_RES_TYPE = enif_open_resource_type(env, n_mod_str, n_name, free_node, flags, NULL); |
| 20 23 | EXS_RES_TYPE = enif_open_resource_type(env, s_mod_str, s_name, free_schema, flags, NULL); |
| 24 | + EXSS_RES_TYPE = enif_open_resource_type(env, ss_mod_str, ss_name, free_stylesheet, flags, NULL); |
| 21 25 | if(EXD_RES_TYPE == NULL) return -1; |
| 22 26 | if(EXN_RES_TYPE == NULL) return -1; |
| 23 27 | if(EXS_RES_TYPE == NULL) return -1; |
| 28 | + if(EXSS_RES_TYPE == NULL) return -1; |
| 24 29 | return 0; |
| 25 30 | } |
| 26 31 | |
| @@ -76,6 +81,8 @@ static ErlNifFunc nif_funcs[] = | |
| 76 81 | {"priv_schema_from_string", 1, priv_schema_from_string, ERL_NIF_DIRTY_JOB_CPU_BOUND}, |
| 77 82 | {"priv_schema_from_string_with_path", 2, priv_schema_from_string_with_path, ERL_NIF_DIRTY_JOB_CPU_BOUND}, |
| 78 83 | {"priv_schema_validate_doc", 2, priv_schema_validate_doc, ERL_NIF_DIRTY_JOB_CPU_BOUND}, |
| 84 | + {"priv_stylesheet_from_string", 1, priv_stylesheet_from_string, ERL_NIF_DIRTY_JOB_CPU_BOUND}, |
| 85 | + {"priv_stylesheet_transform", 3, priv_stylesheet_transform, ERL_NIF_DIRTY_JOB_CPU_BOUND}, |
| 79 86 | {"priv_doc_new_root_no_ns", 1, priv_doc_new_root_no_ns, 0}, |
| 80 87 | {"priv_doc_new_root_with_ns", 3, priv_doc_new_root_with_ns, 0} |
| 81 88 | }; |
| @@ -7,12 +7,16 @@ | |
| 7 7 | #include <libxml/xpathInternals.h> |
| 8 8 | #include <libxml/c14n.h> |
| 9 9 | #include <libxml/xmlschemas.h> |
| 10 | + #include <libxslt/xsltInternals.h> |
| 11 | + #include <libxslt/xsltutils.h> |
| 12 | + #include <libxslt/transform.h> |
| 10 13 | |
| 11 14 | #include "unlinked_node.h" |
| 12 15 | |
| 13 16 | ErlNifResourceType* EXD_RES_TYPE; |
| 14 17 | ErlNifResourceType* EXN_RES_TYPE; |
| 15 18 | ErlNifResourceType* EXS_RES_TYPE; |
| 19 | + ErlNifResourceType* EXSS_RES_TYPE; |
| 16 20 | |
| 17 21 | typedef struct { |
| 18 22 | ErlNifPid* owner; |
| @@ -39,7 +43,13 @@ typedef struct { | |
| 39 43 | xmlDocPtr doc; |
| 40 44 | } Schema; |
| 41 45 | |
| 46 | + typedef struct { |
| 47 | + ErlNifPid* owner; |
| 48 | + xsltStylesheetPtr stylesheet; |
| 49 | + } Stylesheet; |
| 50 | + |
| 42 51 | void Exogiri_error_array_pusher(void *ctx, xmlErrorPtr error); |
| 52 | + char *nif_binary_to_char(ErlNifBinary * bin); |
| 43 53 | xmlChar *nif_binary_to_xmlChar(ErlNifBinary * bin); |
| 44 54 | ERL_NIF_TERM xml_char_to_binary_term(ErlNifEnv* env, xmlChar *content); |
| @@ -3,6 +3,7 @@ | |
| 3 3 | void free_schema(__attribute__((unused))ErlNifEnv* env, void* obj) { |
| 4 4 | Schema* schema = (Schema*)obj; |
| 5 5 | xmlSchemaFree(schema->schema); |
| 6 | + enif_free(schema->owner); |
| 6 7 | if (schema->doc) { |
| 7 8 | xmlFreeDoc(schema->doc); |
| 8 9 | } |
| @@ -0,0 +1,325 @@ | |
| 1 | + #include "exogiri.h" |
| 2 | + |
| 3 | + void free_stylesheet(__attribute__((unused))ErlNifEnv* env, void* obj) { |
| 4 | + Stylesheet* stylesheet = (Stylesheet*)obj; |
| 5 | + xsltFreeStylesheet(stylesheet->stylesheet); |
| 6 | + enif_free(stylesheet->owner); |
| 7 | + } |
| 8 | + |
| 9 | + static void |
| 10 | + xslt_generic_error_handler(void *ctx, const char *msg, ...) |
| 11 | + { |
| 12 | + Errors* errors = (Errors*)ctx; |
| 13 | + ERL_NIF_TERM new_error; |
| 14 | + ERL_NIF_TERM existing_errors; |
| 15 | + char *message; |
| 16 | + size_t err_size; |
| 17 | + va_list args; |
| 18 | + va_list args_for_len; |
| 19 | + |
| 20 | + va_start(args, msg); |
| 21 | + va_copy(args_for_len, args); |
| 22 | + err_size = vsnprintf(NULL, 0, msg, args_for_len); |
| 23 | + message = (char*)malloc(sizeof(char) * err_size + 1); |
| 24 | + vsnprintf(message, err_size, msg, args); |
| 25 | + va_end(args); |
| 26 | + existing_errors = errors->errors; |
| 27 | + new_error = enif_make_string(errors->env, message, ERL_NIF_LATIN1); |
| 28 | + errors->errors = enif_make_list_cell( |
| 29 | + errors->env, |
| 30 | + new_error, |
| 31 | + existing_errors |
| 32 | + ); |
| 33 | + free(message); |
| 34 | + } |
| 35 | + |
| 36 | + static ERL_NIF_TERM read_stylesheet_from_doc(ErlNifEnv* env, xmlDocPtr doc) { |
| 37 | + ErlNifPid *self; |
| 38 | + xsltStylesheetPtr ss; |
| 39 | + Errors* parse_errors; |
| 40 | + ERL_NIF_TERM result; |
| 41 | + ERL_NIF_TERM pf_atom; |
| 42 | + ERL_NIF_TERM atom_result; |
| 43 | + unsigned int err_len; |
| 44 | + |
| 45 | + self = (ErlNifPid *)enif_alloc(sizeof(ErlNifPid)); |
| 46 | + enif_self(env, self); |
| 47 | + |
| 48 | + parse_errors = (Errors*)enif_alloc(sizeof(Errors)); |
| 49 | + parse_errors->env = env; |
| 50 | + parse_errors->errors = enif_make_list(env, 0); |
| 51 | + |
| 52 | + xsltSetGenericErrorFunc((void *)parse_errors, xslt_generic_error_handler); |
| 53 | + ss = xsltParseStylesheetDoc(doc); |
| 54 | + xsltSetGenericErrorFunc(NULL, NULL); |
| 55 | + enif_get_list_length(env,parse_errors->errors,&err_len); |
| 56 | + if (err_len > 0) { |
| 57 | + if (!ss) { |
| 58 | + xmlFreeDoc(doc); |
| 59 | + } else { |
| 60 | + xsltFreeStylesheet(ss); |
| 61 | + } |
| 62 | + ASSIGN_ERROR(env, atom_result); |
| 63 | + result = enif_make_tuple2( |
| 64 | + env, |
| 65 | + atom_result, |
| 66 | + parse_errors->errors |
| 67 | + ); |
| 68 | + enif_free(parse_errors); |
| 69 | + return result; |
| 70 | + } |
| 71 | + if (!ss) { |
| 72 | + xmlFreeDoc(doc); |
| 73 | + enif_make_existing_atom( |
| 74 | + env, |
| 75 | + "unknown_error", |
| 76 | + &pf_atom, |
| 77 | + ERL_NIF_LATIN1 |
| 78 | + ); |
| 79 | + ASSIGN_ERROR(env, atom_result); |
| 80 | + result = enif_make_tuple2( |
| 81 | + env, |
| 82 | + atom_result, |
| 83 | + pf_atom |
| 84 | + ); |
| 85 | + enif_free(parse_errors); |
| 86 | + return result; |
| 87 | + } |
| 88 | + |
| 89 | + enif_free(parse_errors); |
| 90 | + |
| 91 | + Stylesheet* ssRes = (Stylesheet *)enif_alloc_resource(EXSS_RES_TYPE, sizeof(Stylesheet)); |
| 92 | + ssRes->owner = self; |
| 93 | + ssRes->stylesheet = ss; |
| 94 | + result = enif_make_resource(env, ssRes); |
| 95 | + enif_release_resource(ssRes); |
| 96 | + |
| 97 | + ASSIGN_OK(env, atom_result); |
| 98 | + return enif_make_tuple2( |
| 99 | + env, |
| 100 | + atom_result, |
| 101 | + result |
| 102 | + ); |
| 103 | + } |
| 104 | + |
| 105 | + ERL_NIF_TERM priv_stylesheet_from_string(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { |
| 106 | + ErlNifBinary nb; |
| 107 | + ERL_NIF_TERM atom_result; |
| 108 | + xmlDocPtr doc; |
| 109 | + Errors* parse_errors; |
| 110 | + ERL_NIF_TERM pf_atom; |
| 111 | + ERL_NIF_TERM result; |
| 112 | + unsigned int err_len; |
| 113 | + |
| 114 | + if(argc != 1) |
| 115 | + { |
| 116 | + return enif_make_badarg(env); |
| 117 | + } |
| 118 | + if (!enif_inspect_binary(env, argv[0], &nb)) { |
| 119 | + return enif_make_badarg(env); |
| 120 | + } |
| 121 | + |
| 122 | + parse_errors = (Errors*)enif_alloc(sizeof(Errors)); |
| 123 | + parse_errors->env = env; |
| 124 | + parse_errors->errors = enif_make_list(env, 0); |
| 125 | + |
| 126 | + xmlSetStructuredErrorFunc((void *)parse_errors, Exogiri_error_array_pusher); |
| 127 | + doc = xmlReadMemory( |
| 128 | + (const char *)nb.data, |
| 129 | + nb.size, |
| 130 | + NULL, |
| 131 | + NULL, |
| 132 | + 0 |
| 133 | + ); |
| 134 | + xmlSetStructuredErrorFunc(NULL, NULL); |
| 135 | + |
| 136 | + enif_get_list_length(env,parse_errors->errors,&err_len); |
| 137 | + if (err_len > 0) { |
| 138 | + if (doc) { |
| 139 | + xmlFreeDoc(doc); |
| 140 | + } |
| 141 | + ASSIGN_ERROR(env, atom_result); |
| 142 | + result = enif_make_tuple2( |
| 143 | + env, |
| 144 | + atom_result, |
| 145 | + parse_errors->errors |
| 146 | + ); |
| 147 | + enif_free(parse_errors); |
| 148 | + return result; |
| 149 | + } |
| 150 | + if (!doc) { |
| 151 | + enif_make_existing_atom( |
| 152 | + env, |
| 153 | + "unknown_error", |
| 154 | + &pf_atom, |
| 155 | + ERL_NIF_LATIN1 |
| 156 | + ); |
| 157 | + ASSIGN_ERROR(env, atom_result); |
| 158 | + result = enif_make_tuple2( |
| 159 | + env, |
| 160 | + atom_result, |
| 161 | + pf_atom |
| 162 | + ); |
| 163 | + enif_free(parse_errors); |
| 164 | + return result; |
| 165 | + } |
| 166 | + |
| 167 | + enif_free(parse_errors); |
| 168 | + |
| 169 | + return read_stylesheet_from_doc(env, doc); |
| 170 | + } |
| 171 | + |
| 172 | + static void free_params_array(char **p_array) { |
| 173 | + char *cur_ptr; |
| 174 | + |
| 175 | + if (!p_array) { |
| 176 | + return; |
| 177 | + } |
| 178 | + |
| 179 | + int i = 0; |
| 180 | + cur_ptr = p_array[i]; |
| 181 | + |
| 182 | + while(cur_ptr != NULL) { |
| 183 | + enif_free(cur_ptr); |
| 184 | + i++; |
| 185 | + cur_ptr = p_array[i]; |
| 186 | + } |
| 187 | + |
| 188 | + enif_free(p_array); |
| 189 | + } |
| 190 | + |
| 191 | + static char **extract_transform_params(ErlNifEnv* env, ERL_NIF_TERM param_list, unsigned int list_size) { |
| 192 | + char **transform_params; |
| 193 | + ERL_NIF_TERM head; |
| 194 | + ERL_NIF_TERM tail; |
| 195 | + ERL_NIF_TERM tail2; |
| 196 | + ERL_NIF_TERM current; |
| 197 | + ErlNifBinary kb; |
| 198 | + ErlNifBinary vb; |
| 199 | + char *kc; |
| 200 | + char *vc; |
| 201 | + size_t allocated_size; |
| 202 | + |
| 203 | + if (list_size < 1) { |
| 204 | + return NULL; |
| 205 | + } |
| 206 | + |
| 207 | + allocated_size = (sizeof(char *) * list_size) + 1; |
| 208 | + |
| 209 | + transform_params = enif_alloc(allocated_size); |
| 210 | + transform_params[list_size] = NULL; |
| 211 | + |
| 212 | + current = param_list; |
| 213 | + |
| 214 | + for (unsigned int i = 0; i < list_size; i = i + 2) { |
| 215 | + enif_get_list_cell(env, current, &head, &tail); |
| 216 | + enif_inspect_binary(env, head, &kb); |
| 217 | + kc = nif_binary_to_char(&kb); |
| 218 | + enif_get_list_cell(env, tail, &head, &tail2); |
| 219 | + enif_inspect_binary(env, head, &vb); |
| 220 | + vc = nif_binary_to_char(&vb); |
| 221 | + transform_params[i] = kc; |
| 222 | + transform_params[i + 1] = vc; |
| 223 | + current = tail2; |
| 224 | + } |
| 225 | + |
| 226 | + return transform_params; |
| 227 | + } |
| 228 | + |
| 229 | + ERL_NIF_TERM priv_stylesheet_transform(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { |
| 230 | + ERL_NIF_TERM atom_result; |
| 231 | + ERL_NIF_TERM pf_atom; |
| 232 | + ERL_NIF_TERM result; |
| 233 | + xmlDocPtr result_doc; |
| 234 | + Document *document; |
| 235 | + Stylesheet *s_sheet; |
| 236 | + ErlNifPid self; |
| 237 | + ErlNifPid *doc_owner; |
| 238 | + Errors *parse_errors; |
| 239 | + char **transform_params; |
| 240 | + unsigned int err_len; |
| 241 | + unsigned int param_len; |
| 242 | + |
| 243 | + if(argc != 3) |
| 244 | + { |
| 245 | + return enif_make_badarg(env); |
| 246 | + } |
| 247 | + if (!enif_get_resource(env, argv[0],EXSS_RES_TYPE,(void **)&s_sheet)) { |
| 248 | + return enif_make_badarg(env); |
| 249 | + } |
| 250 | + if (!enif_get_resource(env, argv[1],EXD_RES_TYPE,(void **)&document)) { |
| 251 | + return enif_make_badarg(env); |
| 252 | + } |
| 253 | + if (!enif_is_list(env, argv[2])) { |
| 254 | + return enif_make_badarg(env); |
| 255 | + } |
| 256 | + |
| 257 | + CHECK_STRUCT_OWNER(env, self, s_sheet) |
| 258 | + CHECK_STRUCT_OWNER(env, self, document) |
| 259 | + |
| 260 | + parse_errors = (Errors*)enif_alloc(sizeof(Errors)); |
| 261 | + parse_errors->env = env; |
| 262 | + parse_errors->errors = enif_make_list(env, 0); |
| 263 | + |
| 264 | + enif_get_list_length(env, argv[2], ¶m_len); |
| 265 | + transform_params = extract_transform_params(env, argv[2],param_len); |
| 266 | + |
| 267 | + xsltSetGenericErrorFunc((void *)parse_errors, xslt_generic_error_handler); |
| 268 | + xmlSetGenericErrorFunc((void *)parse_errors, xslt_generic_error_handler); |
| 269 | + result_doc = xsltApplyStylesheet(s_sheet->stylesheet, document->doc, (const char **)transform_params);; |
| 270 | + xsltSetGenericErrorFunc(NULL, NULL); |
| 271 | + xmlSetGenericErrorFunc(NULL, NULL); |
| 272 | + |
| 273 | + free_params_array(transform_params); |
| 274 | + |
| 275 | + enif_get_list_length(env,parse_errors->errors,&err_len); |
| 276 | + if (err_len > 0) { |
| 277 | + if (result_doc) { |
| 278 | + xmlFreeDoc(result_doc); |
| 279 | + } |
| 280 | + ASSIGN_ERROR(env, atom_result); |
| 281 | + result = enif_make_tuple2( |
| 282 | + env, |
| 283 | + atom_result, |
| 284 | + parse_errors->errors |
| 285 | + ); |
| 286 | + enif_free(parse_errors); |
| 287 | + return result; |
| 288 | + } |
| 289 | + if (!result_doc) { |
| 290 | + enif_make_existing_atom( |
| 291 | + env, |
| 292 | + "unknown_error", |
| 293 | + &pf_atom, |
| 294 | + ERL_NIF_LATIN1 |
| 295 | + ); |
| 296 | + ASSIGN_ERROR(env, atom_result); |
| 297 | + result = enif_make_tuple2( |
| 298 | + env, |
| 299 | + atom_result, |
| 300 | + pf_atom |
| 301 | + ); |
| 302 | + enif_free(parse_errors); |
| 303 | + return result; |
| 304 | + } |
| 305 | + |
| 306 | + enif_free(parse_errors); |
| 307 | + |
| 308 | + doc_owner = (ErlNifPid *)enif_alloc(sizeof(ErlNifPid)); |
| 309 | + enif_self(env, doc_owner); |
| 310 | + |
| 311 | + Document* docRes = (Document *)enif_alloc_resource(EXD_RES_TYPE, sizeof(Document)); |
| 312 | + docRes->owner = doc_owner; |
| 313 | + docRes->doc = result_doc; |
| 314 | + docRes->unlinked_nodes = NULL; |
| 315 | + docRes->unlinked_nses = NULL; |
| 316 | + result = enif_make_resource(env, docRes); |
| 317 | + enif_release_resource(docRes); |
| 318 | + |
| 319 | + ASSIGN_OK(env, atom_result); |
| 320 | + return enif_make_tuple2( |
| 321 | + env, |
| 322 | + atom_result, |
| 323 | + result |
| 324 | + ); |
| 325 | + } |
| \ No newline at end of file |
Loading more files…