nokogiri 1.7.2-java → 1.8.0-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 (101) hide show
  1. checksums.yaml +4 -4
  2. data/.cross_rubies +4 -4
  3. data/.travis.yml +43 -24
  4. data/CHANGELOG.md +54 -6
  5. data/Gemfile +8 -7
  6. data/Gemfile-libxml-ruby +3 -0
  7. data/LICENSE-DEPENDENCIES.md +1612 -0
  8. data/{LICENSE.txt → LICENSE.md} +1 -1
  9. data/Manifest.txt +5 -8
  10. data/README.md +8 -5
  11. data/Rakefile +15 -31
  12. data/appveyor.yml +2 -0
  13. data/dependencies.yml +12 -7
  14. data/ext/java/nokogiri/HtmlDocument.java +2 -2
  15. data/ext/java/nokogiri/HtmlSaxParserContext.java +20 -21
  16. data/ext/java/nokogiri/HtmlSaxPushParser.java +6 -10
  17. data/ext/java/nokogiri/NokogiriService.java +10 -31
  18. data/ext/java/nokogiri/XmlAttr.java +1 -26
  19. data/ext/java/nokogiri/XmlCdata.java +0 -1
  20. data/ext/java/nokogiri/XmlComment.java +1 -1
  21. data/ext/java/nokogiri/XmlDocument.java +4 -5
  22. data/ext/java/nokogiri/XmlDocumentFragment.java +29 -21
  23. data/ext/java/nokogiri/XmlDtd.java +1 -1
  24. data/ext/java/nokogiri/XmlElement.java +9 -10
  25. data/ext/java/nokogiri/XmlEntityDecl.java +4 -5
  26. data/ext/java/nokogiri/XmlNode.java +105 -103
  27. data/ext/java/nokogiri/XmlNodeSet.java +64 -76
  28. data/ext/java/nokogiri/XmlReader.java +48 -48
  29. data/ext/java/nokogiri/XmlRelaxng.java +1 -1
  30. data/ext/java/nokogiri/XmlSaxPushParser.java +37 -17
  31. data/ext/java/nokogiri/XmlSchema.java +7 -5
  32. data/ext/java/nokogiri/XmlSyntaxError.java +47 -35
  33. data/ext/java/nokogiri/XmlXpathContext.java +160 -132
  34. data/ext/java/nokogiri/XsltStylesheet.java +15 -24
  35. data/ext/java/nokogiri/internals/HtmlDomParserContext.java +19 -23
  36. data/ext/java/nokogiri/internals/NokogiriDomParser.java +1 -1
  37. data/ext/java/nokogiri/internals/NokogiriEncodingReaderWrapper.java +1 -1
  38. data/ext/java/nokogiri/internals/NokogiriEntityResolver.java +11 -13
  39. data/ext/java/nokogiri/internals/NokogiriErrorHandler.java +5 -21
  40. data/ext/java/nokogiri/internals/NokogiriHandler.java +1 -1
  41. data/ext/java/nokogiri/internals/NokogiriHelpers.java +105 -142
  42. data/ext/java/nokogiri/internals/NokogiriNamespaceContext.java +16 -26
  43. data/ext/java/nokogiri/internals/NokogiriXPathFunction.java +32 -50
  44. data/ext/java/nokogiri/internals/NokogiriXPathFunctionResolver.java +10 -13
  45. data/ext/java/nokogiri/internals/NokogiriXPathVariableResolver.java +3 -10
  46. data/ext/java/nokogiri/internals/ParserContext.java +4 -8
  47. data/ext/java/nokogiri/internals/ReaderNode.java +53 -93
  48. data/ext/java/nokogiri/internals/SaveContextVisitor.java +77 -89
  49. data/ext/java/nokogiri/internals/SchemaErrorHandler.java +6 -9
  50. data/ext/java/nokogiri/internals/XalanDTMManagerPatch.java +167 -0
  51. data/ext/java/nokogiri/internals/XmlDomParserContext.java +17 -6
  52. data/ext/java/nokogiri/internals/c14n/Canonicalizer.java +1 -1
  53. data/ext/java/nokogiri/internals/c14n/Canonicalizer11.java +28 -28
  54. data/ext/java/nokogiri/internals/c14n/Canonicalizer20010315.java +3 -4
  55. data/ext/java/nokogiri/internals/c14n/Canonicalizer20010315Excl.java +2 -2
  56. data/ext/java/nokogiri/internals/c14n/CanonicalizerBase.java +10 -10
  57. data/ext/java/nokogiri/internals/c14n/ElementProxy.java +5 -5
  58. data/ext/java/nokogiri/internals/c14n/InclusiveNamespaces.java +2 -2
  59. data/ext/java/nokogiri/internals/c14n/NameSpaceSymbTable.java +1 -1
  60. data/ext/java/nokogiri/internals/c14n/XMLUtils.java +2 -2
  61. data/ext/java/org/apache/xml/dtm/ref/dom2dtm/DOM2DTMExt.java +1749 -0
  62. data/ext/nokogiri/extconf.rb +12 -17
  63. data/ext/nokogiri/nokogiri.h +0 -10
  64. data/ext/nokogiri/xml_attr.c +12 -8
  65. data/ext/nokogiri/xml_node.c +17 -14
  66. data/ext/nokogiri/xml_sax_push_parser.c +56 -12
  67. data/lib/nokogiri/html/sax/parser.rb +10 -0
  68. data/lib/nokogiri/nokogiri.jar +0 -0
  69. data/lib/nokogiri/version.rb +5 -4
  70. data/lib/nokogiri/xml/document.rb +9 -9
  71. data/lib/nokogiri/xml/node.rb +7 -7
  72. data/lib/nokogiri/xml/node_set.rb +12 -7
  73. data/lib/nokogiri/xml/sax/parser.rb +6 -7
  74. data/lib/nokogiri/xml/searchable.rb +34 -25
  75. data/lib/nokogiri/xml/syntax_error.rb +24 -1
  76. data/test/decorators/test_slop.rb +4 -1
  77. data/test/helper.rb +10 -0
  78. data/test/html/sax/test_parser.rb +27 -0
  79. data/test/html/test_document.rb +12 -1
  80. data/test/html/test_document_encoding.rb +1 -3
  81. data/test/html/test_document_fragment.rb +3 -0
  82. data/test/xml/sax/test_push_parser.rb +48 -0
  83. data/test/xml/test_attr.rb +7 -0
  84. data/test/xml/test_document.rb +1 -1
  85. data/test/xml/test_document_fragment.rb +27 -0
  86. data/test/xml/test_entity_reference.rb +2 -2
  87. data/test/xml/test_node.rb +12 -15
  88. data/test/xml/test_node_reparenting.rb +14 -0
  89. data/test/xml/test_node_set.rb +8 -6
  90. data/test/xml/test_reader.rb +19 -0
  91. data/test/xml/test_syntax_error.rb +21 -15
  92. data/test/xml/test_unparented_node.rb +54 -11
  93. data/test/xml/test_xpath.rb +23 -6
  94. metadata +32 -20
  95. data/ext/java/nokogiri/internals/NokogiriDocumentCache.java +0 -73
  96. data/ext/java/nokogiri/internals/XsltExtensionFunction.java +0 -72
  97. data/suppressions/nokogiri_ree-1.8.7.358.supp +0 -61
  98. data/suppressions/nokogiri_ruby-1.8.7.370.supp +0 -0
  99. data/suppressions/nokogiri_ruby-1.9.2.320.supp +0 -28
  100. data/suppressions/nokogiri_ruby-1.9.3.327.supp +0 -28
  101. data/test_all +0 -105
