diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..4ad1cdd8 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,7 @@ +*.txt text +*.h text +*.c text +*.rb text +*.yml text +*.vcproj text eol=crlf +*.sh text eol=lf \ No newline at end of file diff --git a/.github/workflows/mri.yml b/.github/workflows/mri.yml index d824304e..a3d3d583 100644 --- a/.github/workflows/mri.yml +++ b/.github/workflows/mri.yml @@ -1,39 +1,39 @@ -name: CI - -on: - push: - branches: [ master ] - pull_request: - branches: [ master ] - -jobs: - build: - name: >- - ${{ matrix.os }} ${{ matrix.ruby }}${{ matrix.yjit }} - env: - TESTOPTS: -v - - runs-on: ${{ matrix.os }} - if: | - !( contains(github.event.pull_request.title, '[ci skip]') - || contains(github.event.pull_request.title, '[skip ci]')) - strategy: - fail-fast: false - matrix: - os: [ubuntu-latest, macos-latest, windows-latest] - ruby: ['3.0', '3.1', '3.2', '3.3'] - steps: - - uses: actions/checkout@v3 - - name: Set up Ruby - uses: ruby/setup-ruby-pkgs@v1 - with: - ruby-version: ${{ matrix.ruby }} - apt-get: libxml2-dev - # brew: libxml2 - mingw: libxml2 - bundler-cache: true # runs 'bundle install' and caches installed gems automatically - timeout-minutes: 10 - - name: Build - run: bundle exec rake compile - - name: Test - run: bundle exec rake test +name: CI + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +jobs: + build: + name: >- + ${{ matrix.os }} ${{ matrix.ruby }}${{ matrix.yjit }} + env: + TESTOPTS: -v + + runs-on: ${{ matrix.os }} + if: | + !( contains(github.event.pull_request.title, '[ci skip]') + || contains(github.event.pull_request.title, '[skip ci]')) + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + ruby: ['3.2', '3.3', '3.4'] + steps: + - uses: actions/checkout@v4 + - name: Set up Ruby + uses: ruby/setup-ruby-pkgs@v1 + with: + ruby-version: ${{ matrix.ruby }} + apt-get: libxml2-dev + # brew: libxml2 + mingw: libxml2 + bundler-cache: true # runs 'bundle install' and caches installed gems automatically + timeout-minutes: 10 + - name: Build + run: bundle exec rake compile + - name: Test + run: bundle exec rake test diff --git a/ext/libxml/ruby_xml_writer.c b/ext/libxml/ruby_xml_writer.c index 27c7bd8a..625376b0 100644 --- a/ext/libxml/ruby_xml_writer.c +++ b/ext/libxml/ruby_xml_writer.c @@ -283,134 +283,44 @@ static VALUE rxml_writer_result(VALUE self) } /* ===== private helpers ===== */ - -static VALUE numeric_rxml_writer_void(VALUE obj, int (*fn)(xmlTextWriterPtr)) +static void encodeStrings(rb_encoding* encoding, int count, VALUE* strings, const xmlChar** encoded_strings) { - int ret; - rxml_writer_object* rwo; - - rwo = rxml_textwriter_get(obj); - ret = fn(rwo->writer); - - return (-1 == ret ? Qfalse : Qtrue); -} - -#define numeric_rxml_writer_string(/*VALUE*/ obj, /*VALUE*/ name_or_content, /*int (**/fn/*)(xmlTextWriterPtr, const xmlChar *)*/) \ - numeric_rxml_writer_va_strings(obj, Qundef, 1, fn, name_or_content) - -/** - * This is quite ugly but thanks to libxml2 coding style, all xmlTextWriter* - * calls can be redirected to a single function. This can be convenient to: - * - avoid repeating yourself - * - convert strings to UTF-8 - * - validate names - * and so on - **/ -#define XMLWRITER_MAX_STRING_ARGS 5 -static VALUE numeric_rxml_writer_va_strings(VALUE obj, VALUE pe, size_t strings_count, int (*fn)(ANYARGS), ...) -{ - va_list ap; - size_t argc; - int ret = -1; - rxml_writer_object* rwo; - const xmlChar* argv[XMLWRITER_MAX_STRING_ARGS]; - VALUE utf8[XMLWRITER_MAX_STRING_ARGS], orig[XMLWRITER_MAX_STRING_ARGS]; - - if (strings_count > XMLWRITER_MAX_STRING_ARGS) + for (int i = 0; i < count; i++) { - rb_bug("more arguments than expected"); - } - va_start(ap, fn); - rwo = rxml_textwriter_get(obj); - for (argc = 0; argc < strings_count; argc++) - { - VALUE arg; + VALUE string = strings[i]; - arg = va_arg(ap, VALUE); - orig[argc] = arg; - if (NIL_P(arg)) + if (NIL_P(string)) { - utf8[argc] = Qnil; - argv[argc] = NULL; + encoded_strings[i] = NULL; } else { - utf8[argc] = rb_str_conv_enc(orig[argc], rb_enc_get(orig[argc]), rwo->encoding); - argv[argc] = BAD_CAST StringValueCStr(utf8[argc]); + VALUE encoded = rb_str_conv_enc(strings[i], rb_enc_get(string), encoding); + encoded_strings[i] = BAD_CAST StringValueCStr(encoded); } } - va_end(ap); +} - if (Qundef == pe) - { - switch (strings_count) - { - case 0: - ret = fn(rwo->writer); - break; - case 1: - ret = fn(rwo->writer, argv[0]); - break; - case 2: - ret = fn(rwo->writer, argv[0], argv[1]); - break; - case 3: - ret = fn(rwo->writer, argv[0], argv[1], argv[2]); - break; - case 4: - ret = fn(rwo->writer, argv[0], argv[1], argv[2], argv[3]); - break; - case 5: - ret = fn(rwo->writer, argv[0], argv[1], argv[2], argv[3], argv[4]); - break; - default: - break; - } - } - else - { - int xpe; - - xpe = RTEST(pe); - switch (strings_count) - { /* strings_count doesn't include pe */ - case 0: - ret = fn(rwo->writer, xpe); - break; - case 1: - ret = fn(rwo->writer, xpe, argv[0]); - break; - case 2: - ret = fn(rwo->writer, xpe, argv[0], argv[1]); - break; - case 3: - ret = fn(rwo->writer, xpe, argv[0], argv[1], argv[2]); - break; - case 4: - ret = fn(rwo->writer, xpe, argv[0], argv[1], argv[2], argv[3]); - break; - case 5: - ret = fn(rwo->writer, xpe, argv[0], argv[1], argv[2], argv[3], argv[4]); - break; - default: - break; - } - } +static VALUE invoke_void_arg_function(VALUE self, int (*fn)(xmlTextWriterPtr)) +{ + rxml_writer_object* rwo = rxml_textwriter_get(self); + int result = fn(rwo->writer); + return (result == -1 ? Qfalse : Qtrue); +} - while (--strings_count > 0) - { - if (!NIL_P(orig[strings_count])) - { - if (orig[strings_count] != utf8[strings_count]) - { - rb_str_free(utf8[strings_count]); - } - } - } +static VALUE invoke_single_arg_function(VALUE self, int (*fn)(xmlTextWriterPtr, const xmlChar *), VALUE value) +{ + rxml_writer_object* rwo = rxml_textwriter_get(self); - return (-1 == ret ? Qfalse : Qtrue); + VALUE rubyStrings[] = { value }; + const xmlChar* xmlStrings[] = { NULL }; + encodeStrings(rwo->encoding, sizeof(rubyStrings)/sizeof(VALUE), rubyStrings, xmlStrings); + + int result = fn(rwo->writer, xmlStrings[0]); + return (result == -1 ? Qfalse : Qtrue); } + /* ===== public instance methods ===== */ #if LIBXML_VERSION >= 20605 @@ -443,7 +353,7 @@ static VALUE rxml_writer_set_indent(VALUE self, VALUE indentation) */ static VALUE rxml_writer_set_indent_string(VALUE self, VALUE indentation) { - return numeric_rxml_writer_string(self, indentation, xmlTextWriterSetIndentString); + return invoke_single_arg_function(self, xmlTextWriterSetIndentString, indentation); } #endif /* LIBXML_VERSION >= 20605 */ @@ -459,7 +369,7 @@ static VALUE rxml_writer_set_indent_string(VALUE self, VALUE indentation) */ static VALUE rxml_writer_write_comment(VALUE self, VALUE content) { - return numeric_rxml_writer_string(self, content, xmlTextWriterWriteComment); + return invoke_single_arg_function(self, xmlTextWriterWriteComment, content); } /* call-seq: @@ -470,7 +380,7 @@ static VALUE rxml_writer_write_comment(VALUE self, VALUE content) */ static VALUE rxml_writer_write_cdata(VALUE self, VALUE content) { - return numeric_rxml_writer_string(self, content, xmlTextWriterWriteCDATA); + return invoke_single_arg_function(self, xmlTextWriterWriteCDATA, content); } static VALUE rxml_writer_start_element(VALUE, VALUE); @@ -499,7 +409,13 @@ static VALUE rxml_writer_write_element(int argc, VALUE* argv, VALUE self) } else { - return numeric_rxml_writer_va_strings(self, Qundef, 2, xmlTextWriterWriteElement, name, content); + rxml_writer_object* rwo = rxml_textwriter_get(self); + VALUE rubyStrings[] = {name, content}; + const xmlChar* xmlStrings[] = {NULL, NULL}; + encodeStrings(rwo->encoding, sizeof(rubyStrings)/sizeof(VALUE), rubyStrings, xmlStrings); + + int result = xmlTextWriterWriteElement(rwo->writer, xmlStrings[0], xmlStrings[1]); + return (result == -1 ? Qfalse : Qtrue); } } @@ -537,7 +453,12 @@ static VALUE rxml_writer_write_element_ns(int argc, VALUE* argv, VALUE self) } else { - return numeric_rxml_writer_va_strings(self, Qundef, 4, xmlTextWriterWriteElementNS, prefix, name, namespaceURI, content); + rxml_writer_object* rwo = rxml_textwriter_get(self); + VALUE rubyStrings[] = {prefix, name, namespaceURI, content}; + const xmlChar* xmlStrings[] = {NULL, NULL, NULL, NULL}; + encodeStrings(rwo->encoding, sizeof(rubyStrings)/sizeof(VALUE), rubyStrings, xmlStrings); + int result = xmlTextWriterWriteElementNS(rwo->writer, xmlStrings[0], xmlStrings[1], xmlStrings[2], xmlStrings[3]); + return (result == -1 ? Qfalse : Qtrue); } } @@ -549,7 +470,12 @@ static VALUE rxml_writer_write_element_ns(int argc, VALUE* argv, VALUE self) */ static VALUE rxml_writer_write_attribute(VALUE self, VALUE name, VALUE content) { - return numeric_rxml_writer_va_strings(self, Qundef, 2, xmlTextWriterWriteAttribute, name, content); + rxml_writer_object* rwo = rxml_textwriter_get(self); + VALUE rubyStrings[] = {name, content}; + const xmlChar* xmlStrings[] = {NULL, NULL}; + encodeStrings(rwo->encoding, sizeof(rubyStrings)/sizeof(VALUE), rubyStrings, xmlStrings); + int result = xmlTextWriterWriteAttribute(rwo->writer, xmlStrings[0], xmlStrings[1]); + return (result == -1 ? Qfalse : Qtrue); } /* call-seq: @@ -569,10 +495,14 @@ static VALUE rxml_writer_write_attribute(VALUE self, VALUE name, VALUE content) static VALUE rxml_writer_write_attribute_ns(int argc, VALUE* argv, VALUE self) { VALUE prefix, name, namespaceURI, content; - rb_scan_args(argc, argv, "22", &prefix, &name, &namespaceURI, &content); - return numeric_rxml_writer_va_strings(self, Qundef, 4, xmlTextWriterWriteAttributeNS, prefix, name, namespaceURI, content); + rxml_writer_object* rwo = rxml_textwriter_get(self); + VALUE rubyStrings[] = {prefix, name, namespaceURI, content}; + const xmlChar* xmlStrings[] = {NULL, NULL, NULL, NULL}; + encodeStrings(rwo->encoding, sizeof(rubyStrings)/sizeof(VALUE), rubyStrings, xmlStrings); + int result = xmlTextWriterWriteAttributeNS(rwo->writer, xmlStrings[0], xmlStrings[1], xmlStrings[2], xmlStrings[3]); + return (result == -1 ? Qfalse : Qtrue); } /* call-seq: @@ -583,7 +513,12 @@ static VALUE rxml_writer_write_attribute_ns(int argc, VALUE* argv, VALUE self) */ static VALUE rxml_writer_write_pi(VALUE self, VALUE target, VALUE content) { - return numeric_rxml_writer_va_strings(self, Qundef, 2, xmlTextWriterWritePI, target, content); + rxml_writer_object* rwo = rxml_textwriter_get(self); + VALUE rubyStrings[] = {target, content}; + const xmlChar* xmlStrings[] = {NULL, NULL}; + encodeStrings(rwo->encoding, sizeof(rubyStrings)/sizeof(VALUE), rubyStrings, xmlStrings); + int result = xmlTextWriterWritePI(rwo->writer, xmlStrings[0], xmlStrings[1]); + return (result == -1 ? Qfalse : Qtrue); } /* ===== public start/end interface ===== */ @@ -597,7 +532,7 @@ static VALUE rxml_writer_write_pi(VALUE self, VALUE target, VALUE content) */ static VALUE rxml_writer_write_string(VALUE self, VALUE content) { - return numeric_rxml_writer_string(self, content, xmlTextWriterWriteString); + return invoke_single_arg_function(self, xmlTextWriterWriteString, content); } /* call-seq: @@ -609,7 +544,7 @@ static VALUE rxml_writer_write_string(VALUE self, VALUE content) */ static VALUE rxml_writer_write_raw(VALUE self, VALUE content) { - return numeric_rxml_writer_string(self, content, xmlTextWriterWriteRaw); + return invoke_single_arg_function(self, xmlTextWriterWriteRaw, content); } /* call-seq: @@ -619,7 +554,7 @@ static VALUE rxml_writer_write_raw(VALUE self, VALUE content) */ static VALUE rxml_writer_start_attribute(VALUE self, VALUE name) { - return numeric_rxml_writer_string(self, name, xmlTextWriterStartAttribute); + return invoke_single_arg_function(self, xmlTextWriterStartAttribute, name); } /* call-seq: @@ -635,10 +570,14 @@ static VALUE rxml_writer_start_attribute(VALUE self, VALUE name) static VALUE rxml_writer_start_attribute_ns(int argc, VALUE* argv, VALUE self) { VALUE prefix, name, namespaceURI; - rb_scan_args(argc, argv, "21", &prefix, &name, &namespaceURI); - return numeric_rxml_writer_va_strings(self, Qundef, 3, xmlTextWriterStartAttributeNS, prefix, name, namespaceURI); + rxml_writer_object* rwo = rxml_textwriter_get(self); + VALUE rubyStrings[] = {prefix, name, namespaceURI}; + const xmlChar* xmlStrings[] = {NULL, NULL, NULL}; + encodeStrings(rwo->encoding, sizeof(rubyStrings)/sizeof(VALUE), rubyStrings, xmlStrings); + int result = xmlTextWriterStartAttributeNS(rwo->writer, xmlStrings[0], xmlStrings[1], xmlStrings[2]); + return (result == -1 ? Qfalse : Qtrue); } /* call-seq: @@ -648,7 +587,7 @@ static VALUE rxml_writer_start_attribute_ns(int argc, VALUE* argv, VALUE self) */ static VALUE rxml_writer_end_attribute(VALUE self) { - return numeric_rxml_writer_void(self, xmlTextWriterEndAttribute); + return invoke_void_arg_function(self, xmlTextWriterEndAttribute); } #if LIBXML_VERSION >= 20607 @@ -660,7 +599,7 @@ static VALUE rxml_writer_end_attribute(VALUE self) */ static VALUE rxml_writer_start_comment(VALUE self) { - return numeric_rxml_writer_void(self, xmlTextWriterStartComment); + return invoke_void_arg_function(self, xmlTextWriterStartComment); } /* call-seq: @@ -671,7 +610,7 @@ static VALUE rxml_writer_start_comment(VALUE self) */ static VALUE rxml_writer_end_comment(VALUE self) { - return numeric_rxml_writer_void(self, xmlTextWriterEndComment); + return invoke_void_arg_function(self, xmlTextWriterEndComment); } #endif /* LIBXML_VERSION >= 20607 */ @@ -682,7 +621,7 @@ static VALUE rxml_writer_end_comment(VALUE self) */ static VALUE rxml_writer_start_element(VALUE self, VALUE name) { - return numeric_rxml_writer_string(self, name, xmlTextWriterStartElement); + return invoke_single_arg_function(self, xmlTextWriterStartElement, name); } /* call-seq: @@ -698,10 +637,14 @@ static VALUE rxml_writer_start_element(VALUE self, VALUE name) static VALUE rxml_writer_start_element_ns(int argc, VALUE* argv, VALUE self) { VALUE prefix, name, namespaceURI; - rb_scan_args(argc, argv, "21", &prefix, &name, &namespaceURI); - return numeric_rxml_writer_va_strings(self, Qundef, 3, xmlTextWriterStartElementNS, prefix, name, namespaceURI); + rxml_writer_object* rwo = rxml_textwriter_get(self); + VALUE rubyStrings[] = {prefix, name, namespaceURI}; + const xmlChar* xmlStrings[] = {NULL, NULL, NULL}; + encodeStrings(rwo->encoding, sizeof(rubyStrings)/sizeof(VALUE), rubyStrings, xmlStrings); + int result = xmlTextWriterStartElementNS(rwo->writer, xmlStrings[0], xmlStrings[1], xmlStrings[2]); + return (result == -1 ? Qfalse : Qtrue); } /* call-seq: @@ -711,7 +654,7 @@ static VALUE rxml_writer_start_element_ns(int argc, VALUE* argv, VALUE self) */ static VALUE rxml_writer_end_element(VALUE self) { - return numeric_rxml_writer_void(self, xmlTextWriterEndElement); + return invoke_void_arg_function(self, xmlTextWriterEndElement); } /* call-seq: @@ -723,7 +666,7 @@ static VALUE rxml_writer_end_element(VALUE self) */ static VALUE rxml_writer_full_end_element(VALUE self) { - return numeric_rxml_writer_void(self, xmlTextWriterFullEndElement); + return invoke_void_arg_function(self, xmlTextWriterFullEndElement); } /* call-seq: @@ -733,7 +676,7 @@ static VALUE rxml_writer_full_end_element(VALUE self) */ static VALUE rxml_writer_start_cdata(VALUE self) { - return numeric_rxml_writer_void(self, xmlTextWriterStartCDATA); + return invoke_void_arg_function(self, xmlTextWriterStartCDATA); } /* call-seq: @@ -743,7 +686,7 @@ static VALUE rxml_writer_start_cdata(VALUE self) */ static VALUE rxml_writer_end_cdata(VALUE self) { - return numeric_rxml_writer_void(self, xmlTextWriterEndCDATA); + return invoke_void_arg_function(self, xmlTextWriterEndCDATA); } /* call-seq: @@ -801,7 +744,7 @@ static VALUE rxml_writer_start_document(int argc, VALUE* argv, VALUE self) */ static VALUE rxml_writer_end_document(VALUE self) { - return numeric_rxml_writer_void(self, xmlTextWriterEndDocument); + return invoke_void_arg_function(self, xmlTextWriterEndDocument); } /* call-seq: @@ -811,7 +754,7 @@ static VALUE rxml_writer_end_document(VALUE self) */ static VALUE rxml_writer_start_pi(VALUE self, VALUE target) { - return numeric_rxml_writer_string(self, target, xmlTextWriterStartPI); + return invoke_single_arg_function(self, xmlTextWriterStartPI, target); } /* call-seq: @@ -821,7 +764,7 @@ static VALUE rxml_writer_start_pi(VALUE self, VALUE target) */ static VALUE rxml_writer_end_pi(VALUE self) { - return numeric_rxml_writer_void(self, xmlTextWriterEndPI); + return invoke_void_arg_function(self, xmlTextWriterEndPI); } /* call-seq: @@ -832,10 +775,14 @@ static VALUE rxml_writer_end_pi(VALUE self) static VALUE rxml_writer_start_dtd(int argc, VALUE* argv, VALUE self) { VALUE name, pubid, sysid; - rb_scan_args(argc, argv, "12", &name, &pubid, &sysid); - return numeric_rxml_writer_va_strings(self, Qundef, 3, xmlTextWriterStartDTD, name, pubid, sysid); + rxml_writer_object* rwo = rxml_textwriter_get(self); + VALUE rubyStrings[] = {name, pubid, sysid}; + const xmlChar* xmlStrings[] = {NULL, NULL, NULL}; + encodeStrings(rwo->encoding, sizeof(rubyStrings)/sizeof(VALUE), rubyStrings, xmlStrings); + int result = xmlTextWriterStartDTD(rwo->writer, xmlStrings[0], xmlStrings[1], xmlStrings[2]); + return (result == -1 ? Qfalse : Qtrue); } /* call-seq: @@ -845,7 +792,7 @@ static VALUE rxml_writer_start_dtd(int argc, VALUE* argv, VALUE self) */ static VALUE rxml_writer_start_dtd_element(VALUE self, VALUE name) { - return numeric_rxml_writer_string(self, name, xmlTextWriterStartDTDElement); + return invoke_single_arg_function(self, xmlTextWriterStartDTDElement, name); } /* call-seq: @@ -856,14 +803,14 @@ static VALUE rxml_writer_start_dtd_element(VALUE self, VALUE name) static VALUE rxml_writer_start_dtd_entity(int argc, VALUE* argv, VALUE self) { VALUE name, pe; - rb_scan_args(argc, argv, "11", &name, &pe); - if (NIL_P(pe)) - { - pe = Qfalse; - } - return numeric_rxml_writer_va_strings(self, pe, 1, xmlTextWriterStartDTDEntity, name); + rxml_writer_object* rwo = rxml_textwriter_get(self); + VALUE rubyStrings[] = {name}; + const xmlChar* xmlStrings[] = {NULL}; + encodeStrings(rwo->encoding, sizeof(rubyStrings)/sizeof(VALUE), rubyStrings, xmlStrings); + int result = xmlTextWriterStartDTDEntity(rwo->writer, RB_TEST(pe), xmlStrings[0]); + return (result == -1 ? Qfalse : Qtrue); } /* call-seq: @@ -873,7 +820,7 @@ static VALUE rxml_writer_start_dtd_entity(int argc, VALUE* argv, VALUE self) */ static VALUE rxml_writer_start_dtd_attlist(VALUE self, VALUE name) { - return numeric_rxml_writer_string(self, name, xmlTextWriterStartDTDAttlist); + return invoke_single_arg_function(self, xmlTextWriterStartDTDAttlist, name); } /* call-seq: @@ -883,7 +830,7 @@ static VALUE rxml_writer_start_dtd_attlist(VALUE self, VALUE name) */ static VALUE rxml_writer_end_dtd(VALUE self) { - return numeric_rxml_writer_void(self, xmlTextWriterEndDTD); + return invoke_void_arg_function(self, xmlTextWriterEndDTD); } /* call-seq: @@ -893,7 +840,7 @@ static VALUE rxml_writer_end_dtd(VALUE self) */ static VALUE rxml_writer_end_dtd_entity(VALUE self) { - return numeric_rxml_writer_void(self, xmlTextWriterEndDTDEntity); + return invoke_void_arg_function(self, xmlTextWriterEndDTDEntity); } /* call-seq: @@ -903,7 +850,7 @@ static VALUE rxml_writer_end_dtd_entity(VALUE self) */ static VALUE rxml_writer_end_dtd_attlist(VALUE self) { - return numeric_rxml_writer_void(self, xmlTextWriterEndDTDAttlist); + return invoke_void_arg_function(self, xmlTextWriterEndDTDAttlist); } /* call-seq: @@ -913,7 +860,7 @@ static VALUE rxml_writer_end_dtd_attlist(VALUE self) */ static VALUE rxml_writer_end_dtd_element(VALUE self) { - return numeric_rxml_writer_void(self, xmlTextWriterEndDTDElement); + return invoke_void_arg_function(self, xmlTextWriterEndDTDElement); } /* call-seq: @@ -938,10 +885,14 @@ static VALUE rxml_writer_end_dtd_element(VALUE self) static VALUE rxml_writer_write_dtd(int argc, VALUE* argv, VALUE self) { VALUE name, pubid, sysid, subset; - rb_scan_args(argc, argv, "13", &name, &pubid, &sysid, &subset); - return numeric_rxml_writer_va_strings(self, Qundef, 4, xmlTextWriterWriteDTD, name, pubid, sysid, subset); + rxml_writer_object* rwo = rxml_textwriter_get(self); + VALUE rubyStrings[] = {name, pubid, sysid, subset}; + const xmlChar* xmlStrings[] = {NULL, NULL, NULL, NULL}; + encodeStrings(rwo->encoding, sizeof(rubyStrings)/sizeof(VALUE), rubyStrings, xmlStrings); + int result = xmlTextWriterWriteDTD(rwo->writer, xmlStrings[0], xmlStrings[1], xmlStrings[2], xmlStrings[3]); + return (result == -1 ? Qfalse : Qtrue); } /* call-seq: @@ -953,7 +904,12 @@ static VALUE rxml_writer_write_dtd(int argc, VALUE* argv, VALUE self) */ static VALUE rxml_writer_write_dtd_attlist(VALUE self, VALUE name, VALUE content) { - return numeric_rxml_writer_va_strings(self, Qundef, 2, xmlTextWriterWriteDTDAttlist, name, content); + rxml_writer_object* rwo = rxml_textwriter_get(self); + VALUE rubyStrings[] = {name, content}; + const xmlChar* xmlStrings[] = {NULL, NULL}; + encodeStrings(rwo->encoding, sizeof(rubyStrings)/sizeof(VALUE), rubyStrings, xmlStrings); + int result = xmlTextWriterWriteDTDAttlist(rwo->writer, xmlStrings[0], xmlStrings[1]); + return (result == -1 ? Qfalse : Qtrue); } /* call-seq: @@ -965,7 +921,12 @@ static VALUE rxml_writer_write_dtd_attlist(VALUE self, VALUE name, VALUE content */ static VALUE rxml_writer_write_dtd_element(VALUE self, VALUE name, VALUE content) { - return numeric_rxml_writer_va_strings(self, Qundef, 2, xmlTextWriterWriteDTDElement, name, content); + rxml_writer_object* rwo = rxml_textwriter_get(self); + VALUE rubyStrings[] = {name, content}; + const xmlChar* xmlStrings[] = {NULL, NULL}; + encodeStrings(rwo->encoding, sizeof(rubyStrings)/sizeof(VALUE), rubyStrings, xmlStrings); + int result = xmlTextWriterWriteDTDElement(rwo->writer, xmlStrings[0], xmlStrings[1]); + return (result == -1 ? Qfalse : Qtrue); } /* call-seq: @@ -975,7 +936,12 @@ static VALUE rxml_writer_write_dtd_element(VALUE self, VALUE name, VALUE content */ static VALUE rxml_writer_write_dtd_entity(VALUE self, VALUE name, VALUE pubid, VALUE sysid, VALUE ndataid, VALUE content, VALUE pe) { - return numeric_rxml_writer_va_strings(self, pe, 5, xmlTextWriterWriteDTDEntity, name, pubid, sysid, ndataid, content); + rxml_writer_object* rwo = rxml_textwriter_get(self); + VALUE rubyStrings[] = {name, pubid, sysid, ndataid, content}; + const xmlChar* xmlStrings[] = {NULL, NULL, NULL, NULL, NULL}; + encodeStrings(rwo->encoding, sizeof(rubyStrings)/sizeof(VALUE), rubyStrings, xmlStrings); + int result = xmlTextWriterWriteDTDEntity(rwo->writer, RB_TEST(pe), xmlStrings[0], xmlStrings[1], xmlStrings[2], xmlStrings[3], xmlStrings[4]); + return (result == -1 ? Qfalse : Qtrue); } /* call-seq: @@ -992,7 +958,12 @@ static VALUE rxml_writer_write_dtd_entity(VALUE self, VALUE name, VALUE pubid, V */ static VALUE rxml_writer_write_dtd_external_entity(VALUE self, VALUE name, VALUE pubid, VALUE sysid, VALUE ndataid, VALUE pe) { - return numeric_rxml_writer_va_strings(self, pe, 4, xmlTextWriterWriteDTDExternalEntity, name, pubid, sysid, ndataid); + rxml_writer_object* rwo = rxml_textwriter_get(self); + VALUE rubyStrings[] = {name, pubid, sysid, ndataid}; + const xmlChar* xmlStrings[] = {NULL, NULL, NULL, NULL}; + encodeStrings(rwo->encoding, sizeof(rubyStrings)/sizeof(VALUE), rubyStrings, xmlStrings); + int result = xmlTextWriterWriteDTDExternalEntity(rwo->writer, RB_TEST(pe), xmlStrings[0], xmlStrings[1], xmlStrings[2], xmlStrings[3]); + return (result == -1 ? Qfalse : Qtrue); } /* call-seq: @@ -1002,7 +973,12 @@ static VALUE rxml_writer_write_dtd_external_entity(VALUE self, VALUE name, VALUE */ static VALUE rxml_writer_write_dtd_external_entity_contents(VALUE self, VALUE pubid, VALUE sysid, VALUE ndataid) { - return numeric_rxml_writer_va_strings(self, Qundef, 3, xmlTextWriterWriteDTDExternalEntityContents, pubid, sysid, ndataid); + rxml_writer_object* rwo = rxml_textwriter_get(self); + VALUE rubyStrings[] = {pubid, sysid, ndataid,}; + const xmlChar* xmlStrings[] = {NULL, NULL, NULL}; + encodeStrings(rwo->encoding, sizeof(rubyStrings)/sizeof(VALUE), rubyStrings, xmlStrings); + int result = xmlTextWriterWriteDTDExternalEntityContents(rwo->writer, xmlStrings[0], xmlStrings[1], xmlStrings[2]); + return (result == -1 ? Qfalse : Qtrue); } /* call-seq: @@ -1018,7 +994,12 @@ static VALUE rxml_writer_write_dtd_external_entity_contents(VALUE self, VALUE pu */ static VALUE rxml_writer_write_dtd_internal_entity(VALUE self, VALUE name, VALUE content, VALUE pe) { - return numeric_rxml_writer_va_strings(self, pe, 2, xmlTextWriterWriteDTDInternalEntity, name, content); + rxml_writer_object* rwo = rxml_textwriter_get(self); + VALUE rubyStrings[] = {name, content}; + const xmlChar* xmlStrings[] = {NULL, NULL}; + encodeStrings(rwo->encoding, sizeof(rubyStrings)/sizeof(VALUE), rubyStrings, xmlStrings); + int result = xmlTextWriterWriteDTDInternalEntity(rwo->writer, RB_TEST(pe), xmlStrings[0], xmlStrings[1]); + return (result == -1 ? Qfalse : Qtrue); } /* call-seq: @@ -1028,7 +1009,12 @@ static VALUE rxml_writer_write_dtd_internal_entity(VALUE self, VALUE name, VALUE */ static VALUE rxml_writer_write_dtd_notation(VALUE self, VALUE name, VALUE pubid, VALUE sysid) { - return numeric_rxml_writer_va_strings(self, Qundef, 3, xmlTextWriterWriteDTDNotation, name, pubid, sysid); + rxml_writer_object* rwo = rxml_textwriter_get(self); + VALUE rubyStrings[] = {name, pubid, sysid}; + const xmlChar* xmlStrings[] = {NULL, NULL, NULL}; + encodeStrings(rwo->encoding, sizeof(rubyStrings)/sizeof(VALUE), rubyStrings, xmlStrings); + int result = xmlTextWriterWriteDTDNotation(rwo->writer, xmlStrings[0], xmlStrings[1], xmlStrings[2]); + return (result == -1 ? Qfalse : Qtrue); } #if LIBXML_VERSION >= 20900 diff --git a/ext/libxml/ruby_xml_xpath.c b/ext/libxml/ruby_xml_xpath.c index 26cee2d0..b55892ef 100644 --- a/ext/libxml/ruby_xml_xpath.c +++ b/ext/libxml/ruby_xml_xpath.c @@ -1,195 +1,195 @@ -/* - * Document-class: LibXML::XML::XPath - * - * The XML::XPath module is used to query XML documents. It is - * usually accessed via the XML::Document#find or - * XML::Node#find methods. For example: - * - * document.find('/foo', namespaces) -> XML::XPath::Object - * - * The optional namespaces parameter can be a string, array or - * hash table. - * - * document.find('/foo', 'xlink:http://www.w3.org/1999/xlink') - * document.find('/foo', ['xlink:http://www.w3.org/1999/xlink', - * 'xi:http://www.w3.org/2001/XInclude') - * document.find('/foo', 'xlink' => 'http://www.w3.org/1999/xlink', - * 'xi' => 'http://www.w3.org/2001/XInclude') - * - * - * === Working With Default Namespaces - * - * Finding namespaced elements and attributes can be tricky. - * Lets work through an example of a document with a default - * namespace: - * - * - * - * Phil Bogle's Contacts - * - * - * To find nodes you must define the atom namespace for - * libxml. One way to do this is: - * - * node = doc.find('atom:title', 'atom:http://www.w3.org/2005/Atom') - * - * Alternatively, you can register the default namespace like this: - * - * doc.root.namespaces.default_prefix = 'atom' - * node = doc.find('atom:title') - * - * === More Complex Namespace Examples - * - * Lets work through some more complex examples using the - * following xml document: - * - * - * - * - * - * - * - * - * - * - * - * # Since the soap namespace is defined on the root - * # node we can directly use it. - * doc.find('/soap:Envelope') - * - * # Since the ns1 namespace is not defined on the root node - * # we have to first register it with the xpath engine. - * doc.find('//ns1:IdAndName', - * 'ns1:http://domain.somewhere.com') - * - * # Since the getManufacturerNamesResponse element uses a default - * # namespace we first have to give it a prefix and register - * # it with the xpath engine. - * doc.find('//ns:getManufacturerNamesResponse', - * 'ns:http://services.somewhere.com') - * - * # Here is an example showing a complex namespace aware - * # xpath expression. - * doc.find('/soap:Envelope/soap:Body/ns0:getManufacturerNamesResponse/ns0:IDAndNameList/ns1:IdAndName', - * ['ns0:http://services.somewhere.com', 'ns1:http://domain.somewhere.com']) -*/ - -#include "ruby_libxml.h" -#include - -VALUE mXPath; - -VALUE rxml_xpath_to_value(xmlXPathContextPtr xctxt, xmlXPathObjectPtr xobject) -{ - VALUE result; - int type; - - if (xobject == NULL) - { - /* xmlLastError is different than xctxt->lastError. Use - xmlLastError since it has the message set while xctxt->lastError - does not. */ - const xmlError *xerror = xmlGetLastError(); - rxml_raise(xerror); - } - - switch (type = xobject->type) - { - case XPATH_NODESET: - result = rxml_xpath_object_wrap(xctxt->doc, xobject); - break; - case XPATH_BOOLEAN: - result = (xobject->boolval != 0) ? Qtrue : Qfalse; - xmlXPathFreeObject(xobject); - break; - case XPATH_NUMBER: - result = rb_float_new(xobject->floatval); - xmlXPathFreeObject(xobject); - break; - case XPATH_STRING: - result = rxml_new_cstr(xobject->stringval, xctxt->doc->encoding); - xmlXPathFreeObject(xobject); - break; - default: - xmlXPathFreeObject(xobject); - rb_raise(rb_eTypeError, - "can't convert XPath object of type %d to Ruby value", type - ); - } - - return result; -} - -xmlXPathObjectPtr rxml_xpath_from_value(VALUE value) -{ - xmlXPathObjectPtr result = NULL; - - switch (TYPE(value)) - { - case T_TRUE: - case T_FALSE: - result = xmlXPathNewBoolean(RTEST(value)); - break; - case T_FIXNUM: - case T_FLOAT: - result = xmlXPathNewFloat(NUM2DBL(value)); - break; - case T_STRING: - result = xmlXPathWrapString(xmlStrdup((const xmlChar *)StringValuePtr(value))); - break; - case T_NIL: - result = xmlXPathNewNodeSet(NULL); - break; - case T_ARRAY: - { - long i, j; - result = xmlXPathNewNodeSet(NULL); - - for (i = RARRAY_LEN(value); i > 0; i--) - { - xmlXPathObjectPtr obj = rxml_xpath_from_value(rb_ary_shift(value)); - - if ((obj->nodesetval != NULL) && (obj->nodesetval->nodeNr != 0)) - { - for (j = 0; j < obj->nodesetval->nodeNr; j++) - { - xmlXPathNodeSetAdd(result->nodesetval, obj->nodesetval->nodeTab[j]); - } - } - } - break; - } - default: - rb_raise(rb_eTypeError, - "can't convert object of type %s to XPath object", rb_obj_classname(value) - ); - } - - return result; -} - -void rxml_init_xpath(void) -{ - mXPath = rb_define_module_under(mXML, "XPath"); - - /* 0: Undefined value. */ - rb_define_const(mXPath, "UNDEFINED", INT2NUM(XPATH_UNDEFINED)); - /* 1: A nodeset, will be wrapped by XPath Object. */ - rb_define_const(mXPath, "NODESET", INT2NUM(XPATH_NODESET)); - /* 2: A boolean value. */ - rb_define_const(mXPath, "BOOLEAN", INT2NUM(XPATH_BOOLEAN)); - /* 3: A numeric value. */ - rb_define_const(mXPath, "NUMBER", INT2NUM(XPATH_NUMBER)); - /* 4: A string value. */ - rb_define_const(mXPath, "STRING", INT2NUM(XPATH_STRING)); - /* 5: An xpointer point */ - rb_define_const(mXPath, "POINT", INT2NUM(XPATH_POINT)); - /* 6: An xpointer range */ - rb_define_const(mXPath, "RANGE", INT2NUM(XPATH_RANGE)); - /* 7: An xpointer location set */ - rb_define_const(mXPath, "LOCATIONSET", INT2NUM(XPATH_LOCATIONSET)); - /* 8: XPath user type */ - rb_define_const(mXPath, "USERS", INT2NUM(XPATH_USERS)); - /* 9: An XSLT value tree, non modifiable */ - rb_define_const(mXPath, "XSLT_TREE", INT2NUM(XPATH_XSLT_TREE)); -} +/* + * Document-class: LibXML::XML::XPath + * + * The XML::XPath module is used to query XML documents. It is + * usually accessed via the XML::Document#find or + * XML::Node#find methods. For example: + * + * document.find('/foo', namespaces) -> XML::XPath::Object + * + * The optional namespaces parameter can be a string, array or + * hash table. + * + * document.find('/foo', 'xlink:http://www.w3.org/1999/xlink') + * document.find('/foo', ['xlink:http://www.w3.org/1999/xlink', + * 'xi:http://www.w3.org/2001/XInclude') + * document.find('/foo', 'xlink' => 'http://www.w3.org/1999/xlink', + * 'xi' => 'http://www.w3.org/2001/XInclude') + * + * + * === Working With Default Namespaces + * + * Finding namespaced elements and attributes can be tricky. + * Lets work through an example of a document with a default + * namespace: + * + * + * + * Phil Bogle's Contacts + * + * + * To find nodes you must define the atom namespace for + * libxml. One way to do this is: + * + * node = doc.find('atom:title', 'atom:http://www.w3.org/2005/Atom') + * + * Alternatively, you can register the default namespace like this: + * + * doc.root.namespaces.default_prefix = 'atom' + * node = doc.find('atom:title') + * + * === More Complex Namespace Examples + * + * Lets work through some more complex examples using the + * following xml document: + * + * + * + * + * + * + * + * + * + * + * + * # Since the soap namespace is defined on the root + * # node we can directly use it. + * doc.find('/soap:Envelope') + * + * # Since the ns1 namespace is not defined on the root node + * # we have to first register it with the xpath engine. + * doc.find('//ns1:IdAndName', + * 'ns1:http://domain.somewhere.com') + * + * # Since the getManufacturerNamesResponse element uses a default + * # namespace we first have to give it a prefix and register + * # it with the xpath engine. + * doc.find('//ns:getManufacturerNamesResponse', + * 'ns:http://services.somewhere.com') + * + * # Here is an example showing a complex namespace aware + * # xpath expression. + * doc.find('/soap:Envelope/soap:Body/ns0:getManufacturerNamesResponse/ns0:IDAndNameList/ns1:IdAndName', + * ['ns0:http://services.somewhere.com', 'ns1:http://domain.somewhere.com']) +*/ + +#include "ruby_libxml.h" +#include + +VALUE mXPath; + +VALUE rxml_xpath_to_value(xmlXPathContextPtr xctxt, xmlXPathObjectPtr xobject) +{ + VALUE result; + int type; + + if (xobject == NULL) + { + /* xmlLastError is different than xctxt->lastError. Use + xmlLastError since it has the message set while xctxt->lastError + does not. */ + const xmlError *xerror = xmlGetLastError(); + rxml_raise(xerror); + } + + switch (type = xobject->type) + { + case XPATH_NODESET: + result = rxml_xpath_object_wrap(xctxt->doc, xobject); + break; + case XPATH_BOOLEAN: + result = (xobject->boolval != 0) ? Qtrue : Qfalse; + xmlXPathFreeObject(xobject); + break; + case XPATH_NUMBER: + result = rb_float_new(xobject->floatval); + xmlXPathFreeObject(xobject); + break; + case XPATH_STRING: + result = rxml_new_cstr(xobject->stringval, xctxt->doc->encoding); + xmlXPathFreeObject(xobject); + break; + default: + xmlXPathFreeObject(xobject); + rb_raise(rb_eTypeError, + "can't convert XPath object of type %d to Ruby value", type + ); + } + + return result; +} + +xmlXPathObjectPtr rxml_xpath_from_value(VALUE value) +{ + xmlXPathObjectPtr result = NULL; + + switch (TYPE(value)) + { + case T_TRUE: + case T_FALSE: + result = xmlXPathNewBoolean(RTEST(value)); + break; + case T_FIXNUM: + case T_FLOAT: + result = xmlXPathNewFloat(NUM2DBL(value)); + break; + case T_STRING: + result = xmlXPathWrapString(xmlStrdup((const xmlChar *)StringValuePtr(value))); + break; + case T_NIL: + result = xmlXPathNewNodeSet(NULL); + break; + case T_ARRAY: + { + long i, j; + result = xmlXPathNewNodeSet(NULL); + + for (i = RARRAY_LEN(value); i > 0; i--) + { + xmlXPathObjectPtr obj = rxml_xpath_from_value(rb_ary_shift(value)); + + if ((obj->nodesetval != NULL) && (obj->nodesetval->nodeNr != 0)) + { + for (j = 0; j < obj->nodesetval->nodeNr; j++) + { + xmlXPathNodeSetAdd(result->nodesetval, obj->nodesetval->nodeTab[j]); + } + } + } + break; + } + default: + rb_raise(rb_eTypeError, + "can't convert object of type %s to XPath object", rb_obj_classname(value) + ); + } + + return result; +} + +void rxml_init_xpath(void) +{ + mXPath = rb_define_module_under(mXML, "XPath"); + + /* 0: Undefined value. */ + rb_define_const(mXPath, "UNDEFINED", INT2NUM(XPATH_UNDEFINED)); + /* 1: A nodeset, will be wrapped by XPath Object. */ + rb_define_const(mXPath, "NODESET", INT2NUM(XPATH_NODESET)); + /* 2: A boolean value. */ + rb_define_const(mXPath, "BOOLEAN", INT2NUM(XPATH_BOOLEAN)); + /* 3: A numeric value. */ + rb_define_const(mXPath, "NUMBER", INT2NUM(XPATH_NUMBER)); + /* 4: A string value. */ + rb_define_const(mXPath, "STRING", INT2NUM(XPATH_STRING)); + /* 5: An xpointer point */ + rb_define_const(mXPath, "POINT", INT2NUM(XPATH_POINT)); + /* 6: An xpointer range */ + rb_define_const(mXPath, "RANGE", INT2NUM(XPATH_RANGE)); + /* 7: An xpointer location set */ + rb_define_const(mXPath, "LOCATIONSET", INT2NUM(XPATH_LOCATIONSET)); + /* 8: XPath user type */ + rb_define_const(mXPath, "USERS", INT2NUM(XPATH_USERS)); + /* 9: An XSLT value tree, non modifiable */ + rb_define_const(mXPath, "XSLT_TREE", INT2NUM(XPATH_XSLT_TREE)); +} diff --git a/ext/libxml/ruby_xml_xpath_context.c b/ext/libxml/ruby_xml_xpath_context.c index a9cad48b..d3fc1f2e 100644 --- a/ext/libxml/ruby_xml_xpath_context.c +++ b/ext/libxml/ruby_xml_xpath_context.c @@ -1,362 +1,362 @@ -/* Please see the LICENSE file for copyright and distribution information */ - -#include "ruby_libxml.h" -#include "ruby_xml_xpath_context.h" -#include "ruby_xml_xpath_expression.h" - -#if RUBY_ST_H -#include -#else -#include -#endif - -#include - -/* - * Document-class: LibXML::XML::XPath::Context - * - * The XML::XPath::Context class is used to evaluate XPath - * expressions. Generally, you should not directly use this class, - * but instead use the XML::Document#find and XML::Node#find methods. - * - * doc = XML::Document.string('
content
') - * context = XPath::Context.new(doc) - * context.node = doc.root - * context.register_namespaces_from_node(doc.root) - * nodes = context.find('/header') - */ - -VALUE cXMLXPathContext; - -static void rxml_xpath_context_free(xmlXPathContextPtr ctxt) -{ - xmlXPathFreeContext(ctxt); -} - -static void rxml_xpath_context_mark(xmlXPathContextPtr ctxt) -{ - VALUE value = (VALUE)ctxt->doc->_private; - rb_gc_mark(value); -} - -static VALUE rxml_xpath_context_alloc(VALUE klass) -{ - return Data_Wrap_Struct(cXMLXPathContext, rxml_xpath_context_mark, rxml_xpath_context_free, NULL); -} - -/* call-seq: - * XPath::Context.new(doc) -> XPath::Context - * - * Creates a new XPath context for the specified document. The - * context can then be used to evaluate an XPath expression. - * - * doc = XML::Document.string('
hi
') - * context = XPath::Context.new(doc) - * nodes = XPath::Object.new('//first', context) - * nodes.length == 1 - */ -static VALUE rxml_xpath_context_initialize(VALUE self, VALUE document) -{ - xmlDocPtr xdoc; - - if (rb_obj_is_kind_of(document, cXMLDocument) != Qtrue) - { - rb_raise(rb_eTypeError, "Supplied argument must be a document or node."); - } - - Data_Get_Struct(document, xmlDoc, xdoc); - DATA_PTR(self) = xmlXPathNewContext(xdoc); - - return self; -} - -/* - * call-seq: - * context.doc -> document - * - * Obtain the XML::Document this node belongs to. - */ -static VALUE rxml_xpath_context_doc(VALUE self) -{ - xmlDocPtr xdoc = NULL; - xmlXPathContextPtr ctxt; - Data_Get_Struct(self, xmlXPathContext, ctxt); - - xdoc = ctxt->doc; - return rxml_document_wrap(xdoc); -} - -/* - * call-seq: - * context.register_namespace(prefix, uri) -> (true|false) - * - * Register the specified namespace URI with the specified prefix - * in this context. - - * context.register_namespace('xi', 'http://www.w3.org/2001/XInclude') - */ -static VALUE rxml_xpath_context_register_namespace(VALUE self, VALUE prefix, VALUE uri) -{ - xmlXPathContextPtr ctxt; - Data_Get_Struct(self, xmlXPathContext, ctxt); - - /* Prefix could be a symbol. */ - prefix = rb_obj_as_string(prefix); - - if (xmlXPathRegisterNs(ctxt, (xmlChar*) StringValuePtr(prefix), - (xmlChar*) StringValuePtr(uri)) == 0) - { - return (Qtrue); - } - else - { - /* Should raise an exception, IMHO (whose?, why shouldnt it? -danj)*/ - rb_warning("register namespace failed"); - return (Qfalse); - } -} - -/* call-seq: - * context.register_namespaces_from_node(node) -> self - * - * Helper method to read in namespaces defined on a node. - * - * doc = XML::Document.string('
hi
') - * context = XPath::Context.new(doc) - * context.register_namespaces_from_node(doc.root) - */ -static VALUE rxml_xpath_context_register_namespaces_from_node(VALUE self, - VALUE node) -{ - xmlXPathContextPtr xctxt; - xmlNodePtr xnode; - xmlNsPtr *xnsArr; - - Data_Get_Struct(self, xmlXPathContext, xctxt); - - if (rb_obj_is_kind_of(node, cXMLDocument) == Qtrue) - { - xmlDocPtr xdoc; - Data_Get_Struct(node, xmlDoc, xdoc); - xnode = xmlDocGetRootElement(xdoc); - } - else if (rb_obj_is_kind_of(node, cXMLNode) == Qtrue) - { - Data_Get_Struct(node, xmlNode, xnode); - } - else - { - rb_raise(rb_eTypeError, "The first argument must be a document or node."); - } - - xnsArr = xmlGetNsList(xnode->doc, xnode); - - if (xnsArr) - { - xmlNsPtr xns = *xnsArr; - - while (xns) - { - /* If there is no prefix, then this is the default namespace. - Skip it for now. */ - if (xns->prefix) - { - VALUE prefix = rxml_new_cstr(xns->prefix, xctxt->doc->encoding); - VALUE uri = rxml_new_cstr(xns->href, xctxt->doc->encoding); - rxml_xpath_context_register_namespace(self, prefix, uri); - } - xns = xns->next; - } - xmlFree(xnsArr); - } - - return self; -} - -static int iterate_ns_hash(VALUE prefix, VALUE uri, VALUE self) -{ - rxml_xpath_context_register_namespace(self, prefix, uri); - return ST_CONTINUE; -} - -/* - * call-seq: - * context.register_namespaces(["prefix:uri"]) -> self - * - * Register the specified namespaces in this context. There are - * three different forms that libxml accepts. These include - * a string, an array of strings, or a hash table: - * - * context.register_namespaces('xi:http://www.w3.org/2001/XInclude') - * context.register_namespaces(['xlink:http://www.w3.org/1999/xlink', - * 'xi:http://www.w3.org/2001/XInclude') - * context.register_namespaces('xlink' => 'http://www.w3.org/1999/xlink', - * 'xi' => 'http://www.w3.org/2001/XInclude') - */ -static VALUE rxml_xpath_context_register_namespaces(VALUE self, VALUE nslist) -{ - char *cp; - long i; - VALUE rprefix, ruri; - xmlXPathContextPtr xctxt; - - Data_Get_Struct(self, xmlXPathContext, xctxt); - - /* Need to loop through the 2nd argument and iterate through the - * list of namespaces that we want to allow */ - switch (TYPE(nslist)) - { - case T_STRING: - cp = strchr(StringValuePtr(nslist), (int) ':'); - if (cp == NULL) - { - rprefix = nslist; - ruri = Qnil; - } - else - { - rprefix = rb_str_new(StringValuePtr(nslist), (long) ((intptr_t) cp - (intptr_t)StringValuePtr(nslist))); - ruri = rxml_new_cstr((const xmlChar*)&cp[1], xctxt->doc->encoding); - } - /* Should test the results of this */ - rxml_xpath_context_register_namespace(self, rprefix, ruri); - break; - case T_ARRAY: - for (i = 0; i < RARRAY_LEN(nslist); i++) - { - rxml_xpath_context_register_namespaces(self, RARRAY_PTR(nslist)[i]); - } - break; - case T_HASH: - rb_hash_foreach(nslist, iterate_ns_hash, self); - break; - default: - rb_raise( - rb_eArgError, - "Invalid argument type, only accept string, array of strings, or an array of arrays"); - } - return self; -} - -/* - * call-seq: - * context.node = node - * - * Set the current node used by the XPath engine - - * doc = XML::Document.string('
hi
') - * context.node = doc.root.first - */ -static VALUE rxml_xpath_context_node_set(VALUE self, VALUE node) -{ - xmlXPathContextPtr xctxt; - xmlNodePtr xnode; - - Data_Get_Struct(self, xmlXPathContext, xctxt); - Data_Get_Struct(node, xmlNode, xnode); - xctxt->node = xnode; - return node; -} - -/* - * call-seq: - * context.find("xpath") -> true|false|number|string|XML::XPath::Object - * - * Executes the provided xpath function. The result depends on the execution - * of the xpath statement. It may be true, false, a number, a string or - * a node set. - */ -static VALUE rxml_xpath_context_find(VALUE self, VALUE xpath_expr) -{ - xmlXPathContextPtr xctxt; - xmlXPathObjectPtr xobject; - xmlXPathCompExprPtr xcompexpr; - - Data_Get_Struct(self, xmlXPathContext, xctxt); - - if (TYPE(xpath_expr) == T_STRING) - { - VALUE expression = rb_check_string_type(xpath_expr); - xobject = xmlXPathEval((xmlChar*) StringValueCStr(expression), xctxt); - } - else if (rb_obj_is_kind_of(xpath_expr, cXMLXPathExpression)) - { - Data_Get_Struct(xpath_expr, xmlXPathCompExpr, xcompexpr); - xobject = xmlXPathCompiledEval(xcompexpr, xctxt); - } - else - { - rb_raise(rb_eTypeError, - "Argument should be an instance of a String or XPath::Expression"); - } - - return rxml_xpath_to_value(xctxt, xobject); -} - -#if LIBXML_VERSION >= 20626 -/* - * call-seq: - * context.enable_cache(size = nil) - * - * Enables an XPath::Context's built-in cache. If the cache is - * enabled then XPath objects will be cached internally for reuse. - * The size parameter controls sets the maximum number of XPath objects - * that will be cached per XPath object type (node-set, string, number, - * boolean, and misc objects). Set size to nil to use the default - * cache size of 100. - */ -static VALUE -rxml_xpath_context_enable_cache(int argc, VALUE *argv, VALUE self) -{ - xmlXPathContextPtr xctxt; - VALUE size; - int value = -1; - - Data_Get_Struct(self, xmlXPathContext, xctxt); - - if (rb_scan_args(argc, argv, "01", &size) == 1) - { - value = NUM2INT(size); - } - - if (xmlXPathContextSetCache(xctxt, 1, value, 0) == -1) - rxml_raise(xmlGetLastError()); - - return self; -} - -/* - * call-seq: - * context.disable_cache - * - * Disables an XPath::Context's built-in cache. - */ -static VALUE -rxml_xpath_context_disable_cache(VALUE self) -{ - xmlXPathContextPtr xctxt; - Data_Get_Struct(self, xmlXPathContext, xctxt); - - if (xmlXPathContextSetCache(xctxt, 0, 0, 0) == -1) - rxml_raise(xmlGetLastError()); - - return self; -} -#endif - -void rxml_init_xpath_context(void) -{ - cXMLXPathContext = rb_define_class_under(mXPath, "Context", rb_cObject); - rb_define_alloc_func(cXMLXPathContext, rxml_xpath_context_alloc); - rb_define_method(cXMLXPathContext, "doc", rxml_xpath_context_doc, 0); - rb_define_method(cXMLXPathContext, "initialize", rxml_xpath_context_initialize, 1); - rb_define_method(cXMLXPathContext, "register_namespaces", rxml_xpath_context_register_namespaces, 1); - rb_define_method(cXMLXPathContext, "register_namespaces_from_node", rxml_xpath_context_register_namespaces_from_node, 1); - rb_define_method(cXMLXPathContext, "register_namespace", rxml_xpath_context_register_namespace, 2); - rb_define_method(cXMLXPathContext, "node=", rxml_xpath_context_node_set, 1); - rb_define_method(cXMLXPathContext, "find", rxml_xpath_context_find, 1); -#if LIBXML_VERSION >= 20626 - rb_define_method(cXMLXPathContext, "enable_cache", rxml_xpath_context_enable_cache, -1); - rb_define_method(cXMLXPathContext, "disable_cache", rxml_xpath_context_disable_cache, 0); -#endif -} +/* Please see the LICENSE file for copyright and distribution information */ + +#include "ruby_libxml.h" +#include "ruby_xml_xpath_context.h" +#include "ruby_xml_xpath_expression.h" + +#if RUBY_ST_H +#include +#else +#include +#endif + +#include + +/* + * Document-class: LibXML::XML::XPath::Context + * + * The XML::XPath::Context class is used to evaluate XPath + * expressions. Generally, you should not directly use this class, + * but instead use the XML::Document#find and XML::Node#find methods. + * + * doc = XML::Document.string('
content
') + * context = XPath::Context.new(doc) + * context.node = doc.root + * context.register_namespaces_from_node(doc.root) + * nodes = context.find('/header') + */ + +VALUE cXMLXPathContext; + +static void rxml_xpath_context_free(xmlXPathContextPtr ctxt) +{ + xmlXPathFreeContext(ctxt); +} + +static void rxml_xpath_context_mark(xmlXPathContextPtr ctxt) +{ + VALUE value = (VALUE)ctxt->doc->_private; + rb_gc_mark(value); +} + +static VALUE rxml_xpath_context_alloc(VALUE klass) +{ + return Data_Wrap_Struct(cXMLXPathContext, rxml_xpath_context_mark, rxml_xpath_context_free, NULL); +} + +/* call-seq: + * XPath::Context.new(doc) -> XPath::Context + * + * Creates a new XPath context for the specified document. The + * context can then be used to evaluate an XPath expression. + * + * doc = XML::Document.string('
hi
') + * context = XPath::Context.new(doc) + * nodes = XPath::Object.new('//first', context) + * nodes.length == 1 + */ +static VALUE rxml_xpath_context_initialize(VALUE self, VALUE document) +{ + xmlDocPtr xdoc; + + if (rb_obj_is_kind_of(document, cXMLDocument) != Qtrue) + { + rb_raise(rb_eTypeError, "Supplied argument must be a document or node."); + } + + Data_Get_Struct(document, xmlDoc, xdoc); + DATA_PTR(self) = xmlXPathNewContext(xdoc); + + return self; +} + +/* + * call-seq: + * context.doc -> document + * + * Obtain the XML::Document this node belongs to. + */ +static VALUE rxml_xpath_context_doc(VALUE self) +{ + xmlDocPtr xdoc = NULL; + xmlXPathContextPtr ctxt; + Data_Get_Struct(self, xmlXPathContext, ctxt); + + xdoc = ctxt->doc; + return rxml_document_wrap(xdoc); +} + +/* + * call-seq: + * context.register_namespace(prefix, uri) -> (true|false) + * + * Register the specified namespace URI with the specified prefix + * in this context. + + * context.register_namespace('xi', 'http://www.w3.org/2001/XInclude') + */ +static VALUE rxml_xpath_context_register_namespace(VALUE self, VALUE prefix, VALUE uri) +{ + xmlXPathContextPtr ctxt; + Data_Get_Struct(self, xmlXPathContext, ctxt); + + /* Prefix could be a symbol. */ + prefix = rb_obj_as_string(prefix); + + if (xmlXPathRegisterNs(ctxt, (xmlChar*) StringValuePtr(prefix), + (xmlChar*) StringValuePtr(uri)) == 0) + { + return (Qtrue); + } + else + { + /* Should raise an exception, IMHO (whose?, why shouldnt it? -danj)*/ + rb_warning("register namespace failed"); + return (Qfalse); + } +} + +/* call-seq: + * context.register_namespaces_from_node(node) -> self + * + * Helper method to read in namespaces defined on a node. + * + * doc = XML::Document.string('
hi
') + * context = XPath::Context.new(doc) + * context.register_namespaces_from_node(doc.root) + */ +static VALUE rxml_xpath_context_register_namespaces_from_node(VALUE self, + VALUE node) +{ + xmlXPathContextPtr xctxt; + xmlNodePtr xnode; + xmlNsPtr *xnsArr; + + Data_Get_Struct(self, xmlXPathContext, xctxt); + + if (rb_obj_is_kind_of(node, cXMLDocument) == Qtrue) + { + xmlDocPtr xdoc; + Data_Get_Struct(node, xmlDoc, xdoc); + xnode = xmlDocGetRootElement(xdoc); + } + else if (rb_obj_is_kind_of(node, cXMLNode) == Qtrue) + { + Data_Get_Struct(node, xmlNode, xnode); + } + else + { + rb_raise(rb_eTypeError, "The first argument must be a document or node."); + } + + xnsArr = xmlGetNsList(xnode->doc, xnode); + + if (xnsArr) + { + xmlNsPtr xns = *xnsArr; + + while (xns) + { + /* If there is no prefix, then this is the default namespace. + Skip it for now. */ + if (xns->prefix) + { + VALUE prefix = rxml_new_cstr(xns->prefix, xctxt->doc->encoding); + VALUE uri = rxml_new_cstr(xns->href, xctxt->doc->encoding); + rxml_xpath_context_register_namespace(self, prefix, uri); + } + xns = xns->next; + } + xmlFree(xnsArr); + } + + return self; +} + +static int iterate_ns_hash(VALUE prefix, VALUE uri, VALUE self) +{ + rxml_xpath_context_register_namespace(self, prefix, uri); + return ST_CONTINUE; +} + +/* + * call-seq: + * context.register_namespaces(["prefix:uri"]) -> self + * + * Register the specified namespaces in this context. There are + * three different forms that libxml accepts. These include + * a string, an array of strings, or a hash table: + * + * context.register_namespaces('xi:http://www.w3.org/2001/XInclude') + * context.register_namespaces(['xlink:http://www.w3.org/1999/xlink', + * 'xi:http://www.w3.org/2001/XInclude') + * context.register_namespaces('xlink' => 'http://www.w3.org/1999/xlink', + * 'xi' => 'http://www.w3.org/2001/XInclude') + */ +static VALUE rxml_xpath_context_register_namespaces(VALUE self, VALUE nslist) +{ + char *cp; + long i; + VALUE rprefix, ruri; + xmlXPathContextPtr xctxt; + + Data_Get_Struct(self, xmlXPathContext, xctxt); + + /* Need to loop through the 2nd argument and iterate through the + * list of namespaces that we want to allow */ + switch (TYPE(nslist)) + { + case T_STRING: + cp = strchr(StringValuePtr(nslist), (int) ':'); + if (cp == NULL) + { + rprefix = nslist; + ruri = Qnil; + } + else + { + rprefix = rb_str_new(StringValuePtr(nslist), (long) ((intptr_t) cp - (intptr_t)StringValuePtr(nslist))); + ruri = rxml_new_cstr((const xmlChar*)&cp[1], xctxt->doc->encoding); + } + /* Should test the results of this */ + rxml_xpath_context_register_namespace(self, rprefix, ruri); + break; + case T_ARRAY: + for (i = 0; i < RARRAY_LEN(nslist); i++) + { + rxml_xpath_context_register_namespaces(self, RARRAY_PTR(nslist)[i]); + } + break; + case T_HASH: + rb_hash_foreach(nslist, iterate_ns_hash, self); + break; + default: + rb_raise( + rb_eArgError, + "Invalid argument type, only accept string, array of strings, or an array of arrays"); + } + return self; +} + +/* + * call-seq: + * context.node = node + * + * Set the current node used by the XPath engine + + * doc = XML::Document.string('
hi
') + * context.node = doc.root.first + */ +static VALUE rxml_xpath_context_node_set(VALUE self, VALUE node) +{ + xmlXPathContextPtr xctxt; + xmlNodePtr xnode; + + Data_Get_Struct(self, xmlXPathContext, xctxt); + Data_Get_Struct(node, xmlNode, xnode); + xctxt->node = xnode; + return node; +} + +/* + * call-seq: + * context.find("xpath") -> true|false|number|string|XML::XPath::Object + * + * Executes the provided xpath function. The result depends on the execution + * of the xpath statement. It may be true, false, a number, a string or + * a node set. + */ +static VALUE rxml_xpath_context_find(VALUE self, VALUE xpath_expr) +{ + xmlXPathContextPtr xctxt; + xmlXPathObjectPtr xobject; + xmlXPathCompExprPtr xcompexpr; + + Data_Get_Struct(self, xmlXPathContext, xctxt); + + if (TYPE(xpath_expr) == T_STRING) + { + VALUE expression = rb_check_string_type(xpath_expr); + xobject = xmlXPathEval((xmlChar*) StringValueCStr(expression), xctxt); + } + else if (rb_obj_is_kind_of(xpath_expr, cXMLXPathExpression)) + { + Data_Get_Struct(xpath_expr, xmlXPathCompExpr, xcompexpr); + xobject = xmlXPathCompiledEval(xcompexpr, xctxt); + } + else + { + rb_raise(rb_eTypeError, + "Argument should be an instance of a String or XPath::Expression"); + } + + return rxml_xpath_to_value(xctxt, xobject); +} + +#if LIBXML_VERSION >= 20626 +/* + * call-seq: + * context.enable_cache(size = nil) + * + * Enables an XPath::Context's built-in cache. If the cache is + * enabled then XPath objects will be cached internally for reuse. + * The size parameter controls sets the maximum number of XPath objects + * that will be cached per XPath object type (node-set, string, number, + * boolean, and misc objects). Set size to nil to use the default + * cache size of 100. + */ +static VALUE +rxml_xpath_context_enable_cache(int argc, VALUE *argv, VALUE self) +{ + xmlXPathContextPtr xctxt; + VALUE size; + int value = -1; + + Data_Get_Struct(self, xmlXPathContext, xctxt); + + if (rb_scan_args(argc, argv, "01", &size) == 1) + { + value = NUM2INT(size); + } + + if (xmlXPathContextSetCache(xctxt, 1, value, 0) == -1) + rxml_raise(xmlGetLastError()); + + return self; +} + +/* + * call-seq: + * context.disable_cache + * + * Disables an XPath::Context's built-in cache. + */ +static VALUE +rxml_xpath_context_disable_cache(VALUE self) +{ + xmlXPathContextPtr xctxt; + Data_Get_Struct(self, xmlXPathContext, xctxt); + + if (xmlXPathContextSetCache(xctxt, 0, 0, 0) == -1) + rxml_raise(xmlGetLastError()); + + return self; +} +#endif + +void rxml_init_xpath_context(void) +{ + cXMLXPathContext = rb_define_class_under(mXPath, "Context", rb_cObject); + rb_define_alloc_func(cXMLXPathContext, rxml_xpath_context_alloc); + rb_define_method(cXMLXPathContext, "doc", rxml_xpath_context_doc, 0); + rb_define_method(cXMLXPathContext, "initialize", rxml_xpath_context_initialize, 1); + rb_define_method(cXMLXPathContext, "register_namespaces", rxml_xpath_context_register_namespaces, 1); + rb_define_method(cXMLXPathContext, "register_namespaces_from_node", rxml_xpath_context_register_namespaces_from_node, 1); + rb_define_method(cXMLXPathContext, "register_namespace", rxml_xpath_context_register_namespace, 2); + rb_define_method(cXMLXPathContext, "node=", rxml_xpath_context_node_set, 1); + rb_define_method(cXMLXPathContext, "find", rxml_xpath_context_find, 1); +#if LIBXML_VERSION >= 20626 + rb_define_method(cXMLXPathContext, "enable_cache", rxml_xpath_context_enable_cache, -1); + rb_define_method(cXMLXPathContext, "disable_cache", rxml_xpath_context_disable_cache, 0); +#endif +} diff --git a/libxml-ruby.gemspec b/libxml-ruby.gemspec index 1982d33a..92356df2 100644 --- a/libxml-ruby.gemspec +++ b/libxml-ruby.gemspec @@ -42,6 +42,7 @@ Gem::Specification.new do |spec| spec.test_files = Dir.glob('test/test_*.rb') spec.required_ruby_version = '>= 2.5' spec.date = DateTime.now + spec.add_development_dependency('logger') spec.add_development_dependency('rake-compiler') spec.add_development_dependency('minitest') spec.license = 'MIT' diff --git a/test/test_sax_parser.rb b/test/test_sax_parser.rb index 5be90ce9..264a0c7a 100644 --- a/test/test_sax_parser.rb +++ b/test/test_sax_parser.rb @@ -1,326 +1,345 @@ -# encoding: UTF-8 - -require_relative './test_helper' -require 'stringio' - -class DocTypeCallback - include LibXML::XML::SaxParser::Callbacks - def on_start_element(element, attributes) - end -end - -class TestCaseCallbacks - include LibXML::XML::SaxParser::Callbacks - - attr_accessor :result - - def initialize - @result = Array.new - end - - def on_cdata_block(cdata) - @result << "cdata: #{cdata}" - end - - def on_characters(chars) - @result << "characters: #{chars}" - end - - def on_comment(text) - @result << "comment: #{text}" - end - - def on_end_document - @result << "end_document" - end - - def on_end_element(name) - @result << "end_element: #{name}" - end - - def on_end_element_ns(name, prefix, uri) - @result << "end_element_ns #{name}, prefix: #{prefix}, uri: #{uri}" - end - - # Called for parser errors. - def on_error(error) - @result << "error: #{error}" - end - - def on_processing_instruction(target, data) - @result << "pi: #{target} #{data}" - end - - def on_start_document - @result << "startdoc" - end - - def on_start_element(name, attributes) - attributes ||= Hash.new - @result << "start_element: #{name}, attr: #{attributes.inspect}" - end - - def on_start_element_ns(name, attributes, prefix, uri, namespaces) - attributes ||= Hash.new - namespaces ||= Hash.new - @result << "start_element_ns: #{name}, attr: #{attributes.inspect}, prefix: #{prefix}, uri: #{uri}, ns: #{namespaces.inspect}" - end -end - -class TestSaxParser < Minitest::Test - def saxtest_file - File.join(File.dirname(__FILE__), 'model/atom.xml') - end - - def verify(parser) - result = parser.callbacks.result - - i = -1 - assert_equal("startdoc", result[i+=1]) - assert_equal("pi: xml-stylesheet type=\"text/xsl\" href=\"my_stylesheet.xsl\"", result[i+=1]) - assert_equal("start_element: feed, attr: {}", result[i+=1]) - assert_equal("start_element_ns: feed, attr: {}, prefix: , uri: http://www.w3.org/2005/Atom, ns: {nil=>\"http://www.w3.org/2005/Atom\"}", result[i+=1]) - assert_equal("characters: \n ", result[i+=1]) - assert_equal("comment: Not a valid atom entry ", result[i+=1]) - assert_equal("characters: \n ", result[i+=1]) - assert_equal("start_element: entry, attr: {}", result[i+=1]) - assert_equal("start_element_ns: entry, attr: {}, prefix: , uri: http://www.w3.org/2005/Atom, ns: {}", result[i+=1]) - assert_equal("characters: \n ", result[i+=1]) - assert_equal("start_element: title, attr: {\"type\"=>\"html\"}", result[i+=1]) - assert_equal("start_element_ns: title, attr: {\"type\"=>\"html\"}, prefix: , uri: http://www.w3.org/2005/Atom, ns: {}", result[i+=1]) - assert_equal("cdata: <>", result[i+=1]) - assert_equal("end_element: title", result[i+=1]) - assert_equal("end_element_ns title, prefix: , uri: http://www.w3.org/2005/Atom", result[i+=1]) - assert_equal("characters: \n ", result[i+=1]) - assert_equal("start_element: content, attr: {\"type\"=>\"xhtml\"}", result[i+=1]) - assert_equal("start_element_ns: content, attr: {\"type\"=>\"xhtml\"}, prefix: , uri: http://www.w3.org/2005/Atom, ns: {}", result[i+=1]) - assert_equal("characters: \n ", result[i+=1]) - assert_equal("start_element: xhtml:div, attr: {}", result[i+=1]) - assert_equal("start_element_ns: div, attr: {}, prefix: xhtml, uri: http://www.w3.org/1999/xhtml, ns: {\"xhtml\"=>\"http://www.w3.org/1999/xhtml\"}", result[i+=1]) - assert_equal("characters: \n ", result[i+=1]) - assert_equal("start_element: xhtml:p, attr: {}", result[i+=1]) - assert_equal("start_element_ns: p, attr: {}, prefix: xhtml, uri: http://www.w3.org/1999/xhtml, ns: {}", result[i+=1]) - assert_equal("characters: hi there", result[i+=1]) - assert_equal("end_element: xhtml:p", result[i+=1]) - assert_equal("end_element_ns p, prefix: xhtml, uri: http://www.w3.org/1999/xhtml", result[i+=1]) - assert_equal("characters: \n ", result[i+=1]) - assert_equal("end_element: xhtml:div", result[i+=1]) - assert_equal("end_element_ns div, prefix: xhtml, uri: http://www.w3.org/1999/xhtml", result[i+=1]) - assert_equal("characters: \n ", result[i+=1]) - assert_equal("end_element: content", result[i+=1]) - assert_equal("end_element_ns content, prefix: , uri: http://www.w3.org/2005/Atom", result[i+=1]) - assert_equal("characters: \n ", result[i+=1]) - assert_equal("end_element: entry", result[i+=1]) - assert_equal("end_element_ns entry, prefix: , uri: http://www.w3.org/2005/Atom", result[i+=1]) - assert_equal("characters: \n", result[i+=1]) - assert_equal("end_element: feed", result[i+=1]) - assert_equal("end_element_ns feed, prefix: , uri: http://www.w3.org/2005/Atom", result[i+=1]) - assert_equal("end_document", result[i+=1]) - end - - def test_file - parser = LibXML::XML::SaxParser.file(saxtest_file) - parser.callbacks = TestCaseCallbacks.new - parser.parse - verify(parser) - end - - def test_file_no_callbacks - parser = LibXML::XML::SaxParser.file(saxtest_file) - assert_equal true, parser.parse - end - - def test_noexistent_file - error = assert_raises(LibXML::XML::Error) do - LibXML::XML::SaxParser.file('i_dont_exist.xml') - end - - assert_equal('Warning: failed to load external entity "i_dont_exist.xml".', error.to_s) - end - - def test_nil_file - error = assert_raises(TypeError) do - LibXML::XML::SaxParser.file(nil) - end - - assert_match(/nil into String/, error.to_s) - end - - def test_io - File.open(saxtest_file) do |file| - parser = LibXML::XML::SaxParser.io(file) - parser.callbacks = TestCaseCallbacks.new - parser.parse - verify(parser) - end - end - - def test_nil_io - error = assert_raises(TypeError) do - LibXML::XML::HTMLParser.io(nil) - end - - assert_equal("Must pass in an IO object", error.to_s) - end - - def test_string_no_callbacks - xml = File.read(saxtest_file) - parser = LibXML::XML::SaxParser.string(xml) - assert_equal true, parser.parse - end - - def test_string - xml = File.read(saxtest_file) - parser = LibXML::XML::SaxParser.string(xml) - parser.callbacks = TestCaseCallbacks.new - parser.parse - verify(parser) - end - - def test_string_io - xml = File.read(saxtest_file) - io = StringIO.new(xml) - parser = LibXML::XML::SaxParser.io(io) - - parser.callbacks = TestCaseCallbacks.new - parser.parse - verify(parser) - end - - def test_nil_string - error = assert_raises(TypeError) do - LibXML::XML::SaxParser.string(nil) - end - - assert_equal("wrong argument type nil (expected String)", error.to_s) - end - - def test_doctype - xml = <<-EOS - - - -a1 - -EOS - parser = LibXML::XML::SaxParser.string(xml) - parser.callbacks = DocTypeCallback.new - doc = parser.parse - refute_nil(doc) - end - - def test_parse_warning - # Two xml PIs is a warning - xml = <<-EOS - - - -EOS - - parser = LibXML::XML::SaxParser.string(xml) - parser.callbacks = TestCaseCallbacks.new - - parser.parse - - # Check callbacks - result = parser.callbacks.result - i = -1 - assert_equal("startdoc", result[i+=1]) - assert_equal("error: Warning: xmlParsePITarget: invalid name prefix 'xml' at :2.", result[i+=1]) - assert_equal("pi: xml-invalid ", result[i+=1]) - assert_equal("start_element: Test, attr: {}", result[i+=1]) - assert_equal("start_element_ns: Test, attr: {}, prefix: , uri: , ns: {}", result[i+=1]) - assert_equal("end_element: Test", result[i+=1]) - assert_equal("end_element_ns Test, prefix: , uri: ", result[i+=1]) - assert_equal("end_document", result[i+=1]) - end - - def test_parse_error - xml = <<-EOS - - EOS - parser = LibXML::XML::SaxParser.string(xml) - parser.callbacks = TestCaseCallbacks.new - - error = assert_raises(LibXML::XML::Error) do - parser.parse - end - - # Check callbacks - result = parser.callbacks.result - - i = -1 - - base_err_msg = "Fatal error: (Premature end of data in tag Results line 1|EndTag: '<\\/' not found) at :2\\." - re_err_msg1 = /\A(error: )#{base_err_msg}\z/ - re_err_msg2 = /\A#{base_err_msg}\z/ - - assert_equal("startdoc", result[i+=1]) - assert_equal("start_element: Results, attr: {}", result[i+=1]) - assert_equal("start_element_ns: Results, attr: {}, prefix: , uri: , ns: {}", result[i+=1]) - assert_equal("characters: \n", result[i+=1]) - assert_match(re_err_msg1, result[i+=1]) - assert_equal("end_document", result[i+=1]) - - refute_nil(error) - assert_kind_of(LibXML::XML::Error, error) - assert_match(re_err_msg2, error.message) - assert_equal(LibXML::XML::Error::PARSER, error.domain) - - assert([LibXML::XML::Error::TAG_NOT_FINISHED, LibXML::XML::Error::LTSLASH_REQUIRED].include?(error.code)) - assert_equal(LibXML::XML::Error::FATAL, error.level) - assert_nil(error.file) - assert_equal(2, error.line) - # Sometimes this is nil and sometimes its not depending on OS and libxlm version - # assert_nil(error.str1) - assert_nil(error.str2) - assert_nil(error.str3) - assert([0, 1].include?(error.int1)) - assert_equal(1, error.int2) - assert_nil(error.node) - end - - def test_parse_seg_fail - xml = <<-EOS - - - - - AQUALIA THERMAL Lichte cr├иme - Versterkende & kalmerende 24 u hydraterende verzorging
- Huid wordt continu gehydrateerd, intens versterkt en gekalmeerd.
- Hypoallergeen. Geschikt voor de gevoelige huid.
-
- 01.EFFECTIVITEIT
- Intensief gehydrateerd, de huid voelt gekalmeerd. Ze voelt de hele dag soepel en fluweelzacht aan, zonder een trekkerig gevoel. De huid is elastischer, soepeler en stralender. Doeltreffendheid getest onder dermatologisch toezicht.
-
- 02.GEBRUIK
- 's Morgens en/ of 's avonds aanbrengen.
-
- 03.ACTIEVE INGREDIENTEN
- Technologische innovatie: 24 u continue cellulaire vochtnevel. Voor de 1ste keer worden Thermaal Bronwater van Vichy, rijk aan zeldzame mineralen en Actief HyaluronineтДв verwerkt in microcapsules, die deze vervolgens verspreiden in de cellen.
-
- 04.TEXTUUR
- De lichte cr├иme is verfrissend en trekt makkelijk in. Niet vet en niet kleverig. Zonder 'maskereffect'.
-
- 05.GEUR
- Geparfumeerd
-
- 06.INHOUD
- 40 ml tube
-
-
-
- EOS - - parser = LibXML::XML::SaxParser.string(xml) - parser.callbacks = TestCaseCallbacks.new - - error = assert_raises(LibXML::XML::Error) do - parser.parse - end - assert_match("Fatal error: xmlParseEntityRef: no name at", error.to_s) - - # Check callbacks - parser.callbacks.result - end -end +# encoding: UTF-8 + +require_relative './test_helper' +require 'stringio' + +class DocTypeCallback + include LibXML::XML::SaxParser::Callbacks + def on_start_element(element, attributes) + end +end + +class TestCaseCallbacks + include LibXML::XML::SaxParser::Callbacks + + attr_accessor :result + + def initialize + @result = Array.new + end + + def on_cdata_block(cdata) + @result << "cdata: #{cdata}" + end + + def on_characters(chars) + @result << "characters: #{chars}" + end + + def on_comment(text) + @result << "comment: #{text}" + end + + def on_end_document + @result << "end_document" + end + + def on_end_element(name) + @result << "end_element: #{name}" + end + + def on_end_element_ns(name, prefix, uri) + @result << "end_element_ns #{name}, prefix: #{prefix}, uri: #{uri}" + end + + # Called for parser errors. + def on_error(error) + @result << "error: #{error}" + end + + def on_processing_instruction(target, data) + @result << "pi: #{target} #{data}" + end + + def on_start_document + @result << "startdoc" + end + + def on_start_element(name, attributes) + attributes ||= Hash.new + @result << "start_element: #{name}, attr: #{attributes.inspect}" + end + + def on_start_element_ns(name, attributes, prefix, uri, namespaces) + attributes ||= Hash.new + namespaces ||= Hash.new + @result << "start_element_ns: #{name}, attr: #{attributes.inspect}, prefix: #{prefix}, uri: #{uri}, ns: #{namespaces.inspect}" + end +end + +class TestSaxParser < Minitest::Test + def saxtest_file + File.join(File.dirname(__FILE__), 'model/atom.xml') + end + + def verify(parser) + result = parser.callbacks.result + + i = -1 + assert_equal("startdoc", result[i+=1]) + assert_equal("pi: xml-stylesheet type=\"text/xsl\" href=\"my_stylesheet.xsl\"", result[i+=1]) + assert_equal("start_element: feed, attr: {}", result[i+=1]) + if Gem::Version.new(RUBY_VERSION) < Gem::Version.new('3.4') + assert_equal("start_element_ns: feed, attr: {}, prefix: , uri: http://www.w3.org/2005/Atom, ns: {nil=>\"http://www.w3.org/2005/Atom\"}", result[i+=1]) + else + assert_equal("start_element_ns: feed, attr: {}, prefix: , uri: http://www.w3.org/2005/Atom, ns: {nil => \"http://www.w3.org/2005/Atom\"}", result[i+=1]) + end + assert_equal("characters: \n ", result[i+=1]) + assert_equal("comment: Not a valid atom entry ", result[i+=1]) + assert_equal("characters: \n ", result[i+=1]) + assert_equal("start_element: entry, attr: {}", result[i+=1]) + assert_equal("start_element_ns: entry, attr: {}, prefix: , uri: http://www.w3.org/2005/Atom, ns: {}", result[i+=1]) + assert_equal("characters: \n ", result[i+=1]) + if Gem::Version.new(RUBY_VERSION) < Gem::Version.new('3.4') + assert_equal("start_element: title, attr: {\"type\"=>\"html\"}", result[i+=1]) + assert_equal("start_element_ns: title, attr: {\"type\"=>\"html\"}, prefix: , uri: http://www.w3.org/2005/Atom, ns: {}", result[i+=1]) + else + assert_equal("start_element: title, attr: {\"type\" => \"html\"}", result[i+=1]) + assert_equal("start_element_ns: title, attr: {\"type\" => \"html\"}, prefix: , uri: http://www.w3.org/2005/Atom, ns: {}", result[i+=1]) + end + + assert_equal("cdata: <>", result[i+=1]) + assert_equal("end_element: title", result[i+=1]) + assert_equal("end_element_ns title, prefix: , uri: http://www.w3.org/2005/Atom", result[i+=1]) + assert_equal("characters: \n ", result[i+=1]) + if Gem::Version.new(RUBY_VERSION) < Gem::Version.new('3.4') + assert_equal("start_element: content, attr: {\"type\"=>\"xhtml\"}", result[i+=1]) + assert_equal("start_element_ns: content, attr: {\"type\"=>\"xhtml\"}, prefix: , uri: http://www.w3.org/2005/Atom, ns: {}", result[i+=1]) + else + assert_equal("start_element: content, attr: {\"type\" => \"xhtml\"}", result[i+=1]) + assert_equal("start_element_ns: content, attr: {\"type\" => \"xhtml\"}, prefix: , uri: http://www.w3.org/2005/Atom, ns: {}", result[i+=1]) + end + assert_equal("characters: \n ", result[i+=1]) + assert_equal("start_element: xhtml:div, attr: {}", result[i+=1]) + if Gem::Version.new(RUBY_VERSION) < Gem::Version.new('3.4') + assert_equal("start_element_ns: div, attr: {}, prefix: xhtml, uri: http://www.w3.org/1999/xhtml, ns: {\"xhtml\"=>\"http://www.w3.org/1999/xhtml\"}", result[i+=1]) + else + assert_equal("start_element_ns: div, attr: {}, prefix: xhtml, uri: http://www.w3.org/1999/xhtml, ns: {\"xhtml\" => \"http://www.w3.org/1999/xhtml\"}", result[i+=1]) + end + assert_equal("characters: \n ", result[i+=1]) + assert_equal("start_element: xhtml:p, attr: {}", result[i+=1]) + assert_equal("start_element_ns: p, attr: {}, prefix: xhtml, uri: http://www.w3.org/1999/xhtml, ns: {}", result[i+=1]) + assert_equal("characters: hi there", result[i+=1]) + assert_equal("end_element: xhtml:p", result[i+=1]) + assert_equal("end_element_ns p, prefix: xhtml, uri: http://www.w3.org/1999/xhtml", result[i+=1]) + assert_equal("characters: \n ", result[i+=1]) + assert_equal("end_element: xhtml:div", result[i+=1]) + assert_equal("end_element_ns div, prefix: xhtml, uri: http://www.w3.org/1999/xhtml", result[i+=1]) + assert_equal("characters: \n ", result[i+=1]) + assert_equal("end_element: content", result[i+=1]) + assert_equal("end_element_ns content, prefix: , uri: http://www.w3.org/2005/Atom", result[i+=1]) + assert_equal("characters: \n ", result[i+=1]) + assert_equal("end_element: entry", result[i+=1]) + assert_equal("end_element_ns entry, prefix: , uri: http://www.w3.org/2005/Atom", result[i+=1]) + assert_equal("characters: \n", result[i+=1]) + assert_equal("end_element: feed", result[i+=1]) + assert_equal("end_element_ns feed, prefix: , uri: http://www.w3.org/2005/Atom", result[i+=1]) + assert_equal("end_document", result[i+=1]) + end + + def test_file + parser = LibXML::XML::SaxParser.file(saxtest_file) + parser.callbacks = TestCaseCallbacks.new + parser.parse + verify(parser) + end + + def test_file_no_callbacks + parser = LibXML::XML::SaxParser.file(saxtest_file) + assert_equal true, parser.parse + end + + def test_noexistent_file + error = assert_raises(LibXML::XML::Error) do + LibXML::XML::SaxParser.file('i_dont_exist.xml') + end + + assert_equal('Warning: failed to load external entity "i_dont_exist.xml".', error.to_s) + end + + def test_nil_file + error = assert_raises(TypeError) do + LibXML::XML::SaxParser.file(nil) + end + + assert_match(/nil into String/, error.to_s) + end + + def test_io + File.open(saxtest_file) do |file| + parser = LibXML::XML::SaxParser.io(file) + parser.callbacks = TestCaseCallbacks.new + parser.parse + verify(parser) + end + end + + def test_nil_io + error = assert_raises(TypeError) do + LibXML::XML::HTMLParser.io(nil) + end + + assert_equal("Must pass in an IO object", error.to_s) + end + + def test_string_no_callbacks + xml = File.read(saxtest_file) + parser = LibXML::XML::SaxParser.string(xml) + assert_equal true, parser.parse + end + + def test_string + xml = File.read(saxtest_file) + parser = LibXML::XML::SaxParser.string(xml) + parser.callbacks = TestCaseCallbacks.new + parser.parse + verify(parser) + end + + def test_string_io + xml = File.read(saxtest_file) + io = StringIO.new(xml) + parser = LibXML::XML::SaxParser.io(io) + + parser.callbacks = TestCaseCallbacks.new + parser.parse + verify(parser) + end + + def test_nil_string + error = assert_raises(TypeError) do + LibXML::XML::SaxParser.string(nil) + end + + assert_equal("wrong argument type nil (expected String)", error.to_s) + end + + def test_doctype + xml = <<-EOS + + + +a1 + +EOS + parser = LibXML::XML::SaxParser.string(xml) + parser.callbacks = DocTypeCallback.new + doc = parser.parse + refute_nil(doc) + end + + def test_parse_warning + # Two xml PIs is a warning + xml = <<-EOS + + + +EOS + + parser = LibXML::XML::SaxParser.string(xml) + parser.callbacks = TestCaseCallbacks.new + + parser.parse + + # Check callbacks + result = parser.callbacks.result + i = -1 + assert_equal("startdoc", result[i+=1]) + assert_equal("error: Warning: xmlParsePITarget: invalid name prefix 'xml' at :2.", result[i+=1]) + assert_equal("pi: xml-invalid ", result[i+=1]) + assert_equal("start_element: Test, attr: {}", result[i+=1]) + assert_equal("start_element_ns: Test, attr: {}, prefix: , uri: , ns: {}", result[i+=1]) + assert_equal("end_element: Test", result[i+=1]) + assert_equal("end_element_ns Test, prefix: , uri: ", result[i+=1]) + assert_equal("end_document", result[i+=1]) + end + + def test_parse_error + xml = <<-EOS + + EOS + parser = LibXML::XML::SaxParser.string(xml) + parser.callbacks = TestCaseCallbacks.new + + error = assert_raises(LibXML::XML::Error) do + parser.parse + end + + # Check callbacks + result = parser.callbacks.result + + i = -1 + + base_err_msg = "Fatal error: (Premature end of data in tag Results line 1|EndTag: '<\\/' not found) at :2\\." + re_err_msg1 = /\A(error: )#{base_err_msg}\z/ + re_err_msg2 = /\A#{base_err_msg}\z/ + + assert_equal("startdoc", result[i+=1]) + assert_equal("start_element: Results, attr: {}", result[i+=1]) + assert_equal("start_element_ns: Results, attr: {}, prefix: , uri: , ns: {}", result[i+=1]) + assert_equal("characters: \n", result[i+=1]) + assert_match(re_err_msg1, result[i+=1]) + assert_equal("end_document", result[i+=1]) + + refute_nil(error) + assert_kind_of(LibXML::XML::Error, error) + assert_match(re_err_msg2, error.message) + assert_equal(LibXML::XML::Error::PARSER, error.domain) + + assert([LibXML::XML::Error::TAG_NOT_FINISHED, LibXML::XML::Error::LTSLASH_REQUIRED].include?(error.code)) + assert_equal(LibXML::XML::Error::FATAL, error.level) + assert_nil(error.file) + assert_equal(2, error.line) + # Sometimes this is nil and sometimes its not depending on OS and libxlm version + # assert_nil(error.str1) + assert_nil(error.str2) + assert_nil(error.str3) + assert([0, 1].include?(error.int1)) + assert_equal(1, error.int2) + assert_nil(error.node) + end + + def test_parse_seg_fail + xml = <<-EOS + + + + + AQUALIA THERMAL Lichte cr├иme - Versterkende & kalmerende 24 u hydraterende verzorging
+ Huid wordt continu gehydrateerd, intens versterkt en gekalmeerd.
+ Hypoallergeen. Geschikt voor de gevoelige huid.
+
+ 01.EFFECTIVITEIT
+ Intensief gehydrateerd, de huid voelt gekalmeerd. Ze voelt de hele dag soepel en fluweelzacht aan, zonder een trekkerig gevoel. De huid is elastischer, soepeler en stralender. Doeltreffendheid getest onder dermatologisch toezicht.
+
+ 02.GEBRUIK
+ 's Morgens en/ of 's avonds aanbrengen.
+
+ 03.ACTIEVE INGREDIENTEN
+ Technologische innovatie: 24 u continue cellulaire vochtnevel. Voor de 1ste keer worden Thermaal Bronwater van Vichy, rijk aan zeldzame mineralen en Actief HyaluronineтДв verwerkt in microcapsules, die deze vervolgens verspreiden in de cellen.
+
+ 04.TEXTUUR
+ De lichte cr├иme is verfrissend en trekt makkelijk in. Niet vet en niet kleverig. Zonder 'maskereffect'.
+
+ 05.GEUR
+ Geparfumeerd
+
+ 06.INHOUD
+ 40 ml tube
+
+
+
+ EOS + + parser = LibXML::XML::SaxParser.string(xml) + parser.callbacks = TestCaseCallbacks.new + + error = assert_raises(LibXML::XML::Error) do + parser.parse + end + assert_match("Fatal error: xmlParseEntityRef: no name at", error.to_s) + + # Check callbacks + parser.callbacks.result + end +end