nokogiri 1.11.0.rc3-java → 1.11.0.rc4-java

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of nokogiri might be problematic. Click here for more details.

Files changed (83) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE-DEPENDENCIES.md +1015 -947
  3. data/README.md +1 -1
  4. data/ext/java/nokogiri/HtmlSaxParserContext.java +1 -1
  5. data/ext/java/nokogiri/HtmlSaxPushParser.java +1 -1
  6. data/ext/java/nokogiri/XmlNode.java +4 -6
  7. data/ext/java/nokogiri/XmlReader.java +2 -2
  8. data/ext/java/nokogiri/XmlRelaxng.java +10 -3
  9. data/ext/java/nokogiri/XmlSaxParserContext.java +8 -3
  10. data/ext/java/nokogiri/XmlSchema.java +84 -24
  11. data/ext/java/nokogiri/XmlSyntaxError.java +2 -6
  12. data/ext/java/nokogiri/XmlXpathContext.java +48 -18
  13. data/ext/java/nokogiri/internals/HtmlDomParserContext.java +51 -39
  14. data/ext/java/nokogiri/internals/NokogiriHandler.java +1 -1
  15. data/ext/java/nokogiri/internals/NokogiriNamespaceContext.java +9 -2
  16. data/ext/java/nokogiri/internals/NokogiriXPathFunction.java +62 -6
  17. data/ext/java/nokogiri/internals/NokogiriXPathFunctionResolver.java +4 -2
  18. data/ext/java/nokogiri/internals/XmlDomParserContext.java +2 -2
  19. data/ext/nokogiri/depend +476 -357
  20. data/ext/nokogiri/extconf.rb +441 -321
  21. data/ext/nokogiri/html_document.c +79 -78
  22. data/ext/nokogiri/html_sax_parser_context.c +2 -2
  23. data/ext/nokogiri/nokogiri.c +34 -46
  24. data/ext/nokogiri/xml_document.c +2 -2
  25. data/ext/nokogiri/xml_node.c +1 -1
  26. data/ext/nokogiri/xml_node_set.c +1 -1
  27. data/ext/nokogiri/xml_relax_ng.c +29 -11
  28. data/ext/nokogiri/xml_sax_parser.c +2 -7
  29. data/ext/nokogiri/xml_sax_parser_context.c +2 -2
  30. data/ext/nokogiri/xml_schema.c +55 -13
  31. data/ext/nokogiri/xml_xpath_context.c +80 -4
  32. data/ext/nokogiri/xslt_stylesheet.c +1 -4
  33. data/lib/nokogiri.rb +1 -1
  34. data/lib/nokogiri/css/parser.rb +3 -3
  35. data/lib/nokogiri/css/parser.y +2 -2
  36. data/lib/nokogiri/css/xpath_visitor.rb +70 -42
  37. data/lib/nokogiri/html/document.rb +12 -26
  38. data/lib/nokogiri/nokogiri.jar +0 -0
  39. data/lib/nokogiri/version.rb +2 -149
  40. data/lib/nokogiri/version/constant.rb +5 -0
  41. data/lib/nokogiri/version/info.rb +182 -0
  42. data/lib/nokogiri/xml/document.rb +17 -7
  43. data/lib/nokogiri/xml/document_fragment.rb +4 -6
  44. data/lib/nokogiri/xml/node.rb +50 -27
  45. data/lib/nokogiri/xml/parse_options.rb +6 -0
  46. data/lib/nokogiri/xml/relax_ng.rb +6 -2
  47. data/lib/nokogiri/xml/schema.rb +12 -4
  48. data/lib/nokogiri/xml/searchable.rb +3 -1
  49. metadata +47 -73
  50. data/ext/nokogiri/html_document.h +0 -10
  51. data/ext/nokogiri/html_element_description.h +0 -10
  52. data/ext/nokogiri/html_entity_lookup.h +0 -8
  53. data/ext/nokogiri/html_sax_parser_context.h +0 -11
  54. data/ext/nokogiri/html_sax_push_parser.h +0 -9
  55. data/ext/nokogiri/nokogiri.h +0 -134
  56. data/ext/nokogiri/xml_attr.h +0 -9
  57. data/ext/nokogiri/xml_attribute_decl.h +0 -9
  58. data/ext/nokogiri/xml_cdata.h +0 -9
  59. data/ext/nokogiri/xml_comment.h +0 -9
  60. data/ext/nokogiri/xml_document.h +0 -23
  61. data/ext/nokogiri/xml_document_fragment.h +0 -10
  62. data/ext/nokogiri/xml_dtd.h +0 -10
  63. data/ext/nokogiri/xml_element_content.h +0 -10
  64. data/ext/nokogiri/xml_element_decl.h +0 -9
  65. data/ext/nokogiri/xml_encoding_handler.h +0 -8
  66. data/ext/nokogiri/xml_entity_decl.h +0 -10
  67. data/ext/nokogiri/xml_entity_reference.h +0 -9
  68. data/ext/nokogiri/xml_io.h +0 -11
  69. data/ext/nokogiri/xml_libxml2_hacks.h +0 -12
  70. data/ext/nokogiri/xml_namespace.h +0 -14
  71. data/ext/nokogiri/xml_node.h +0 -13
  72. data/ext/nokogiri/xml_node_set.h +0 -12
  73. data/ext/nokogiri/xml_processing_instruction.h +0 -9
  74. data/ext/nokogiri/xml_reader.h +0 -10
  75. data/ext/nokogiri/xml_relax_ng.h +0 -9
  76. data/ext/nokogiri/xml_sax_parser.h +0 -39
  77. data/ext/nokogiri/xml_sax_parser_context.h +0 -10
  78. data/ext/nokogiri/xml_sax_push_parser.h +0 -9
  79. data/ext/nokogiri/xml_schema.h +0 -9
  80. data/ext/nokogiri/xml_syntax_error.h +0 -13
  81. data/ext/nokogiri/xml_text.h +0 -9
  82. data/ext/nokogiri/xml_xpath_context.h +0 -10
  83. data/ext/nokogiri/xslt_stylesheet.h +0 -14