@@ -178,17 +178,19 @@ public class XmlSchema extends RubyObject {
178
178
 
179
179
  IRubyObject validate_document_or_file(ThreadContext context, XmlDocument xmlDocument) {
180
180
  RubyArray errors = (RubyArray) this.getInstanceVariable("@errors");
181
- ErrorHandler errorHandler = new SchemaErrorHandler(context.getRuntime(), errors);
181
+ ErrorHandler errorHandler = new SchemaErrorHandler(context.runtime, errors);
182
182
  setErrorHandler(errorHandler);
183
183
 
184
184
  try {
185
185
  validate(xmlDocument.getDocument());
186
- } catch(SAXException ex) {
187
- XmlSyntaxError xmlSyntaxError = (XmlSyntaxError) NokogiriService.XML_SYNTAXERROR_ALLOCATOR.allocate(context.getRuntime(), getNokogiriClass(context.getRuntime(), "Nokogiri::XML::SyntaxError"));
186
+ }
187
+ catch (SAXException ex) {
188
+ XmlSyntaxError xmlSyntaxError = XmlSyntaxError.createXMLSyntaxError(context.runtime);
188
189
  xmlSyntaxError.setException(ex);
189
190
  errors.append(xmlSyntaxError);
190
- } catch (IOException ex) {
191
- throw context.getRuntime().newIOError(ex.getMessage());
191
+ }
192
+ catch (IOException ex) {
193
+ throw context.runtime.newIOError(ex.getMessage());
192
194
  }
193
195
 
194
196
  return errors;
@@ -32,7 +32,6 @@
32
32
 
33
33
  package nokogiri;
34
34
 
35
- import static nokogiri.internals.NokogiriHelpers.getNokogiriClass;
36
35
  import static nokogiri.internals.NokogiriHelpers.stringOrNil;
37
36
 
38
37
  import org.jruby.CompatVersion;
@@ -53,64 +52,72 @@ import org.xml.sax.SAXParseException;
53
52
  */
54
53
  @JRubyClass(name="Nokogiri::XML::SyntaxError", parent="Nokogiri::SyntaxError")
55
54
  public class XmlSyntaxError extends RubyException {
55
+
56
56
  private Exception exception;
57
+ private boolean messageSet; // whether a custom error message was set
57
58
 
58
59
  public XmlSyntaxError(Ruby runtime, RubyClass klazz) {
59
60
  super(runtime, klazz);
60
61
  }
61
62
 
62
- /**
63
- * Create and return a copy of this object.
64
- *
65
- * @return a clone of this object
66
- */
67
- @Override
68
- public Object clone() throws CloneNotSupportedException {
69
- return super.clone();
70
- }
71
-
72
63
  public XmlSyntaxError(Ruby runtime, RubyClass rubyClass, Exception ex) {
73
64
  super(runtime, rubyClass, ex.getMessage());
74
65
  this.exception = ex;
75
66
  }
76
67
 
68
+ public XmlSyntaxError(Ruby runtime, RubyClass rubyClass, String message, Exception ex) {
69
+ super(runtime, rubyClass, message);
70
+ this.exception = ex; this.messageSet = true;
71
+ }
72
+
73
+ public static XmlSyntaxError createXMLSyntaxError(final Ruby runtime) {
74
+ RubyClass klazz = (RubyClass) runtime.getClassFromPath("Nokogiri::XML::SyntaxError");
75
+ return new XmlSyntaxError(runtime, klazz);
76
+ }
77
+
78
+ public static XmlSyntaxError createXMLSyntaxError(final Ruby runtime, final Exception ex) {
79
+ RubyClass klazz = (RubyClass) runtime.getClassFromPath("Nokogiri::XML::SyntaxError");
80
+ return new XmlSyntaxError(runtime, klazz, ex);
81
+ }
82
+
83
+ public static XmlSyntaxError createHTMLSyntaxError(final Ruby runtime) {
84
+ RubyClass klazz = (RubyClass) runtime.getClassFromPath("Nokogiri::HTML::SyntaxError");
85
+ return new XmlSyntaxError(runtime, klazz);
86
+ }
87
+
88
+ public static RubyException createXMLXPathSyntaxError(final Ruby runtime, final String msg, final Exception ex) {
89
+ RubyClass klazz = (RubyClass) runtime.getClassFromPath("Nokogiri::XML::XPath::SyntaxError");
90
+ return new XmlSyntaxError(runtime, klazz, msg, ex);
91
+ }
92
+
77
93
  public static XmlSyntaxError createWarning(Ruby runtime, SAXParseException e) {
78
- XmlSyntaxError xmlSyntaxError = createNokogiriXmlSyntaxError(runtime);
94
+ XmlSyntaxError xmlSyntaxError = createXMLSyntaxError(runtime);
79
95
  xmlSyntaxError.setException(runtime, e, 1);
80
96
  return xmlSyntaxError;
81
97
  }
82
98
 
83
99
  public static XmlSyntaxError createError(Ruby runtime, SAXParseException e) {
84
- XmlSyntaxError xmlSyntaxError = createNokogiriXmlSyntaxError(runtime);
100
+ XmlSyntaxError xmlSyntaxError = createXMLSyntaxError(runtime);
85
101
  xmlSyntaxError.setException(runtime, e, 2);
86
102
  return xmlSyntaxError;
87
103
  }
88
104
 
89
105
  public static XmlSyntaxError createFatalError(Ruby runtime, SAXParseException e) {
90
- XmlSyntaxError xmlSyntaxError = createNokogiriXmlSyntaxError(runtime);
106
+ XmlSyntaxError xmlSyntaxError = createXMLSyntaxError(runtime);
91
107
  xmlSyntaxError.setException(runtime, e, 3);
92
108
  return xmlSyntaxError;
93
109
  }
94
110
 
95
- public static XmlSyntaxError createNokogiriXmlSyntaxError(Ruby runtime) {
96
- return (XmlSyntaxError) NokogiriService.XML_SYNTAXERROR_ALLOCATOR.allocate(runtime, getNokogiriClass(runtime, "Nokogiri::XML::SyntaxError"));
97
- }
98
-
99
111
  public void setException(Exception exception) {
100
112
  this.exception = exception;
101
113
  }
102
114
 
103
- public void setException(Ruby runtime, Exception exception, int level) {
115
+ public void setException(Ruby runtime, SAXParseException exception, int level) {
104
116
  this.exception = exception;
105
117
  setInstanceVariable("@level", runtime.newFixnum(level));
106
- setInstanceVariable("@line", runtime.newFixnum(((SAXParseException)exception).getLineNumber()));
107
- setInstanceVariable("@column", runtime.newFixnum(((SAXParseException)exception).getColumnNumber()));
108
- setInstanceVariable("@file", stringOrNil(runtime, ((SAXParseException)exception).getSystemId()));
109
- }
110
-
111
- public static RubyException createXPathSyntaxError(Ruby runtime, Exception e) {
112
- RubyClass klazz = (RubyClass)runtime.getClassFromPath("Nokogiri::XML::XPath::SyntaxError");
113
- return new XmlSyntaxError(runtime, klazz, e);
118
+ setInstanceVariable("@line", runtime.newFixnum(exception.getLineNumber()));
119
+ setInstanceVariable("@column", runtime.newFixnum(exception.getColumnNumber()));
120
+ setInstanceVariable("@file", stringOrNil(runtime, exception.getSystemId()));
114
121
  }
115
122
 
116
123
  //@Override
@@ -119,10 +126,8 @@ public class XmlSyntaxError extends RubyException {
119
126
  @Override
120
127
  @JRubyMethod(name = "to_s", compat = CompatVersion.RUBY1_8)
121
128
  public IRubyObject to_s(ThreadContext context) {
122
- if (exception != null && exception.getMessage() != null)
123
- return context.getRuntime().newString(exception.getMessage());
124
- else
125
- return super.to_s(context);
129
+ IRubyObject msg = msg(context.runtime);
130
+ return msg != null ? msg : super.to_s(context);
126
131
  }
127
132
 
128
133
  //@Override
@@ -130,9 +135,16 @@ public class XmlSyntaxError extends RubyException {
130
135
  // to support older version of JRuby, the annotation is commented out
131
136
  @JRubyMethod(name = "to_s", compat = CompatVersion.RUBY1_9)
132
137
  public IRubyObject to_s19(ThreadContext context) {
133
- if (exception != null && exception.getMessage() != null)
134
- return context.getRuntime().newString(exception.getMessage());
135
- else
136
- return super.to_s19(context);
138
+ IRubyObject msg = msg(context.runtime);
139
+ return msg != null ? msg : super.to_s19(context);
137
140
  }
141
+
142
+ private IRubyObject msg(final Ruby runtime) {
143
+ if (exception != null && exception.getMessage() != null) {
144
+ if (messageSet) return null;
145
+ return runtime.newString( exception.getMessage() );
146
+ }
147
+ return null;
148
+ }
149
+
138
150
  }
@@ -32,15 +32,9 @@
32
32
 
33
33
  package nokogiri;
34
34
 
35
- import static nokogiri.internals.NokogiriHelpers.getNokogiriClass;
36
-
37
- import java.lang.reflect.Constructor;
38
- import java.lang.reflect.InvocationTargetException;
39
35
  import java.util.Set;
40
36
 
41
37
  import javax.xml.transform.TransformerException;
42
- import javax.xml.xpath.XPathExpressionException;
43
- import javax.xml.xpath.XPathFactory;
44
38
 
45
39
  import nokogiri.internals.NokogiriNamespaceContext;
46
40
  import nokogiri.internals.NokogiriXPathFunctionResolver;
@@ -48,18 +42,17 @@ import nokogiri.internals.NokogiriXPathVariableResolver;
48
42
 
49
43
  import org.jruby.Ruby;
50
44
  import org.jruby.RubyClass;
51
- import org.jruby.RubyException;
52
45
  import org.jruby.RubyObject;
53
46
  import org.jruby.anno.JRubyClass;
54
47
  import org.jruby.anno.JRubyMethod;
55
48
  import org.jruby.exceptions.RaiseException;
56
49
  import org.jruby.runtime.ThreadContext;
57
50
  import org.jruby.runtime.builtin.IRubyObject;
51
+ import org.jruby.util.SafePropertyAccessor;
58
52
  import org.w3c.dom.Node;
59
- import org.w3c.dom.NodeList;
60
53
 
61
54
  import org.apache.xml.dtm.DTM;
62
- import org.apache.xml.utils.PrefixResolver;
55
+ import org.apache.xpath.XPath;
63
56
  import org.apache.xpath.XPathContext;
64
57
  import org.apache.xpath.jaxp.JAXPExtensionsProvider;
65
58
  import org.apache.xpath.jaxp.JAXPPrefixResolver;
@@ -75,171 +68,206 @@ import org.apache.xpath.objects.XObject;
75
68
  */
76
69
  @JRubyClass(name="Nokogiri::XML::XPathContext")
77
70
  public class XmlXpathContext extends RubyObject {
78
- public final static String XPATH_CONTEXT = "CACHCED_XPATH_CONTEXT";
79
71
 
80
- private XmlNode context;
81
- private final NokogiriXPathFunctionResolver functionResolver;
82
- private final NokogiriXPathVariableResolver variableResolver;
83
- private PrefixResolver prefixResolver;
84
- private XPathContext xpathSupport = null;
85
- private NokogiriNamespaceContext nsContext;
86
-
87
- public XmlXpathContext(Ruby ruby, RubyClass rubyClass) {
88
- super(ruby, rubyClass);
89
- functionResolver = NokogiriXPathFunctionResolver.create(ruby.getCurrentContext().nil);
90
- variableResolver = NokogiriXPathVariableResolver.create();
72
+ static {
73
+ final String DTMManager = "org.apache.xml.dtm.DTMManager";
74
+ if (SafePropertyAccessor.getProperty(DTMManager) == null) {
75
+ try { // use patched "org.apache.xml.dtm.ref.DTMManagerDefault"
76
+ System.setProperty(DTMManager, nokogiri.internals.XalanDTMManagerPatch.class.getName());
77
+ }
78
+ catch (SecurityException ex) { /* no-op - will work although might be slower */ }
79
+ }
91
80
  }
92
81
 
93
- private void setNode(XmlNode node) throws IllegalArgumentException, ClassNotFoundException, InstantiationException, IllegalAccessException, InvocationTargetException {
94
- Node doc = node.getNode().getOwnerDocument();
95
- if (doc == null) {
96
- doc = node.getNode();
97
- }
98
- xpathSupport = (XPathContext) doc.getUserData(XPATH_CONTEXT);
82
+ /**
83
+ * user-data key for (cached) {@link XPathContext}
84
+ */
85
+ public static final String XPATH_CONTEXT = "CACHED_XPATH_CONTEXT";
99
86
 
100
- if (xpathSupport == null) {
101
- JAXPExtensionsProvider jep = getProviderInstance();
102
- xpathSupport = new XPathContext(jep);
103
- xpathSupport.setVarStack(new JAXPVariableStack(variableResolver));
104
- doc.setUserData(XPATH_CONTEXT, xpathSupport, null);
105
- }
87
+ private XmlNode context;
106
88
 
107
- context = node;
108
- nsContext = NokogiriNamespaceContext.create();
109
- prefixResolver = new JAXPPrefixResolver(nsContext);
89
+ public XmlXpathContext(Ruby runtime, RubyClass klass) {
90
+ super(runtime, klass);
110
91
  }
111
92
 
112
- private JAXPExtensionsProvider getProviderInstance() throws ClassNotFoundException, IllegalArgumentException, InstantiationException, IllegalAccessException, InvocationTargetException {
113
- Class<?> clazz = Class.forName("org.apache.xpath.jaxp.JAXPExtensionsProvider");
114
- Constructor[] constructors = clazz.getDeclaredConstructors();
115
- for (int i = 0; i < constructors.length; i++) {
116
- Class[] parameterTypes = constructors[i].getParameterTypes();
117
- if (parameterTypes.length == 2) {
118
- return (JAXPExtensionsProvider) constructors[i].newInstance(functionResolver, false);
119
- } else if (parameterTypes.length == 1) {
120
- return (JAXPExtensionsProvider) constructors[i].newInstance(functionResolver);
121
- }
122
- }
123
- return null;
93
+ public XmlXpathContext(Ruby runtime, RubyClass klass, XmlNode node) {
94
+ this(runtime, klass);
95
+ initNode(node);
124
96
  }
125
97
 
126
- /**
127
- * Create and return a copy of this object.
128
- *
129
- * @return a clone of this object
130
- */
131
- @Override
132
- public Object clone() throws CloneNotSupportedException {
133
- return super.clone();
98
+ private void initNode(XmlNode node) {
99
+ context = node;
134
100
  }
135
101
 
136
102
  @JRubyMethod(name = "new", meta = true)
137
- public static IRubyObject rbNew(ThreadContext thread_context, IRubyObject klazz, IRubyObject node) {
138
- XmlNode xmlNode = (XmlNode)node;
139
- XmlXpathContext xmlXpathContext = (XmlXpathContext) NokogiriService.XML_XPATHCONTEXT_ALLOCATOR.allocate(thread_context.getRuntime(), (RubyClass)klazz);
140
- XPathFactory.newInstance().newXPath();
103
+ public static IRubyObject rbNew(ThreadContext context, IRubyObject klazz, IRubyObject node) {
141
104
  try {
142
- xmlXpathContext.setNode(xmlNode);
143
- } catch (IllegalArgumentException e) {
144
- throw thread_context.getRuntime().newRuntimeError(e.getMessage());
145
- } catch (ClassNotFoundException e) {
146
- throw thread_context.getRuntime().newRuntimeError(e.getMessage());
147
- } catch (InstantiationException e) {
148
- throw thread_context.getRuntime().newRuntimeError(e.getMessage());
149
- } catch (IllegalAccessException e) {
150
- throw thread_context.getRuntime().newRuntimeError(e.getMessage());
151
- } catch (InvocationTargetException e) {
152
- throw thread_context.getRuntime().newRuntimeError(e.getMessage());
105
+ return new XmlXpathContext(context.runtime, (RubyClass) klazz, (XmlNode) node);
106
+ }
107
+ catch (IllegalArgumentException e) {
108
+ throw context.getRuntime().newRuntimeError(e.getMessage());
153
109
  }
154
- return xmlXpathContext;
155
110
  }
156
111
 
157
112
  @JRubyMethod
158
- public IRubyObject evaluate(ThreadContext thread_context, IRubyObject expr, IRubyObject handler) {
159
- functionResolver.setHandler(handler);
160
- String src = (String) expr.toJava(String.class);
161
- if(!handler.isNil()) {
113
+ public IRubyObject evaluate(ThreadContext context, IRubyObject expr, IRubyObject handler) {
114
+
115
+ String src = expr.convertToString().asJavaString();
116
+ if (!handler.isNil()) {
162
117
  if (!isContainsPrefix(src)) {
118
+ StringBuilder replacement = new StringBuilder();
163
119
  Set<String> methodNames = handler.getMetaClass().getMethods().keySet();
120
+ final String PREFIX = NokogiriNamespaceContext.NOKOGIRI_PREFIX;
164
121
  for (String name : methodNames) {
165
- src = src.replaceAll(name, NokogiriNamespaceContext.NOKOGIRI_PREFIX+":"+name);
122
+ replacement.setLength(0);
123
+ replacement.ensureCapacity(PREFIX.length() + 1 + name.length());
124
+ replacement.append(PREFIX).append(':').append(name);
125
+ src = src.replace(name, replacement); // replace(name, NOKOGIRI_PREFIX + ':' + name)
166
126
  }
167
127
  }
168
128
  }
169
- return node_set(thread_context, src);
129
+
130
+ return node_set(context, src, handler);
131
+ }
132
+
133
+ @JRubyMethod
134
+ public IRubyObject evaluate(ThreadContext context, IRubyObject expr) {
135
+ return this.evaluate(context, expr, context.getRuntime().getNil());
136
+ }
137
+
138
+ private final NokogiriNamespaceContext nsContext = NokogiriNamespaceContext.create();
139
+
140
+ @JRubyMethod
141
+ public IRubyObject register_ns(IRubyObject prefix, IRubyObject uri) {
142
+ nsContext.registerNamespace(prefix.asJavaString(), uri.asJavaString());
143
+ return this;
144
+ }
145
+
146
+ private NokogiriXPathVariableResolver variableResolver; // binds (if any)
147
+
148
+ @JRubyMethod
149
+ public IRubyObject register_variable(IRubyObject name, IRubyObject value) {
150
+ NokogiriXPathVariableResolver variableResolver = this.variableResolver;
151
+ if (variableResolver == null) {
152
+ variableResolver = NokogiriXPathVariableResolver.create();
153
+ this.variableResolver = variableResolver;
154
+ }
155
+ variableResolver.registerVariable(name.asJavaString(), value.asJavaString());
156
+ return this;
170
157
  }
171
158
 
172
- protected IRubyObject node_set(ThreadContext thread_context, String expr) {
159
+ private IRubyObject node_set(ThreadContext context, String expr, IRubyObject handler) {
160
+ final NokogiriXPathFunctionResolver fnResolver =
161
+ handler.isNil() ? null : NokogiriXPathFunctionResolver.create(handler);
173
162
  try {
174
- return tryGetNodeSet(thread_context, expr);
175
- } catch (XPathExpressionException xpee) {
176
- RubyException e = XmlSyntaxError.createXPathSyntaxError(getRuntime(), xpee);
177
- throw new RaiseException(e);
163
+ return tryGetNodeSet(context, expr, fnResolver);
164
+ }
165
+ catch (TransformerException ex) {
166
+ throw new RaiseException(XmlSyntaxError.createXMLXPathSyntaxError(context.runtime, expr, ex)); // Nokogiri::XML::XPath::SyntaxError
178
167
  }
179
168
  }
180
169
 
181
- private IRubyObject tryGetNodeSet(ThreadContext thread_context, String expr) throws XPathExpressionException {
182
- XObject xobj = null;
170
+ private IRubyObject tryGetNodeSet(ThreadContext context, String expr, NokogiriXPathFunctionResolver fnResolver) throws TransformerException {
171
+ final Node contextNode = this.context.node;
183
172
 
184
- Node contextNode = context.node;
173
+ final JAXPPrefixResolver prefixResolver = new JAXPPrefixResolver(nsContext);
174
+ XPath xpathInternal = new XPath(expr, null, prefixResolver, XPath.SELECT);
185
175
 
186
- try {
187
- org.apache.xpath.XPath xpathInternal = new org.apache.xpath.XPath (expr, null,
188
- prefixResolver, org.apache.xpath.XPath.SELECT );
189
-
190
- // We always need to have a ContextNode with Xalan XPath implementation
191
- // To allow simple expression evaluation like 1+1 we are setting
192
- // dummy Document as Context Node
193
-
194
- if ( contextNode == null )
195
- xobj = xpathInternal.execute(xpathSupport, DTM.NULL, prefixResolver);
196
- else
197
- xobj = xpathInternal.execute(xpathSupport, contextNode, prefixResolver);
198
-
199
- switch (xobj.getType()) {
200
- case XObject.CLASS_BOOLEAN:
201
- return thread_context.getRuntime().newBoolean(xobj.bool());
202
- case XObject.CLASS_NUMBER:
203
- return thread_context.getRuntime().newFloat(xobj.num());
204
- case XObject.CLASS_NODESET:
205
- NodeList nodeList = xobj.nodelist();
206
- XmlNodeSet xmlNodeSet = (XmlNodeSet) NokogiriService.XML_NODESET_ALLOCATOR.allocate(getRuntime(), getNokogiriClass(getRuntime(), "Nokogiri::XML::NodeSet"));
207
- xmlNodeSet.setNodeList(nodeList);
208
- xmlNodeSet.initialize(thread_context.getRuntime(), context);
209
- return xmlNodeSet;
210
- default:
211
- return thread_context.getRuntime().newString(xobj.str());
212
- }
213
- } catch(TransformerException ex) {
214
- throw new XPathExpressionException(expr);
176
+ // We always need to have a ContextNode with Xalan XPath implementation
177
+ // To allow simple expression evaluation like 1+1 we are setting
178
+ // dummy Document as Context Node
179
+ final XObject xobj;
180
+ if ( contextNode == null )
181
+ xobj = xpathInternal.execute(getXPathContext(fnResolver), DTM.NULL, prefixResolver);
182
+ else
183
+ xobj = xpathInternal.execute(getXPathContext(fnResolver), contextNode, prefixResolver);
184
+
185
+ switch (xobj.getType()) {
186
+ case XObject.CLASS_BOOLEAN : return context.getRuntime().newBoolean(xobj.bool());
187
+ case XObject.CLASS_NUMBER : return context.getRuntime().newFloat(xobj.num());
188
+ case XObject.CLASS_NODESET :
189
+ XmlNodeSet xmlNodeSet = XmlNodeSet.create(context.getRuntime());
190
+ xmlNodeSet.setNodeList(xobj.nodelist());
191
+ xmlNodeSet.initialize(context.getRuntime(), this.context);
192
+ return xmlNodeSet;
193
+ default : return context.getRuntime().newString(xobj.str());
194
+ }
195
+ }
196
+
197
+ private XPathContext getXPathContext(final NokogiriXPathFunctionResolver fnResolver) {
198
+ Node doc = context.getNode().getOwnerDocument();
199
+ if (doc == null) doc = context.getNode();
200
+
201
+ XPathContext xpathContext = (XPathContext) doc.getUserData(XPATH_CONTEXT);
202
+
203
+ if ( xpathContext == null ) {
204
+ xpathContext = newXPathContext(fnResolver);
205
+ if ( variableResolver == null ) {
206
+ // NOTE: only caching without variables - could be improved by more sophisticated caching
207
+ doc.setUserData(XPATH_CONTEXT, xpathContext, null);
208
+ }
209
+ }
210
+ else {
211
+ Object owner = xpathContext.getOwnerObject();
212
+ if ( ( owner == null && fnResolver == null ) ||
213
+ ( owner instanceof JAXPExtensionsProvider && ((JAXPExtensionsProvider) owner).hasSameResolver(fnResolver) ) ) {
214
+ // can be re-used assuming it has the same variable-stack (for now only cached if no variables)
215
+ if ( variableResolver == null ) return xpathContext;
216
+ }
217
+ xpathContext = newXPathContext(fnResolver); // otherwise we can not use the cached xpath-context
218
+ }
219
+
220
+ if ( variableResolver != null ) {
221
+ xpathContext.setVarStack(new JAXPVariableStack(variableResolver));
215
222
  }
223
+
224
+ return xpathContext;
216
225
  }
217
226
 
218
- private boolean isContainsPrefix(String str) {
219
- Set<String> prefixes = nsContext.getAllPrefixes();
220
- for (String prefix : prefixes) {
221
- if (str.contains(prefix + ":")) {
227
+ private static XPathContext newXPathContext(final NokogiriXPathFunctionResolver functionResolver) {
228
+ if ( functionResolver == null ) return new XPathContext(false);
229
+ return new XPathContext(new JAXPExtensionsProvider(functionResolver), false);
230
+ }
231
+
232
+ private boolean isContainsPrefix(final String str) {
233
+ final StringBuilder prefix_ = new StringBuilder();
234
+ for ( String prefix : nsContext.getAllPrefixes() ) {
235
+ prefix_.setLength(0);
236
+ prefix_.ensureCapacity(prefix.length() + 1);
237
+ prefix_.append(prefix).append(':');
238
+ if ( str.contains(prefix_) ) { // prefix + ':'
222
239
  return true;
223
240
  }
224
241
  }
225
242
  return false;
226
243
  }
227
244
 
245
+ private static final class JAXPExtensionsProvider extends org.apache.xpath.jaxp.JAXPExtensionsProvider {
228
246
 
229
- @JRubyMethod
230
- public IRubyObject evaluate(ThreadContext context, IRubyObject expr) {
231
- return this.evaluate(context, expr, context.getRuntime().getNil());
232
- }
247
+ final NokogiriXPathFunctionResolver resolver;
233
248
 
234
- @JRubyMethod
235
- public IRubyObject register_ns(ThreadContext context, IRubyObject prefix, IRubyObject uri) {
236
- nsContext.registerNamespace((String)prefix.toJava(String.class), (String)uri.toJava(String.class));
237
- return this;
238
- }
249
+ JAXPExtensionsProvider(NokogiriXPathFunctionResolver resolver) {
250
+ super(resolver, false);
251
+ this.resolver = resolver;
252
+ }
253
+
254
+ //@Override
255
+ //public boolean equals(Object obj) {
256
+ // if (obj instanceof JAXPExtensionsProvider) {
257
+ // return hasSameResolver(((JAXPExtensionsProvider) obj).resolver);
258
+ // }
259
+ // return false;
260
+ //}
261
+
262
+ final boolean hasSameResolver(final NokogiriXPathFunctionResolver resolver) {
263
+ return resolver == this.resolver || resolver != null && (
264
+ resolver.getHandler() == null ? this.resolver.getHandler() == null : (
265
+ resolver.getHandler() == this.resolver.getHandler()
266
+ // resolver.getHandler().eql( this.resolver.getHandler() )
267
+ )
268
+ );
269
+ }
239
270
 
240
- @JRubyMethod
241
- public IRubyObject register_variable(ThreadContext context, IRubyObject name, IRubyObject value) {
242
- variableResolver.registerVariable((String)name.toJava(String.class), (String)value.toJava(String.class));
243
- return this;
244
271
  }
272
+
245
273
  }