-
-
Notifications
You must be signed in to change notification settings - Fork 897
/
XmlDocumentFragment.java
161 lines (141 loc) · 5.32 KB
/
XmlDocumentFragment.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
package nokogiri;
import static nokogiri.internals.NokogiriHelpers.getLocalNameForNamespace;
import static nokogiri.internals.NokogiriHelpers.getNokogiriClass;
import static nokogiri.internals.NokogiriHelpers.getPrefix;
import static nokogiri.internals.NokogiriHelpers.isNamespace;
import static nokogiri.internals.NokogiriHelpers.rubyStringToString;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.jruby.Ruby;
import org.jruby.RubyArray;
import org.jruby.RubyClass;
import org.jruby.RubyString;
import org.jruby.anno.JRubyClass;
import org.jruby.anno.JRubyMethod;
import org.jruby.runtime.Helpers;
import org.jruby.runtime.ThreadContext;
import org.jruby.runtime.builtin.IRubyObject;
import org.jruby.util.ByteList;
import org.w3c.dom.Attr;
import org.w3c.dom.NamedNodeMap;
/**
* Class for Nokogiri::XML::DocumentFragment
*
* @author sergio
* @author Yoko Harada <yokolet@gmail.com>
*/
@JRubyClass(name = "Nokogiri::XML::DocumentFragment", parent = "Nokogiri::XML::Node")
public class XmlDocumentFragment extends XmlNode
{
public
XmlDocumentFragment(Ruby ruby)
{
this(ruby, getNokogiriClass(ruby, "Nokogiri::XML::DocumentFragment"));
}
public
XmlDocumentFragment(Ruby ruby, RubyClass klazz)
{
super(ruby, klazz);
}
@JRubyMethod(name = "new", meta = true, required = 1, optional = 2)
public static IRubyObject
rbNew(ThreadContext context, IRubyObject cls, IRubyObject[] args)
{
if (args.length < 1) {
throw context.runtime.newArgumentError(args.length, 1);
}
if (!(args[0] instanceof XmlDocument)) {
throw context.runtime.newArgumentError("first parameter must be a Nokogiri::XML::Document instance");
}
XmlDocument doc = (XmlDocument) args[0];
// make wellformed fragment, ignore invalid namespace, or add appropriate namespace to parse
if (args.length > 1 && args[1] instanceof RubyString) {
final RubyString arg1 = (RubyString) args[1];
if (XmlDocumentFragment.isTag(arg1)) {
args[1] = RubyString.newString(context.runtime, addNamespaceDeclIfNeeded(doc, rubyStringToString(arg1)));
}
}
XmlDocumentFragment fragment = (XmlDocumentFragment) NokogiriService.XML_DOCUMENT_FRAGMENT_ALLOCATOR.allocate(
context.runtime, (RubyClass)cls);
fragment.setDocument(context, doc);
fragment.setNode(context.runtime, doc.getDocument().createDocumentFragment());
Helpers.invoke(context, fragment, "initialize", args);
return fragment;
}
private static final ByteList TAG_BEG = ByteList.create("<");
private static final ByteList TAG_END = ByteList.create(">");
private static boolean
isTag(final RubyString str)
{
return str.getByteList().startsWith(TAG_BEG) && str.getByteList().endsWith(TAG_END);
}
private static boolean
isNamespaceDefined(String qName, NamedNodeMap nodeMap)
{
if (isNamespace(qName.intern())) { return true; }
for (int i = 0; i < nodeMap.getLength(); i++) {
Attr attr = (Attr)nodeMap.item(i);
if (isNamespace(attr.getNodeName())) {
String localPart = getLocalNameForNamespace(attr.getNodeName(), null);
if (getPrefix(qName).equals(localPart)) {
return true;
}
}
}
return false;
}
private static final Pattern QNAME_RE = Pattern.compile("[^</:>\\s]+:[^</:>=\\s]+");
private static final Pattern START_TAG_RE = Pattern.compile("<[^</>]+>");
private static String
addNamespaceDeclIfNeeded(XmlDocument doc, String tags)
{
if (doc.getDocument() == null) { return tags; }
if (doc.getDocument().getDocumentElement() == null) { return tags; }
Matcher matcher = START_TAG_RE.matcher(tags);
Map<CharSequence, CharSequence> rewriteTable = null;
while (matcher.find()) {
String start_tag = matcher.group();
Matcher matcher2 = QNAME_RE.matcher(start_tag);
while (matcher2.find()) {
String qName = matcher2.group();
NamedNodeMap nodeMap = doc.getDocument().getDocumentElement().getAttributes();
if (isNamespaceDefined(qName, nodeMap)) {
CharSequence namespaceDecl = getNamespaceDecl(getPrefix(qName), nodeMap);
if (namespaceDecl != null) {
if (rewriteTable == null) { rewriteTable = new HashMap(8, 1); }
StringBuilder str = new StringBuilder(qName.length() + namespaceDecl.length() + 3);
String key = str.append('<').append(qName).append('>').toString();
str.setCharAt(key.length() - 1, ' '); // (last) '>' -> ' '
rewriteTable.put(key, str.append(namespaceDecl).append('>'));
}
}
}
}
if (rewriteTable != null) {
for (Map.Entry<CharSequence, CharSequence> e : rewriteTable.entrySet()) {
tags = tags.replace(e.getKey(), e.getValue());
}
}
return tags;
}
private static CharSequence
getNamespaceDecl(final String prefix, NamedNodeMap nodeMap)
{
for (int i = 0; i < nodeMap.getLength(); i++) {
Attr attr = (Attr) nodeMap.item(i);
if (prefix.equals(attr.getLocalName())) {
return new StringBuilder().
append(attr.getName()).append('=').append('"').append(attr.getValue()).append('"');
}
}
return null;
}
@Override
public void
relink_namespace(ThreadContext context)
{
relink_namespace(context, getChildren());
}
}