@@ -1,8 +1,5 @@
1
1
  #include <xml_sax_parser.h>
2
2
 
3
- int vasprintf (char **strp, const char *fmt, va_list ap);
4
- void vasprintf_free (void *p);
5
-
6
3
  static ID id_start_document, id_end_document, id_start_element, id_end_element;
7
4
  static ID id_start_element_namespace, id_end_element_namespace;
8
5
  static ID id_comment, id_characters, id_xmldecl, id_error, id_warning;
@@ -206,7 +203,7 @@ static void warning_func(void * ctx, const char *msg, ...)
206
203
  va_end(args);
207
204
 
208
205
  ruby_message = NOKOGIRI_STR_NEW2(message);
209
- vasprintf_free(message);
206
+ free(message);
210
207
  rb_funcall(doc, id_warning, 1, ruby_message);
211
208
  }
212
209
 
@@ -223,7 +220,7 @@ static void error_func(void * ctx, const char *msg, ...)
223
220
  va_end(args);
224
221
 
225
222
  ruby_message = NOKOGIRI_STR_NEW2(message);
226
- vasprintf_free(message);
223
+ free(message);
227
224
  rb_funcall(doc, id_error, 1, ruby_message);
228
225
  }
229
226
 
@@ -262,8 +259,6 @@ static VALUE allocate(VALUE klass)
262
259
  {
263
260
  xmlSAXHandlerPtr handler = calloc((size_t)1, sizeof(xmlSAXHandler));
264
261
 
265
- xmlSetStructuredErrorFunc(NULL, NULL);
266
-
267
262
  handler->startDocument = start_document;
268
263
  handler->endDocument = end_document;
269
264
  handler->startElement = start_element;
@@ -4,13 +4,13 @@ VALUE cNokogiriXmlSaxParserContext ;
4
4
 
5
5
  static void deallocate(xmlParserCtxtPtr ctxt)
6
6
  {
7
- NOKOGIRI_DEBUG_START(handler);
7
+ NOKOGIRI_DEBUG_START(ctxt);
8
8
 
9
9
  ctxt->sax = NULL;
10
10
 
11
11
  xmlFreeParserCtxt(ctxt);
12
12
 
13
- NOKOGIRI_DEBUG_END(handler);
13
+ NOKOGIRI_DEBUG_END(ctxt);
14
14
  }
15
15
 
16
16
  /*
@@ -93,15 +93,27 @@ static VALUE validate_file(VALUE self, VALUE rb_filename)
93
93
  *
94
94
  * Create a new Schema from the contents of +string+
95
95
  */
96
- static VALUE read_memory(VALUE klass, VALUE content)
96
+ static VALUE read_memory(int argc, VALUE *argv, VALUE klass)
97
97
  {
98
+ VALUE content;
99
+ VALUE parse_options;
100
+ int parse_options_int;
101
+ xmlSchemaParserCtxtPtr ctx;
98
102
  xmlSchemaPtr schema;
99
- xmlSchemaParserCtxtPtr ctx = xmlSchemaNewMemParserCtxt(
100
- (const char *)StringValuePtr(content),
101
- (int)RSTRING_LEN(content)
102
- );
103
+ VALUE errors;
103
104
  VALUE rb_schema;
104
- VALUE errors = rb_ary_new();
105
+ int scanned_args = 0;
106
+ xmlExternalEntityLoader old_loader = 0;
107
+
108
+ scanned_args = rb_scan_args(argc, argv, "11", &content, &parse_options);
109
+ if (scanned_args == 1) {
110
+ parse_options = rb_const_get(rb_const_get(mNokogiriXml, rb_intern("ParseOptions")), rb_intern("DEFAULT_SCHEMA"));
111
+ }
112
+ parse_options_int = (int)NUM2INT(rb_funcall(parse_options, rb_intern("to_i"), 0));
113
+
114
+ ctx = xmlSchemaNewMemParserCtxt((const char *)StringValuePtr(content), (int)RSTRING_LEN(content));
115
+
116
+ errors = rb_ary_new();
105
117
  xmlSetStructuredErrorFunc((void *)errors, Nokogiri_error_array_pusher);
106
118
 
107
119
  #ifdef HAVE_XMLSCHEMASETPARSERSTRUCTUREDERRORS
@@ -109,10 +121,19 @@ static VALUE read_memory(VALUE klass, VALUE content)
109
121
  ctx,
110
122
  Nokogiri_error_array_pusher,
111
123
  (void *)errors
112
- );
124
+ );
113
125
  #endif
