nokogiri 1.5.0-java → 1.5.1-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 (81) hide show
  1. data/CHANGELOG.ja.rdoc +56 -12
  2. data/CHANGELOG.rdoc +45 -0
  3. data/C_CODING_STYLE.rdoc +27 -0
  4. data/Manifest.txt +4 -0
  5. data/README.rdoc +11 -7
  6. data/Rakefile +44 -26
  7. data/bin/nokogiri +10 -2
  8. data/ext/java/nokogiri/HtmlDocument.java +37 -2
  9. data/ext/java/nokogiri/NokogiriService.java +10 -2
  10. data/ext/java/nokogiri/XmlAttr.java +1 -1
  11. data/ext/java/nokogiri/XmlDocument.java +68 -11
  12. data/ext/java/nokogiri/XmlDocumentFragment.java +16 -5
  13. data/ext/java/nokogiri/XmlElement.java +0 -40
  14. data/ext/java/nokogiri/XmlNamespace.java +8 -1
  15. data/ext/java/nokogiri/XmlNode.java +131 -27
  16. data/ext/java/nokogiri/XmlNodeSet.java +4 -1
  17. data/ext/java/nokogiri/XmlSaxParserContext.java +2 -13
  18. data/ext/java/nokogiri/XmlXpathContext.java +4 -1
  19. data/ext/java/nokogiri/XsltStylesheet.java +198 -37
  20. data/ext/java/nokogiri/internals/HtmlDomParserContext.java +40 -2
  21. data/ext/java/nokogiri/internals/NokogiriHelpers.java +82 -9
  22. data/ext/java/nokogiri/internals/NokogiriXsltErrorListener.java +4 -3
  23. data/ext/java/nokogiri/internals/ParserContext.java +33 -3
  24. data/ext/java/nokogiri/internals/SaveContextVisitor.java +203 -12
  25. data/ext/java/nokogiri/internals/XmlDomParser.java +33 -2
  26. data/ext/java/nokogiri/internals/XmlDomParserContext.java +32 -12
  27. data/ext/nokogiri/extconf.rb +11 -3
  28. data/ext/nokogiri/html_document.c +16 -0
  29. data/ext/nokogiri/html_sax_parser_context.c +59 -37
  30. data/ext/nokogiri/html_sax_push_parser.c +87 -0
  31. data/ext/nokogiri/html_sax_push_parser.h +9 -0
  32. data/ext/nokogiri/nokogiri.c +6 -8
  33. data/ext/nokogiri/nokogiri.h +3 -0
  34. data/ext/nokogiri/xml_document.c +101 -3
  35. data/ext/nokogiri/xml_document.h +3 -3
  36. data/ext/nokogiri/xml_node.c +150 -58
  37. data/ext/nokogiri/xml_node_set.c +169 -120
  38. data/ext/nokogiri/xml_node_set.h +5 -0
  39. data/ext/nokogiri/xml_sax_parser_context.c +64 -41
  40. data/ext/nokogiri/xml_text.c +2 -0
  41. data/ext/nokogiri/xml_xpath_context.c +30 -24
  42. data/ext/nokogiri/xslt_stylesheet.c +62 -16
  43. data/ext/nokogiri/xslt_stylesheet.h +5 -0
  44. data/lib/nokogiri/css/parser.rb +163 -157
  45. data/lib/nokogiri/css/parser.y +6 -3
  46. data/lib/nokogiri/css/tokenizer.rb +1 -1
  47. data/lib/nokogiri/css/tokenizer.rex +1 -1
  48. data/lib/nokogiri/html.rb +1 -0
  49. data/lib/nokogiri/html/document.rb +82 -42
  50. data/lib/nokogiri/html/sax/push_parser.rb +16 -0
  51. data/lib/nokogiri/nokogiri.jar +0 -0
  52. data/lib/nokogiri/version.rb +1 -1
  53. data/lib/nokogiri/xml.rb +6 -0
  54. data/lib/nokogiri/xml/builder.rb +7 -1
  55. data/lib/nokogiri/xml/document.rb +32 -17
  56. data/lib/nokogiri/xml/document_fragment.rb +6 -1
  57. data/lib/nokogiri/xml/node.rb +40 -9
  58. data/lib/nokogiri/xslt.rb +5 -1
  59. data/tasks/cross_compile.rb +1 -0
  60. data/tasks/nokogiri.org.rb +6 -0
  61. data/tasks/test.rb +1 -0
  62. data/test/css/test_xpath_visitor.rb +6 -0
  63. data/test/helper.rb +1 -0
  64. data/test/html/test_document.rb +26 -0
  65. data/test/html/test_document_fragment.rb +1 -2
  66. data/test/test_memory_leak.rb +81 -1
  67. data/test/test_xslt_transforms.rb +152 -123
  68. data/test/xml/test_builder.rb +24 -2
  69. data/test/xml/test_c14n.rb +151 -0
  70. data/test/xml/test_document.rb +48 -0
  71. data/test/xml/test_namespace.rb +5 -0
  72. data/test/xml/test_node.rb +82 -1
  73. data/test/xml/test_node_attributes.rb +19 -0
  74. data/test/xml/test_node_inheritance.rb +32 -0
  75. data/test/xml/test_node_reparenting.rb +32 -0
  76. data/test/xml/test_node_set.rb +16 -8
  77. data/test/xml/test_reader_encoding.rb +16 -0
  78. data/test/xml/test_unparented_node.rb +32 -0
  79. data/test/xml/test_xinclude.rb +83 -0
  80. data/test/xml/test_xpath.rb +22 -0
  81. metadata +147 -123