114
126
 
115
- schema = xmlSchemaParse(ctx);
127
+ if (parse_options_int & XML_PARSE_NONET) {
128
+ old_loader = xmlGetExternalEntityLoader();
129
+ xmlSetExternalEntityLoader(xmlNoNetExternalEntityLoader);
130
+ }
131
+
132
+ schema = xmlSchemaParse(ctx);
133
+
134
+ if (old_loader) {
135
+ xmlSetExternalEntityLoader(old_loader);
136
+ }
116
137
 
117
138
  xmlSetStructuredErrorFunc(NULL, NULL);
118
139
  xmlSchemaFreeParserCtxt(ctx);
@@ -129,6 +150,7 @@ static VALUE read_memory(VALUE klass, VALUE content)
129
150
 
130
151
  rb_schema = Data_Wrap_Struct(klass, 0, dealloc, schema);
131
152
  rb_iv_set(rb_schema, "@errors", errors);
153
+ rb_iv_set(rb_schema, "@parse_options", parse_options);
132
154
 
133
155
  return rb_schema;
134
156
  }
@@ -164,18 +186,28 @@ static int has_blank_nodes_p(VALUE cache)
164
186
  *
165
187
  * Create a new Schema from the Nokogiri::XML::Document +doc+
166
188
  */
167
- static VALUE from_document(VALUE klass, VALUE document)
189
+ static VALUE from_document(int argc, VALUE *argv, VALUE klass)
168
190
  {
191
+ VALUE document;
192
+ VALUE parse_options;
193
+ int parse_options_int;
169
194
  xmlDocPtr doc;
170
195
  xmlSchemaParserCtxtPtr ctx;
171
196
  xmlSchemaPtr schema;
172
197
  VALUE errors;
173
198
  VALUE rb_schema;
199
+ int scanned_args = 0;
200
+ xmlExternalEntityLoader old_loader = 0;
201
+
202
+ scanned_args = rb_scan_args(argc, argv, "11", &document, &parse_options);
174
203
 
175
204
  Data_Get_Struct(document, xmlDoc, doc);
205
+ doc = doc->doc; /* In case someone passes us a node. ugh. */
176
206
 
177
- /* In case someone passes us a node. ugh. */
178
- doc = doc->doc;
207
+ if (scanned_args == 1) {
208
+ parse_options = rb_const_get(rb_const_get(mNokogiriXml, rb_intern("ParseOptions")), rb_intern("DEFAULT_SCHEMA"));
209
+ }
210
+ parse_options_int = (int)NUM2INT(rb_funcall(parse_options, rb_intern("to_i"), 0));
179
211
 
180
212
  if (has_blank_nodes_p(DOC_NODE_CACHE(doc))) {
181
213
  rb_raise(rb_eArgError, "Creating a schema from a document that has blank nodes exposed to Ruby is dangerous");
@@ -194,8 +226,17 @@ static VALUE from_document(VALUE klass, VALUE document)
194
226
  );
195
227
  #endif
196
228
 
229
+ if (parse_options_int & XML_PARSE_NONET) {
230
+ old_loader = xmlGetExternalEntityLoader();
231
+ xmlSetExternalEntityLoader(xmlNoNetExternalEntityLoader);
232
+ }
233
+
197
234
  schema = xmlSchemaParse(ctx);
198
235
 
236
+ if (old_loader) {
237
+ xmlSetExternalEntityLoader(old_loader);
238
+ }
239
+
199
240
  xmlSetStructuredErrorFunc(NULL, NULL);
200
241
  xmlSchemaFreeParserCtxt(ctx);
201
242
 
@@ -211,6 +252,7 @@ static VALUE from_document(VALUE klass, VALUE document)
211
252
 
212
253
  rb_schema = Data_Wrap_Struct(klass, 0, dealloc, schema);
213
254
  rb_iv_set(rb_schema, "@errors", errors);
255
+ rb_iv_set(rb_schema, "@parse_options", parse_options);
214
256
 
215
257
  return rb_schema;
216
258
 
@@ -226,8 +268,8 @@ void init_xml_schema()
226
268
 
227
269
  cNokogiriXmlSchema = klass;
228
270
 
229
- rb_define_singleton_method(klass, "read_memory", read_memory, 1);
230
- rb_define_singleton_method(klass, "from_document", from_document, 1);
271
+ rb_define_singleton_method(klass, "read_memory", read_memory, -1);
272
+ rb_define_singleton_method(klass, "from_document", from_document, -1);
231
273
 
232
274
  rb_define_private_method(klass, "validate_document", validate_document, 1);
233
275
  rb_define_private_method(klass, "validate_file", validate_file, 1);
@@ -1,6 +1,11 @@
1
1
  #include <xml_xpath_context.h>
2
2
 
3
- int vasprintf (char **strp, const char *fmt, va_list ap);
3
+ /*
4
+ * these constants have matching declarations in
5
+ * ext/java/nokogiri/internals/NokogiriNamespaceContext.java
6
+ */
7
+ static const xmlChar *NOKOGIRI_BUILTIN_PREFIX = (const xmlChar *)"nokogiri-builtin";
8
+ static const xmlChar *NOKOGIRI_BUILTIN_URI = (const xmlChar *)"https://www.nokogiri.org/default_ns/ruby/builtins";
4
9
 
5
10
  static void deallocate(xmlXPathContextPtr ctx)
6
11
  {
@@ -9,6 +14,73 @@ static void deallocate(xmlXPathContextPtr ctx)
9
14
  NOKOGIRI_DEBUG_END(ctx);
10
15
  }
11
16
 
17
+ /* find a CSS class in an HTML element's `class` attribute */
18
+ const xmlChar* builtin_css_class(const xmlChar* str, const xmlChar *val)
19
+ {
20
+ int val_len;
21
+
22
+ if (str == NULL) { return(NULL); }
23
+ if (val == NULL) { return(NULL); }
24
+
25
+ val_len = xmlStrlen(val);
26
+ if (val_len == 0) { return(str); }
27
+
28
+ while (*str != 0) {
29
+ if ((*str == *val) && !xmlStrncmp(str, val, val_len)) {
30
+ const xmlChar* next_byte = str + val_len;
31
+
32
+ /* only match if the next byte is whitespace or end of string */
33
+ if ((*next_byte == 0) || (IS_BLANK_CH(*next_byte))) {
34
+ return((const xmlChar*)str);
35
+ }
36
+ }
37
+
38
+ /* advance str to whitespace */
39
+ while ((*str != 0) && !IS_BLANK_CH(*str)) {
40
+ str++;
41
+ }
42
+
43
+ /* advance str to start of next word or end of string */
44
+ while ((*str != 0) && IS_BLANK_CH(*str)) {
45
+ str++;
46
+ }
47
+ }
48
+
49
+ return(NULL);
50
+ }
51
+
52
+ /* xmlXPathFunction to wrap builtin_css_class() */
53
+ static void xpath_builtin_css_class(xmlXPathParserContextPtr ctxt, int nargs)
54
+ {
55
+ xmlXPathObjectPtr hay, needle;
56
+
57
+ CHECK_ARITY(2);
58
+
59
+ CAST_TO_STRING;
60
+ needle = valuePop(ctxt);
61
+ if ((needle == NULL) || (needle->type != XPATH_STRING)) {
62
+ xmlXPathFreeObject(needle);
63
+ XP_ERROR(XPATH_INVALID_TYPE);
64
+ }
65
+
66
+ CAST_TO_STRING;
67
+ hay = valuePop(ctxt);
68
+ if ((hay == NULL) || (hay->type != XPATH_STRING)) {
69
+ xmlXPathFreeObject(hay);
70
+ xmlXPathFreeObject(needle);
71
+ XP_ERROR(XPATH_INVALID_TYPE);
72
+ }
73
+
74
+ if (builtin_css_class(hay->stringval, needle->stringval)) {
75
+ valuePush(ctxt, xmlXPathNewBoolean(1));
76
+ } else {
77
+ valuePush(ctxt, xmlXPathNewBoolean(0));
78
+ }
79
+
80
+ xmlXPathFreeObject(hay);
81
+ xmlXPathFreeObject(needle);
82
+ }
83
+
12
84
  /*
13
85
  * call-seq:
14
86
  * register_ns(prefix, uri)
@@ -263,14 +335,18 @@ static VALUE new(VALUE klass, VALUE nodeobj)
263
335
  xmlXPathContextPtr ctx;
264
336
  VALUE self;
265
337
 
266
- xmlXPathInit();
267
-
268
338
  Data_Get_Struct(nodeobj, xmlNode, node);
269
339
 
340
+ xmlXPathInit();
341
+
270
342
  ctx = xmlXPathNewContext(node->doc);
271
343
  ctx->node = node;
344
+
345
+ xmlXPathRegisterNs(ctx, NOKOGIRI_BUILTIN_PREFIX, NOKOGIRI_BUILTIN_URI);
346
+ xmlXPathRegisterFuncNS(ctx, (const xmlChar *)"css-class", NOKOGIRI_BUILTIN_URI,
347
+ xpath_builtin_css_class);
348
+
272
349
  self = Data_Wrap_Struct(klass, 0, deallocate, ctx);
273
- /*rb_iv_set(self, "@xpath_handler", Qnil); */
274
350
  return self;
275
351
  }
276
352
 
@@ -7,9 +7,6 @@
7
7
 
8
8
  VALUE xslt;
9
9
 
10
- int vasprintf (char **strp, const char *fmt, va_list ap);
11
- void vasprintf_free (void *p);
12
-
13
10
  static void mark(nokogiriXsltStylesheetTuple *wrapper)
14
11
  {
15
12
  rb_gc_mark(wrapper->func_instances);
@@ -37,7 +34,7 @@ static void xslt_generic_error_handler(void * ctx, const char *msg, ...)
37
34
 
38
35
  rb_str_cat2((VALUE)ctx, message);
39
36
 
40
- vasprintf_free(message);
37
+ free(message);
41
38
  }
42
39
 
43
40
  VALUE Nokogiri_wrap_xslt_stylesheet(xsltStylesheetPtr ss)
@@ -113,7 +113,7 @@ module Nokogiri
113
113
  end
114
114
 
115
115
  ###
116
- # Parser a document contained in +args+. Nokogiri will try to guess what
116
+ # Parse a document contained in +args+. Nokogiri will try to guess what
117
117
  # type of document you are attempting to parse. For more information, see
118
118
  # Nokogiri.parse
119
119
  #
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
  #
3
3
  # DO NOT MODIFY!!!!
4
- # This file is automatically generated by Racc 1.4.16
4
+ # This file is automatically generated by Racc 1.5.1
5
5
  # from Racc grammar file "".