@@ -582,9 +582,17 @@ public class NokogiriService implements BasicLibraryService {
582
582
  }
583
583
  };
584
584
 
585
- private static ObjectAllocator XSLT_STYLESHEET_ALLOCATOR = new ObjectAllocator() {
585
+ public static ObjectAllocator XSLT_STYLESHEET_ALLOCATOR = new ObjectAllocator() {
586
+ private XsltStylesheet xsltStylesheet = null;
586
587
  public IRubyObject allocate(Ruby runtime, RubyClass klazz) {
587
- return new XsltStylesheet(runtime, klazz);
588
+ if (xsltStylesheet == null) xsltStylesheet = new XsltStylesheet(runtime, klazz);
589
+ try {
590
+ XsltStylesheet clone = (XsltStylesheet) xsltStylesheet.clone();
591
+ clone.setMetaClass(klazz);
592
+ return clone;
593
+ } catch (CloneNotSupportedException e) {
594
+ return new XmlText(runtime, klazz);
595
+ }
588
596
  }
589
597
  };
590
598
  }
@@ -154,7 +154,7 @@ public class XmlAttr extends XmlNode{
154
154
  protected IRubyObject getNodeName(ThreadContext context) {
155
155
  if (name != null) return name;
156
156
  String attrName = ((Attr)node).getName();
157
- attrName = NokogiriHelpers.getLocalPart(attrName);
157
+ if (!(doc instanceof HtmlDocument)) attrName = NokogiriHelpers.getLocalPart(attrName);
158
158
  return attrName == null ? context.getRuntime().getNil() : RubyString.newString(context.getRuntime(), attrName);
159
159
  }
160
160
 
@@ -33,12 +33,13 @@
33
33
  package nokogiri;
34
34
 
35
35
  import static nokogiri.internals.NokogiriHelpers.getCachedNodeOrCreate;
36
- import static nokogiri.internals.NokogiriHelpers.getLocalNameForNamespace;
37
36
  import static nokogiri.internals.NokogiriHelpers.getNokogiriClass;
38
37
  import static nokogiri.internals.NokogiriHelpers.isNamespace;
39
38
  import static nokogiri.internals.NokogiriHelpers.rubyStringToString;
40
39
  import static nokogiri.internals.NokogiriHelpers.stringOrNil;
41
40
 
41
+ import java.util.List;
42
+
42
43
  import javax.xml.parsers.DocumentBuilderFactory;
43
44
  import javax.xml.parsers.ParserConfigurationException;
44
45
 
@@ -48,14 +49,17 @@ import nokogiri.internals.SaveContextVisitor;
48
49
  import nokogiri.internals.XmlDomParserContext;
49
50
 
50
51
  import org.jruby.Ruby;
52
+ import org.jruby.RubyArray;
51
53
  import org.jruby.RubyClass;
52
54
  import org.jruby.RubyFixnum;
53
55
  import org.jruby.RubyNil;
56
+ import org.jruby.RubyString;
54
57
  import org.jruby.anno.JRubyClass;
55
58
  import org.jruby.anno.JRubyMethod;
56
59
  import org.jruby.javasupport.JavaUtil;
57
60
  import org.jruby.javasupport.util.RuntimeHelpers;
58
61
  import org.jruby.runtime.Arity;
62
+ import org.jruby.runtime.Block;
59
63
  import org.jruby.runtime.ThreadContext;
60
64
  import org.jruby.runtime.builtin.IRubyObject;
61
65
  import org.w3c.dom.Attr;
@@ -108,8 +112,7 @@ public class XmlDocument extends XmlNode {
108
112
  setInstanceVariable("@decorators", ruby.getNil());
109
113
  }
110
114
 
111
- @Override
112
- public void setNode(ThreadContext context, Node node) {
115
+ public void setDocumentNode(ThreadContext context, Node node) {
113
116
  super.setNode(context, node);
114
117
  if (nsCache == null) nsCache = new NokogiriNamespaceCache();
115
118
  Ruby runtime = context.getRuntime();
@@ -128,7 +131,7 @@ public class XmlDocument extends XmlNode {
128
131
  // not sure, but like attribute values, text value will be lost
129
132
  // unless it is referred once before this document is used.
130
133
  // this seems to happen only when the fragment is parsed from Node#in_context.
131
- private void stabilizeTextContent(Document document) {
134
+ protected void stabilizeTextContent(Document document) {
132
135
  if (document.getDocumentElement() != null) document.getDocumentElement().getTextContent();
133
136
  }
134
137
 
@@ -215,7 +218,7 @@ public class XmlDocument extends XmlNode {
215
218
  return getUrl();
216
219
  }
217
220
 
218
- protected static Document createNewDocument() {
221
+ public static Document createNewDocument() {
219
222
  try {
220
223
  DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(DOCUMENTBUILDERFACTORY_IMPLE_NAME, NokogiriService.class.getClassLoader());
221
224
  return factory.newDocumentBuilder().newDocument();
@@ -237,11 +240,11 @@ public class XmlDocument extends XmlNode {
237
240
  Document docNode = createNewDocument();
238
241
  if ("Nokogiri::HTML::Document".equals(((RubyClass)klazz).getName())) {
239
242
  xmlDocument = (XmlDocument) NokogiriService.HTML_DOCUMENT_ALLOCATOR.allocate(context.getRuntime(), (RubyClass) klazz);
240
- xmlDocument.setNode(context, docNode);
243
+ xmlDocument.setDocumentNode(context, docNode);
241
244
  } else {
242
245
  // XML::Document and sublass
243
246
  xmlDocument = (XmlDocument) NokogiriService.XML_DOCUMENT_ALLOCATOR.allocate(context.getRuntime(), (RubyClass) klazz);
244
- xmlDocument.setNode(context, docNode);
247
+ xmlDocument.setDocumentNode(context, docNode);
245
248
  }
246
249
  } catch (Exception ex) {
247
250
  throw context.getRuntime().newRuntimeError("couldn't create document: "+ex.toString());
@@ -349,9 +352,13 @@ public class XmlDocument extends XmlNode {
349
352
  node.getOwnerDocument().renameNode(node, null, node.getLocalName());
350
353
  NamedNodeMap attrs = node.getAttributes();
351
354
  for (int i=0; i<attrs.getLength(); i++) {
352
- Node attr = attrs.item(i);
353
- attr.setPrefix(null);
354
- attr.getOwnerDocument().renameNode(attr, null, attr.getLocalName());
355
+ Attr attr = (Attr) attrs.item(i);
356
+ if (isNamespace(attr.getNodeName())) {
357
+ ((org.w3c.dom.Element)node).removeAttributeNode(attr);
358
+ } else {
359
+ attr.setPrefix(null);
360
+ attr.getOwnerDocument().renameNode(attr, null, attr.getLocalName());
361
+ }
355
362
  }
356
363
  }
357
364
  XmlNodeSet nodeSet = (XmlNodeSet) xmlNode.children(context);
@@ -508,7 +515,7 @@ public class XmlDocument extends XmlNode {
508
515
  public static IRubyObject wrapJavaDocument(ThreadContext context, IRubyObject klazz, IRubyObject arg) {
509
516
  XmlDocument xmlDocument = (XmlDocument) NokogiriService.XML_DOCUMENT_ALLOCATOR.allocate(context.getRuntime(), getNokogiriClass(context.getRuntime(), "Nokogiri::XML::Document"));
510
517
  Document document = (Document)arg.toJava(Document.class);
511
- xmlDocument.setNode(context, document);
518
+ xmlDocument.setDocumentNode(context, document);
512
519
  return xmlDocument;
513
520
  }
514
521
 
@@ -516,4 +523,54 @@ public class XmlDocument extends XmlNode {
516
523
  public IRubyObject toJavaDocument(ThreadContext context) {
517
524
  return JavaUtil.convertJavaToUsableRubyObject(context.getRuntime(), (org.w3c.dom.Document)node);
518
525
  }
526
+
527
+ /* call-seq:
528
+ * doc.canonicalize(mode=XML_C14N_1_0,inclusive_namespaces=nil,with_comments=false)
529
+ * doc.canonicalize { |obj, parent| ... }
530
+ *
531
+ * Canonicalize a document and return the results. Takes an optional block
532
+ * that takes two parameters: the +obj+ and that node's +parent+.
533
+ * The +obj+ will be either a Nokogiri::XML::Node, or a Nokogiri::XML::Namespace
534
+ * The block must return a non-nil, non-false value if the +obj+ passed in
535
+ * should be included in the canonicalized document.
536
+ */
537
+ @JRubyMethod(optional=3)
538
+ public IRubyObject canonicalize(ThreadContext context, IRubyObject[] args, Block block) {
539
+ XmlNode startingNode = getStartingNode(block);
540
+ int canonicalOpts = 1;
541
+ int mode = 0;
542
+ if (args.length == 3) {
543
+ mode = (Integer)(args[0].isNil() ? 0 : args[0].toJava(Integer.class));
544
+ if (mode == 1) canonicalOpts = canonicalOpts | 16; // exclusive
545
+ // inclusive prefix list for exclusive c14n
546
+ canonicalOpts = args[2].isTrue() ? (canonicalOpts | 4) : canonicalOpts;
547
+ }
548
+ if (startingNode != this) canonicalOpts = canonicalOpts | 8; // subsets
549
+ // 38 = NO_DECL | NO_EMPTY | AS_XML
550
+ SaveContextVisitor visitor = new SaveContextVisitor(38, null, "UTF-8", false, false, canonicalOpts);
551
+ if (args.length == 3 && !args[1].isNil()) {
552
+ visitor.setC14nExclusiveInclusivePrefixes((List<String>)NokogiriHelpers.rubyStringArrayToJavaList((RubyArray)args[1]));
553
+ }
554
+ startingNode.accept(context, visitor);
555
+ Ruby runtime = context.getRuntime();
556
+ IRubyObject result = runtime.getTrue();
557
+ if (block.isGiven()) {
558
+ List<Node> list = visitor.getC14nNodeList();
559
+ for (Node n : list) {
560
+ IRubyObject currentNode = getCachedNodeOrCreate(runtime, n);
561
+ IRubyObject parentNode = getCachedNodeOrCreate(runtime, n.getParentNode());
562
+ result = block.call(context, currentNode, parentNode);
563
+ }
564
+ }
565
+ return result.isTrue() ? stringOrNil(runtime, visitor.toString()) : RubyString.newEmptyString(runtime);
566
+ }
567
+
568
+ private XmlNode getStartingNode(Block block) {
569
+ if (block.isGiven()) {
570
+ if (block.getBinding().getSelf() instanceof XmlNode) {
571
+ return (XmlNode)block.getBinding().getSelf();
572
+ }
573
+ }
574
+ return this;
575
+ }
519
576
  }
@@ -92,8 +92,10 @@ public class XmlDocumentFragment extends XmlNode {
92
92
  // make wellformed fragment, ignore invalid namespace, or add appropriate namespace to parse
93
93
  if (args.length > 1 && args[1] instanceof RubyString) {
94
94
  args[1] = trim(context, doc, (RubyString)args[1]);
95
- args[1] = RubyString.newString(context.getRuntime(), ignoreNamespaceIfNeeded(doc, rubyStringToString(args[1])));
96
- args[1] = RubyString.newString(context.getRuntime(), addNamespaceDeclIfNeeded(doc, rubyStringToString(args[1])));
95
+ if (XmlDocumentFragment.isTag((RubyString)args[1])) {
96
+ args[1] = RubyString.newString(context.getRuntime(), ignoreNamespaceIfNeeded(doc, rubyStringToString(args[1])));
97
+ args[1] = RubyString.newString(context.getRuntime(), addNamespaceDeclIfNeeded(doc, rubyStringToString(args[1])));
98
+ }
97
99
  }
98
100
 
99
101
  XmlDocumentFragment fragment = (XmlDocumentFragment) NokogiriService.XML_DOCUMENT_FRAGMENT_ALLOCATOR.allocate(context.getRuntime(), (RubyClass)cls);
@@ -120,18 +122,27 @@ public class XmlDocumentFragment extends XmlNode {
120
122
  return result.isNil() ? str : result;
121
123
  }
122
124
 
125
+ private static boolean isTag(RubyString ruby_string) {
126
+ String str = rubyStringToString(ruby_string);
127
+ if (str.startsWith("<") && str.endsWith(">")) return true;
128
+ return false;
129
+ }
130
+
123
131
  private static Pattern qname_pattern = Pattern.compile("[^</:>\\s]+:[^</:>=\\s]+");
124
132
  private static Pattern starttag_pattern = Pattern.compile("<[^</>]+>");
125
133
 
126
134
  private static String ignoreNamespaceIfNeeded(XmlDocument doc, String tags) {
127
135
  if (doc.getDocument() == null) return tags;
128
- if (doc.getDocument().getDocumentElement() == null) return tags;
129
136
  Matcher matcher = qname_pattern.matcher(tags);
130
137
  Map<String, String> rewriteTable = new HashMap<String, String>();
131
138
  while(matcher.find()) {
132
139
  String qName = matcher.group();
133
- NamedNodeMap nodeMap = doc.getDocument().getDocumentElement().getAttributes();
134
- if (!isNamespaceDefined(qName, nodeMap)) {
140
+ if (doc.getDocument().getDocumentElement() != null) {
141
+ NamedNodeMap nodeMap = doc.getDocument().getDocumentElement().getAttributes();
142
+ if (!isNamespaceDefined(qName, nodeMap)) {
143
+ rewriteTable.put(qName, getLocalPart(qName));
144
+ }
145
+ } else {
135
146
  rewriteTable.put(qName, getLocalPart(qName));
136
147
  }
137
148
  }
@@ -42,7 +42,6 @@ import org.jruby.anno.JRubyClass;
42
42
  import org.jruby.anno.JRubyMethod;
43
43
  import org.jruby.javasupport.util.RuntimeHelpers;
44
44
  import org.jruby.runtime.ThreadContext;
45
- import org.jruby.runtime.Visibility;
46
45
  import org.jruby.runtime.builtin.IRubyObject;
47
46
  import org.w3c.dom.Attr;
48
47
  import org.w3c.dom.Element;
@@ -102,45 +101,6 @@ public class XmlElement extends XmlNode {
102
101
  @Override
103
102
  public boolean isElement() { return true; }
104
103
 
105
- @Override
106
- @JRubyMethod(visibility = Visibility.PRIVATE)
107
- public IRubyObject get(ThreadContext context, IRubyObject rbkey) {
108
- if (rbkey == null || rbkey.isNil()) context.getRuntime().getNil();
109
- String key = rubyStringToString(rbkey);
110
- Element element = (Element) node;
111
- String value = element.getAttribute(key);
112
- if(!value.equals("")){
113
- return context.getRuntime().newString(value);
114
- }
115
- return context.getRuntime().getNil();
116
- }
117
-
118
- @Override
119
- public IRubyObject key_p(ThreadContext context, IRubyObject rbkey) {
120
- String key = rubyStringToString(rbkey);
121
- Element element = (Element) node;
122
- return context.getRuntime().newBoolean(element.hasAttribute(key));
123
- }
124
-
125
- @Override
126
- public IRubyObject op_aset(ThreadContext context,
127
- IRubyObject rbkey,
128
- IRubyObject rbval) {
129
- String key = rubyStringToString(rbkey);
130
- String val = rubyStringToString(rbval);
131
- Element element = (Element) node;
132
- element.setAttribute(key, val);
133
- return this;
134
- }
135
-
136
- @Override
137
- public IRubyObject remove_attribute(ThreadContext context, IRubyObject name) {
138
- String key = name.convertToString().asJavaString();
139
- Element element = (Element) node;
140
- element.removeAttribute(key);
141
- return this;
142
- }
143
-
144
104
  @Override
145
105
  public void relink_namespace(ThreadContext context) {
146
106
  Element e = (Element) node;
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * (The MIT License)
3
3
  *
4
- * Copyright (c) 2008 - 2011:
4
+ * Copyright (c) 2008 - 2012:
5
5
  *
6
6
  * * {Aaron Patterson}[http://tenderlovemaking.com]
7
7
  * * {Mike Dalessio}[http://mike.daless.io]
@@ -36,6 +36,7 @@ import static nokogiri.internals.NokogiriHelpers.CACHED_NODE;
36
36
  import static nokogiri.internals.NokogiriHelpers.getCachedNodeOrCreate;
37
37
  import static nokogiri.internals.NokogiriHelpers.getLocalNameForNamespace;
38
38
  import static nokogiri.internals.NokogiriHelpers.getNokogiriClass;
39
+ import nokogiri.internals.NokogiriHelpers;
39
40
  import nokogiri.internals.SaveContextVisitor;
40
41
 
41
42
  import org.jruby.Ruby;
@@ -79,6 +80,12 @@ public class XmlNamespace extends RubyObject {
79
80
  public String getHref() {
80
81
  return hrefString;
81
82
  }
83
+
84
+ void deleteHref() {
85
+ hrefString = "http://www.w3.org/XML/1998/namespace";
86
+ href = NokogiriHelpers.stringOrNil(getRuntime(), hrefString);
87
+ attr.getOwnerElement().removeAttributeNode(attr);
88
+ }
82
89
 
83
90
  public void init(Attr attr, IRubyObject prefix, IRubyObject href, IRubyObject xmlDocument) {
84
91
  init(attr, prefix, href, (String) prefix.toJava(String.class), (String) href.toJava(String.class), xmlDocument);
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * (The MIT License)
3
3
  *
4
- * Copyright (c) 2008 - 2011:
4
+ * Copyright (c) 2008 - 2012:
5
5
  *
6
6
  * * {Aaron Patterson}[http://tenderlovemaking.com]
7
7
  * * {Mike Dalessio}[http://mike.daless.io]
@@ -42,6 +42,13 @@ import static nokogiri.internals.NokogiriHelpers.stringOrNil;
42
42
 
43
43
  import java.io.ByteArrayInputStream;
44
44
  import java.io.InputStream;
45
+ import java.io.UnsupportedEncodingException;
46
+ import java.nio.ByteBuffer;
47
+ import java.nio.CharBuffer;
48
+ import java.nio.charset.CharacterCodingException;
49
+ import java.nio.charset.Charset;
50
+ import java.nio.charset.CharsetDecoder;
51
+ import java.nio.charset.CharsetEncoder;
45
52
  import java.util.ArrayList;
46
53
  import java.util.List;
47
54
 
@@ -53,7 +60,6 @@ import nokogiri.internals.XmlDomParserContext;
53
60
 
54
61
  import org.jruby.Ruby;
55
62
  import org.jruby.RubyArray;
56
- import org.jruby.RubyBoolean;
57
63
  import org.jruby.RubyClass;
58
64
  import org.jruby.RubyFixnum;
59
65
  import org.jruby.RubyModule;
@@ -61,6 +67,7 @@ import org.jruby.RubyObject;
61
67
  import org.jruby.RubyString;
62
68
  import org.jruby.anno.JRubyClass;
63
69
  import org.jruby.anno.JRubyMethod;
70
+ import org.jruby.exceptions.RaiseException;
64
71
  import org.jruby.javasupport.util.RuntimeHelpers;
65
72
  import org.jruby.runtime.Block;
66
73
  import org.jruby.runtime.ThreadContext;
@@ -431,7 +438,25 @@ public class XmlNode extends RubyObject {
431
438
  //this should delegate to subclasses' implementation
432
439
  }
433
440
 
434
- public void accept(ThreadContext context, SaveContextVisitor visitor) {}
441
+ // Users might extend XmlNode. This method works for such a case.
442
+ public void accept(ThreadContext context, SaveContextVisitor visitor) {
443
+ visitor.enter(node);
444
+ XmlNodeSet xmlNodeSet = (XmlNodeSet) children(context);
445
+ if (xmlNodeSet.length() > 0) {
446
+ RubyArray array = (RubyArray) xmlNodeSet.to_a(context);
447
+ for(int i = 0; i < array.getLength(); i++) {
448
+ Object item = array.get(i);
449
+ if (item instanceof XmlNode) {
450
+ XmlNode cur = (XmlNode) item;
451
+ cur.accept(context, visitor);
452
+ } else if (item instanceof XmlNamespace) {
453
+ XmlNamespace cur = (XmlNamespace)item;
454
+ cur.accept(context, visitor);
455
+ }
456
+ }
457
+ }
458
+ visitor.leave(node);
459
+ }
435
460
 
436
461
  public void setName(IRubyObject name) {
437
462
  this.name = name;
@@ -502,6 +527,8 @@ public class XmlNode extends RubyObject {
502
527
  if (node != namespaceOwner) {
503
528
  node.getOwnerDocument().renameNode(node, ns.getHref(), ns.getPrefix() + node.getLocalName());
504
529
  }
530
+
531
+ updateNodeNamespaceIfNecessary(context, ns);
505
532
 
506
533
  return ns;
507
534
  }
@@ -528,7 +555,7 @@ public class XmlNode extends RubyObject {
528
555
  RubyArray attr = ruby.newArray();
529
556
 
530
557
  for(int i = 0; i < nodeMap.getLength(); i++) {
531
- if (!NokogiriHelpers.isNamespace(nodeMap.item(i))) {
558
+ if ((doc instanceof HtmlDocument) || !NokogiriHelpers.isNamespace(nodeMap.item(i))) {
532
559
  attr.append(getCachedNodeOrCreate(context.getRuntime(), nodeMap.item(i)));
533
560
  }
534
561
  }
@@ -551,8 +578,13 @@ public class XmlNode extends RubyObject {
551
578
 
552
579
  @JRubyMethod(name = "blank?")
553
580
  public IRubyObject blank_p(ThreadContext context) {
554
- String data = node.getTextContent();
555
- if ("".equals(data.trim())) return context.getRuntime().getTrue();
581
+ // according to libxml doc,
582
+ // a node is blank if if it is a Text or CDATA node consisting of whitespace only
583
+ if (node.getNodeType() == Node.TEXT_NODE || node.getNodeType() == Node.CDATA_SECTION_NODE) {
584
+ String data = node.getTextContent();
585
+ if (data == null) return context.getRuntime().getTrue();
586
+ if ("".equals(data.trim())) return context.getRuntime().getTrue();
587
+ }
556
588
  return context.getRuntime().getFalse();
557
589
  }
558
590
 
@@ -683,7 +715,7 @@ public class XmlNode extends RubyObject {
683
715
 
684
716
  RubyArray documentErrors = getErrorArray(document);
685
717
  RubyArray docErrors = getErrorArray(doc);
686
- if (isErrorIncreated(documentErrors, docErrors)) {
718
+ if (isErrorIncreased(documentErrors, docErrors)) {
687
719
  for (int i = 0; i < docErrors.getLength(); i++) {
688
720
  documentErrors.add(docErrors.get(i));
689
721
  }
@@ -702,8 +734,7 @@ public class XmlNode extends RubyObject {
702
734
  }
703
735
  RubyArray nodeArray = RubyArray.newArray(runtime);
704
736
  nodeArray.add(NokogiriHelpers.getCachedNodeOrCreate(runtime, first));
705
-
706
- NokogiriHelpers.nodeListToRubyArray(runtime, first.getChildNodes(), nodeArray);
737
+
707
738
  XmlNodeSet xmlNodeSet = XmlNodeSet.newXmlNodeSet(context, nodeArray);
708
739
  return xmlNodeSet;
709
740
  }
@@ -716,9 +747,10 @@ public class XmlNode extends RubyObject {
716
747
  return RubyArray.newArray(document.getRuntime());
717
748
  }
718
749
 
719
- private boolean isErrorIncreated(RubyArray baseErrors, RubyArray createdErrors) {
720
- RubyBoolean result = baseErrors.compare(baseErrors.getRuntime().getCurrentContext(), "eql?", createdErrors, null);
721
- return result.isFalse();
750
+ private boolean isErrorIncreased(RubyArray baseErrors, RubyArray createdErrors) {
751
+ RubyFixnum length = ((RubyArray)createdErrors.op_diff(baseErrors)).length();
752
+ int diff_in_length = (Integer)length.toJava(Integer.class);
753
+ return diff_in_length > 0;
722
754
  }
723
755
 
724
756
  @JRubyMethod(name = {"content", "text", "inner_text"})
@@ -792,10 +824,19 @@ public class XmlNode extends RubyObject {
792
824
  /**
793
825
  * Get the attribute at the given key, <code>key</code>.
794
826
  * Assumes that this node has attributes (i.e. that key? returned
795
- * true). Overridden in XmlElement.
827
+ * true).
796
828
  */
797
829
  @JRubyMethod(visibility = Visibility.PRIVATE)
798
- public IRubyObject get(ThreadContext context, IRubyObject key) {
830
+ public IRubyObject get(ThreadContext context, IRubyObject rbkey) {
831
+ if (node instanceof Element) {
832
+ if (rbkey == null || rbkey.isNil()) context.getRuntime().getNil();
833
+ String key = rubyStringToString(rbkey);
834
+ Element element = (Element) node;
835
+ String value = element.getAttribute(key);
836
+ if (value != null) {
837
+ return context.getRuntime().newString(value);
838
+ }
839
+ }
799
840
  return context.getRuntime().getNil();
800
841
  }
801
842
 
@@ -888,11 +929,18 @@ public class XmlNode extends RubyObject {
888
929
  */
889
930
  @JRubyMethod(name = {"key?", "has_attribute?"})
890
931
  public IRubyObject key_p(ThreadContext context, IRubyObject rbkey) {
891
- return context.getRuntime().getNil();
932
+ if (node instanceof Element) {
933
+ String key = rubyStringToString(rbkey);
934
+ Element element = (Element) node;
935
+ return context.getRuntime().newBoolean(element.hasAttribute(key));
936
+ } else {
937
+ return context.getRuntime().getNil();
938
+ }
892
939
  }
893
940
 
894
941
  @JRubyMethod
895
- public IRubyObject namespace(ThreadContext context){
942
+ public IRubyObject namespace(ThreadContext context) {
943
+ if (doc instanceof HtmlDocument) return context.getRuntime().getNil();
896
944
  XmlDocument xmlDocument = (XmlDocument) doc;
897
945
  NokogiriNamespaceCache nsCache = xmlDocument.getNamespaceCache();
898
946
  String prefix = node.getPrefix();
@@ -917,6 +965,7 @@ public class XmlNode extends RubyObject {
917
965
  Ruby ruby = context.getRuntime();
918
966
  RubyArray namespace_definitions = ruby.newArray();
919
967
  if (doc == null) return namespace_definitions;
968
+ if (doc instanceof HtmlDocument) return namespace_definitions;
920
969
  List<XmlNamespace> namespaces = ((XmlDocument)doc).getNamespaceCache().get(node);
921
970
  for (XmlNamespace namespace : namespaces) {
922
971
  ((RubyArray)namespace_definitions).append(namespace);
@@ -953,7 +1002,11 @@ public class XmlNode extends RubyObject {
953
1002
 
954
1003
  protected void setContent(IRubyObject content) {
955
1004
  this.content = content;
956
- this.node.setTextContent(rubyStringToString(content));
1005
+ String javaContent = rubyStringToString(content);
1006
+ node.setTextContent(javaContent);
1007
+ if (javaContent.length() == 0) return;
1008
+ if (node.getNodeType() == Node.TEXT_NODE || node.getNodeType() == Node.CDATA_SECTION_NODE) return;
1009
+ node.getFirstChild().setUserData(NokogiriHelpers.ENCODED_STRING, true, null);
957
1010
  }
958
1011
 
959
1012
  private void setContent(String content) {
@@ -974,8 +1027,7 @@ public class XmlNode extends RubyObject {
974
1027
  * IRubyObject options}
975
1028
  */
976
1029
  @JRubyMethod(required=4, visibility=Visibility.PRIVATE)
977
- public IRubyObject native_write_to(ThreadContext context,
978
- IRubyObject[] args) {
1030
+ public IRubyObject native_write_to(ThreadContext context, IRubyObject[] args) {
979
1031
 
980
1032
  IRubyObject io = args[0];
981
1033
  IRubyObject encoding = args[1];
@@ -985,10 +1037,20 @@ public class XmlNode extends RubyObject {
985
1037
  String encString = encoding.isNil() ? null : rubyStringToString(encoding);
986
1038
 
987
1039
  SaveContextVisitor visitor =
988
- new SaveContextVisitor((Integer)options.toJava(Integer.class), rubyStringToString(indentString), encString,
989
- isHtmlDoc(context), isFragment());
1040
+ new SaveContextVisitor((Integer) options.toJava(Integer.class), rubyStringToString(indentString), encString, isHtmlDoc(context), isFragment(), 0);
990
1041
  accept(context, visitor);
991
- IRubyObject rubyString = stringOrNil(context.getRuntime(), visitor.toString());
1042
+
1043
+ IRubyObject rubyString = null;
1044
+ if (NokogiriHelpers.isUTF8(encString)) {
1045
+ rubyString = stringOrNil(context.getRuntime(), visitor.toString());
1046
+ } else {
1047
+ try {
1048
+ byte[] bytes = NokogiriHelpers.convertEncoding(Charset.forName(encString), visitor.toString());
1049
+ rubyString = stringOrNil(context.getRuntime(), bytes);
1050
+ } catch (CharacterCodingException e) {
1051
+ throw context.getRuntime().newRuntimeError(e.getMessage());
1052
+ }
1053
+ }
992
1054
  RuntimeHelpers.invoke(context, io, "write", rubyString);
993
1055
 
994
1056
  return io;
@@ -1035,9 +1097,17 @@ public class XmlNode extends RubyObject {
1035
1097
  return this;
1036
1098
  }
1037
1099
 
1038
- @JRubyMethod(name = {"[]=", "set_attribute"})
1039
- public IRubyObject op_aset(ThreadContext context, IRubyObject index, IRubyObject val) {
1040
- return val;
1100
+ @JRubyMethod(visibility = Visibility.PRIVATE)
1101
+ public IRubyObject set(ThreadContext context, IRubyObject rbkey, IRubyObject rbval) {
1102
+ if (node instanceof Element) {
1103
+ String key = rubyStringToString(rbkey);
1104
+ String val = rubyStringToString(rbval);
1105
+ Element element = (Element) node;
1106
+ element.setAttribute(key, val);
1107
+ return this;
1108
+ } else {
1109
+ return rbval;
1110
+ }
1041
1111
  }
1042
1112
 
1043
1113
  @JRubyMethod
@@ -1066,6 +1136,11 @@ public class XmlNode extends RubyObject {
1066
1136
 
1067
1137
  @JRubyMethod(name = {"remove_attribute", "delete"})
1068
1138
  public IRubyObject remove_attribute(ThreadContext context, IRubyObject name) {
1139
+ if (node instanceof Element) {
1140
+ String key = name.convertToString().asJavaString();
1141
+ Element element = (Element) node;
1142
+ element.removeAttribute(key);
1143
+ }
1069
1144
  return this;
1070
1145
  }
1071
1146
 
@@ -1297,10 +1372,12 @@ public class XmlNode extends RubyObject {
1297
1372
  * otherNode parentless also. */
1298
1373
  if (otherNode.getParentNode() != null)
1299
1374
  otherNode.getParentNode().removeChild(otherNode);
1300
-
1301
1375
  return;
1302
1376
  }
1303
-
1377
+ if (thisNode.getPreviousSibling() != null &&
1378
+ thisNode.getPreviousSibling().getNodeType() == Node.TEXT_NODE &&
1379
+ otherNode.getNodeType() == Node.TEXT_NODE) return;
1380
+
1304
1381
  parent.insertBefore(otherNode, thisNode);
1305
1382
  }
1306
1383
 
@@ -1318,6 +1395,11 @@ public class XmlNode extends RubyObject {
1318
1395
  }
1319
1396
 
1320
1397
  Node nextSib = thisNode.getNextSibling();
1398
+
1399
+ if (nextSib != null &&
1400
+ nextSib.getNodeType() == Node.TEXT_NODE &&
1401
+ otherNode.getNodeType() == Node.TEXT_NODE) return;
1402
+
1321
1403
  if (nextSib != null) {
1322
1404
  parent.insertBefore(otherNode, nextSib);
1323
1405
  } else {
@@ -1375,4 +1457,26 @@ public class XmlNode extends RubyObject {
1375
1457
  public IRubyObject add_next_sibling_node(ThreadContext context, IRubyObject other) {
1376
1458
  return adoptAs(context, AdoptScheme.NEXT_SIBLING, other);
1377
1459
  }
1460
+
1461
+ /**
1462
+ * call-seq:
1463
+ * process_xincludes(options)
1464
+ *
1465
+ * Loads and substitutes all xinclude elements below the node. The
1466
+ * parser context will be initialized with +options+.
1467
+ *
1468
+ */
1469
+ @JRubyMethod(visibility=Visibility.PRIVATE)
1470
+ public IRubyObject process_xincludes(ThreadContext context, IRubyObject options) {
1471
+ XmlDocument xmlDocument = (XmlDocument)document(context);
1472
+ RubyArray errors = (RubyArray)xmlDocument.getInstanceVariable("@errors");
1473
+ while(errors.getLength() > 0) {
1474
+ XmlSyntaxError error = (XmlSyntaxError)errors.shift(context);
1475
+ if (error.toString().contains("Include operation failed")) {
1476
+ throw new RaiseException(error);
1477
+ }
1478
+ }
1479
+ return this;
1480
+ }
1481
+
1378
1482
  }