6
6
  #
7
7
 
@@ -476,7 +476,7 @@ def _reduce_26(val, _values, result)
476
476
  end
477
477
 
478
478
  def _reduce_27(val, _values, result)
479
- # Non standard, but hpricot supports it.
479
+ # non-standard, from hpricot
480
480
  result = Node.new(:PSEUDO_CLASS,
481
481
  [Node.new(:FUNCTION, ['nth-child(', val[1]])]
482
482
  )
@@ -558,7 +558,7 @@ def _reduce_40(val, _values, result)
558
558
  when 'n'
559
559
  result = Node.new(:NTH, ['1','n','+','0'])
560
560
  else
561
- # This is not CSS standard. It allows us to support this:
561
+ # non-standard to support custom functions:
562
562
  # assert_xpath("//a[foo(., @href)]", @parser.parse('a:foo(@href)'))
563
563
  # assert_xpath("//a[foo(., @a, b)]", @parser.parse('a:foo(@a, b)'))
564
564
  # assert_xpath("//a[foo(., a, 10)]", @parser.parse('a:foo(a, 10)'))
@@ -88,7 +88,7 @@ rule
88
88
  )
89
89
  }
90
90
  | LSQUARE NUMBER RSQUARE {
91
- # Non standard, but hpricot supports it.
91
+ # non-standard, from hpricot
92
92
  result = Node.new(:PSEUDO_CLASS,
93
93
  [Node.new(:FUNCTION, ['nth-child(', val[1]])]
94
94
  )
@@ -139,7 +139,7 @@ rule
139
139
  when 'n'
140
140
  result = Node.new(:NTH, ['1','n','+','0'])
141
141
  else
142
- # This is not CSS standard. It allows us to support this:
142
+ # non-standard to support custom functions:
143
143
  # assert_xpath("//a[foo(., @href)]", @parser.parse('a:foo(@href)'))
144
144
  # assert_xpath("//a[foo(., @a, b)]", @parser.parse('a:foo(@a, b)'))
145
145
  # assert_xpath("//a[foo(., a, 10)]", @parser.parse('a:foo(a, 10)'))
@@ -3,7 +3,6 @@ module Nokogiri
3
3
  module CSS
4
4
  class XPathVisitor # :nodoc:
5
5
  def visit_function node
6
-
7
6
  msg = :"visit_function_#{node.value.first.gsub(/[(]/, '')}"
8
7
  return self.send(msg, node) if self.respond_to?(msg)
9
8
 
@@ -13,50 +12,51 @@ module Nokogiri
13
12
  when /^self\(/
14
13
  "self::#{node.value[1]}"
15
14
  when /^eq\(/
16
- "position() = #{node.value[1]}"
15
+ "position()=#{node.value[1]}"
17
16
  when /^(nth|nth-of-type)\(/
18
17
  if node.value[1].is_a?(Nokogiri::CSS::Node) and node.value[1].type == :NTH
19
18
  nth(node.value[1])
20
19
  else
21
- "position() = #{node.value[1]}"
20
+ "position()=#{node.value[1]}"
22
21
  end
23
22
  when /^nth-child\(/
24
23
  if node.value[1].is_a?(Nokogiri::CSS::Node) and node.value[1].type == :NTH
25
24
  nth(node.value[1], :child => true)
26
25
  else
27
- "count(preceding-sibling::*) = #{node.value[1].to_i-1}"
26
+ "count(preceding-sibling::*)=#{node.value[1].to_i-1}"
28
27
  end
29
28
  when /^nth-last-of-type\(/
30
29
  if node.value[1].is_a?(Nokogiri::CSS::Node) and node.value[1].type == :NTH
31
30
  nth(node.value[1], :last => true)
32
31
  else
33
32
  index = node.value[1].to_i - 1
34
- index == 0 ? "position() = last()" : "position() = last() - #{index}"
33
+ index == 0 ? "position()=last()" : "position()=last()-#{index}"
35
34
  end
36
35
  when /^nth-last-child\(/
37
36
  if node.value[1].is_a?(Nokogiri::CSS::Node) and node.value[1].type == :NTH
38
37
  nth(node.value[1], :last => true, :child => true)
39
38
  else
40
- "count(following-sibling::*) = #{node.value[1].to_i-1}"
39
+ "count(following-sibling::*)=#{node.value[1].to_i-1}"
41
40
  end
42
41
  when /^(first|first-of-type)\(/
43
- "position() = 1"
42
+ "position()=1"
44
43
  when /^(last|last-of-type)\(/
45
- "position() = last()"
44
+ "position()=last()"
46
45
  when /^contains\(/
47
- "contains(., #{node.value[1]})"
46
+ "contains(.,#{node.value[1]})"
48
47
  when /^gt\(/
49
- "position() > #{node.value[1]}"
48
+ "position()>#{node.value[1]}"
50
49
  when /^only-child\(/
51
- "last() = 1"
50
+ "last()=1"
52
51
  when /^comment\(/
53
52
  "comment()"
54
53
  when /^has\(/
55
54
  is_direct = node.value[1].value[0].nil? # e.g. "has(> a)", "has(~ a)", "has(+ a)"
56
55
  ".#{"//" if !is_direct}#{node.value[1].accept(self)}"
57
56
  else
57
+ # non-standard. this looks like a function call.
58
58
  args = ['.'] + node.value[1..-1]
59
- "#{node.value.first}#{args.join(', ')})"
59
+ "#{node.value.first}#{args.join(',')})"
60
60
  end
61
61
  end
62
62
 
@@ -71,18 +71,18 @@ module Nokogiri
71
71
 
72
72
  def visit_id node
73
73
  node.value.first =~ /^#(.*)$/
74
- "@id = '#{$1}'"
74
+ "@id='#{$1}'"
75
75
  end
76
76
 
77
77
  def visit_attribute_condition node
78
- attribute = if (node.value.first.type == :FUNCTION) or (node.value.first.value.first =~ /::/)
79
- ''
80
- else
81
- '@'
82
- end
78
+ attribute = if (node.value.first.type == :FUNCTION) or (node.value.first.value.first =~ /::/)
79
+ ''
80
+ else
81
+ '@'
82
+ end
83
83
  attribute += node.value.first.accept(self)
84
84
 
85
- # Support non-standard css
85
+ # non-standard. attributes starting with '@'
86
86
  attribute.gsub!(/^@@/, '@')
87
87
 
88
88
  return attribute unless node.value.length == 3
@@ -90,29 +90,30 @@ module Nokogiri
90
90
  value = node.value.last
91
91
  value = "'#{value}'" if value !~ /^['"]/
92
92
 
93
+ # quoted values - see test_attribute_value_with_quotes in test/css/test_parser.rb
93
94
  if (value[0]==value[-1]) && %q{"'}.include?(value[0])
94
95
  str_value = value[1..-2]
95
96
  if str_value.include?(value[0])
96
- value = 'concat("' + str_value.split('"', -1).join(%q{", '"', "}) + '", "")'
97
+ value = 'concat("' + str_value.split('"', -1).join(%q{",'"',"}) + '","")'
97
98
  end
98
99
  end
99
100
 
100
101
  case node.value[1]
101
102
  when :equal
102
- attribute + " = " + "#{value}"
103
+ attribute + "=" + "#{value}"
103
104
  when :not_equal
104
- attribute + " != " + "#{value}"
105
+ attribute + "!=" + "#{value}"
105
106
  when :substring_match
106
- "contains(#{attribute}, #{value})"
107
+ "contains(#{attribute},#{value})"
107
108
  when :prefix_match
108
- "starts-with(#{attribute}, #{value})"
109
+ "starts-with(#{attribute},#{value})"
109
110
  when :dash_match
110
- "#{attribute} = #{value} or starts-with(#{attribute}, concat(#{value}, '-'))"
111
+ "#{attribute}=#{value} or starts-with(#{attribute},concat(#{value},'-'))"
111
112
  when :includes
112
- "contains(concat(\" \", #{attribute}, \" \"),concat(\" \", #{value}, \" \"))"
113
+ value = value[1..-2] # strip quotes
114
+ css_class(attribute, value)
113
115
  when :suffix_match
114
- "substring(#{attribute}, string-length(#{attribute}) - " +
115
- "string-length(#{value}) + 1, string-length(#{value})) = #{value}"
116
+ "substring(#{attribute},string-length(#{attribute})-string-length(#{value})+1,string-length(#{value}))=#{value}"
116
117
  else
117
118
  attribute + " #{node.value[1]} " + "#{value}"
118
119
  end
@@ -126,14 +127,14 @@ module Nokogiri
126
127
  return self.send(msg, node) if self.respond_to?(msg)
127
128
 
128
129
  case node.value.first
129
- when "first" then "position() = 1"
130
- when "first-child" then "count(preceding-sibling::*) = 0"
131
- when "last" then "position() = last()"
132
- when "last-child" then "count(following-sibling::*) = 0"
133
- when "first-of-type" then "position() = 1"
134
- when "last-of-type" then "position() = last()"
135
- when "only-child" then "count(preceding-sibling::*) = 0 and count(following-sibling::*) = 0"
136
- when "only-of-type" then "last() = 1"
130
+ when "first" then "position()=1"
131
+ when "first-child" then "count(preceding-sibling::*)=0"
132
+ when "last" then "position()=last()"
133
+ when "last-child" then "count(following-sibling::*)=0"
134
+ when "first-of-type" then "position()=1"
135
+ when "last-of-type" then "position()=last()"
136
+ when "only-child" then "count(preceding-sibling::*)=0 and count(following-sibling::*)=0"
137
+ when "only-of-type" then "last()=1"
137
138
  when "empty" then "not(node())"
138
139
  when "parent" then "node()"
139
140
  when "root" then "not(parent::*)"
@@ -144,7 +145,7 @@ module Nokogiri
144
145
  end
145
146
 
146
147
  def visit_class_condition node
147
- "contains(concat(' ', normalize-space(@class), ' '), ' #{node.value.first} ')"
148
+ css_class("@class", node.value.first)
148
149
  end
149
150
 
150
151
  def visit_combinator node
@@ -181,25 +182,26 @@ module Nokogiri
181
182
  node.accept(self)
182
183
  end
183
184
 
184
- private
185
+ private
186
+
185
187
  def nth node, options={}
186
188
  raise ArgumentError, "expected an+b node to contain 4 tokens, but is #{node.value.inspect}" unless node.value.size == 4
187
189
 
188
190
  a, b = read_a_and_positive_b node.value
189
191
  position = if options[:child]
190
- options[:last] ? "(count(following-sibling::*) + 1)" : "(count(preceding-sibling::*) + 1)"
192
+ options[:last] ? "(count(following-sibling::*)+1)" : "(count(preceding-sibling::*)+1)"
191
193
  else
192
194
  options[:last] ? "(last()-position()+1)" : "position()"
193
195
  end
194
196
 
195
197
  if b.zero?
196
- "(#{position} mod #{a}) = 0"
198
+ "(#{position} mod #{a})=0"
197
199
  else
198
200
  compare = a < 0 ? "<=" : ">="
199
201
  if a.abs == 1
200
- "#{position} #{compare} #{b}"
202
+ "#{position}#{compare}#{b}"
201
203
  else
202
- "(#{position} #{compare} #{b}) and (((#{position}-#{b}) mod #{a.abs}) = 0)"
204
+ "(#{position}#{compare}#{b}) and (((#{position}-#{b}) mod #{a.abs})=0)"
203
205
  end
204
206
  end
205
207
  end
@@ -227,6 +229,32 @@ module Nokogiri
227
229
  end =~ /(nth|first|last|only)-of-type(\()?/
228
230
  end
229
231
  end
232
+
233
+ # use only ordinary xpath functions
234
+ def css_class_standard(hay, needle)
235
+ "contains(concat(' ',normalize-space(#{hay}),' '),' #{needle} ')"
236
+ end
237
+
238
+ # use the builtin implementation
239
+ def css_class_builtin(hay, needle)
240
+ "nokogiri-builtin:css-class(#{hay},'#{needle}')"
241
+ end
242
+
243
+ alias_method :css_class, :css_class_standard
244
+ end
245
+
246
+ class XPathVisitorAlwaysUseBuiltins < XPathVisitor # :nodoc:
247
+ private
248
+ alias_method :css_class, :css_class_builtin
249
+ end
250
+
251
+ class XPathVisitorOptimallyUseBuiltins < XPathVisitor # :nodoc:
252
+ private
253
+ if Nokogiri.uses_libxml?
254
+ alias_method :css_class, :css_class_builtin
255
+ else
256
+ alias_method :css_class, :css_class_standard
257
+ end
230
258
  end
231
259
  end
232
260
  end