001 /*
002 * Copyright 2005 John G. Wilson
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 *
016 */
017
018 package groovy.util.slurpersupport;
019
020 import groovy.lang.Buildable;
021 import groovy.lang.Closure;
022 import groovy.lang.GroovyObject;
023 import groovy.lang.Writable;
024
025 import java.io.IOException;
026 import java.io.Writer;
027 import java.util.HashMap;
028 import java.util.Iterator;
029 import java.util.LinkedList;
030 import java.util.List;
031 import java.util.Map;
032 import java.util.Stack;
033
034
035 /**
036 * @author John Wilson
037 *
038 */
039
040 public class Node implements Writable {
041 private final String name;
042 private final Map attributes;
043 private final Map attributeNamespaces;
044 private final String namespaceURI;
045 private final List children = new LinkedList();
046 private final Stack replacementNodeStack = new Stack();
047
048 public Node(final Node parent, final String name, final Map attributes, final Map attributeNamespaces, final String namespaceURI) {
049 this.name = name;
050 this.attributes = attributes;
051 this.attributeNamespaces = attributeNamespaces;
052 this.namespaceURI = namespaceURI;
053 }
054
055 public String name() {
056 return this.name;
057 }
058
059 public String namespaceURI() {
060 return this.namespaceURI;
061 }
062
063 public Map attributes() {
064 return this.attributes;
065 }
066
067 public List children() {
068 return this.children;
069 }
070
071 public void addChild(final Object child) {
072 this.children.add(child);
073 }
074
075 public void replaceNode(final Closure replacementClosure, final GPathResult result) {
076 this.replacementNodeStack.push(new ReplacementNode() {
077 public void build(final GroovyObject builder, final Map namespaceMap, final Map namespaceTagHints) {
078 final Closure c = (Closure)replacementClosure.clone();
079
080 Node.this.replacementNodeStack.pop(); // disable the replacement whilst the closure is being executed
081 c.setDelegate(builder);
082 c.call(new Object[]{result});
083 Node.this.replacementNodeStack.push(this);
084 }
085 });
086 }
087
088
089 protected void replaceBody(final Object newValue) {
090 this.children.clear();
091 this.children.add(newValue);
092 }
093
094 protected void appendNode(final Object newValue, final GPathResult result) {
095 if (newValue instanceof Closure) {
096 this.children.add(new ReplacementNode() {
097 public void build(final GroovyObject builder, final Map namespaceMap, final Map namespaceTagHints) {
098 final Closure c = (Closure)((Closure)newValue).clone();
099
100 c.setDelegate(builder);
101 c.call(new Object[]{result});
102 }
103 });
104 } else {
105 this.children.add(newValue);
106 }
107 }
108
109 /* (non-Javadoc)
110 * @see org.codehaus.groovy.sandbox.util.slurpersupport.Node#text()
111 */
112 public String text() {
113 final StringBuffer buff = new StringBuffer();
114 final Iterator iter = this.children.iterator();
115
116 while (iter.hasNext()) {
117 final Object child = iter.next();
118
119 if (child instanceof Node) {
120 buff.append(((Node)child).text());
121 } else {
122 buff.append(child);
123 }
124 }
125
126 return buff.toString();
127 }
128
129 /* (non-Javadoc)
130 * @see org.codehaus.groovy.sandbox.util.slurpersupport.Node#childNodes()
131 */
132
133 public Iterator childNodes() {
134 return new Iterator() {
135 private final Iterator iter = Node.this.children.iterator();
136 private Object nextElementNodes = getNextElementNodes();
137
138 public boolean hasNext() {
139 return this.nextElementNodes != null;
140 }
141
142 public Object next() {
143 try {
144 return this.nextElementNodes;
145 } finally {
146 this.nextElementNodes = getNextElementNodes();
147 }
148 }
149
150 public void remove() {
151 throw new UnsupportedOperationException();
152 }
153
154 private Object getNextElementNodes() {
155 while (iter.hasNext()) {
156 final Object node = iter.next();
157
158 if (node instanceof Node) {
159 return node;
160 }
161 }
162
163 return null;
164 }
165 };
166 }
167
168 /* (non-Javadoc)
169 * @see org.codehaus.groovy.sandbox.util.slurpersupport.Node#writeTo(java.io.Writer)
170 */
171 public Writer writeTo(final Writer out) throws IOException {
172 if (this.replacementNodeStack.empty()) {
173 final Iterator iter = this.children.iterator();
174
175 while (iter.hasNext()) {
176 final Object child = iter.next();
177
178 if (child instanceof Writable) {
179 ((Writable)child).writeTo(out);
180 } else {
181 out.write(child.toString());
182 }
183 }
184
185 return out;
186
187 } else {
188 return ((Writable)this.replacementNodeStack.peek()).writeTo(out);
189 }
190 }
191
192 public void build(final GroovyObject builder, final Map namespaceMap, final Map namespaceTagHints) {
193 if (this.replacementNodeStack.empty()) {
194 final Closure rest = new Closure(null) {
195 public Object doCall(final Object o) {
196 buildChildren(builder, namespaceMap, namespaceTagHints);
197
198 return null;
199 }
200 };
201
202 if (this.namespaceURI.length() == 0 && this.attributeNamespaces.isEmpty()) {
203 builder.invokeMethod(this.name, new Object[]{this.attributes, rest});
204 } else {
205 final List newTags = new LinkedList();
206 builder.getProperty("mkp");
207 final List namespaces = (List)builder.invokeMethod("getNamespaces", new Object[]{});
208
209 final Map current = (Map)namespaces.get(0);
210 final Map pending = (Map)namespaces.get(1);
211
212 if (this.attributeNamespaces.isEmpty()) {
213 builder.getProperty(getTagFor(this.namespaceURI, current, pending, namespaceMap, namespaceTagHints, newTags, builder));
214 builder.invokeMethod(this.name, new Object[]{this.attributes, rest});
215 } else {
216 final Map attributesWithNamespaces = new HashMap(this.attributes);
217 final Iterator attrs = this.attributes.keySet().iterator();
218
219 while (attrs.hasNext()) {
220 final Object key = attrs.next();
221 final Object attributeNamespaceURI = this.attributeNamespaces.get(key);
222
223 if (attributeNamespaceURI != null) {
224 attributesWithNamespaces.put(getTagFor(attributeNamespaceURI, current, pending, namespaceMap, namespaceTagHints, newTags, builder) +
225 "$" + key, attributesWithNamespaces.remove(key));
226 }
227 }
228
229 builder.getProperty(getTagFor(this.namespaceURI, current, pending, namespaceMap,namespaceTagHints, newTags, builder));
230 builder.invokeMethod(this.name, new Object[]{attributesWithNamespaces, rest});
231 }
232
233 // remove the new tags we had to define for this element
234 if (!newTags.isEmpty()) {
235 final Iterator iter = newTags.iterator();
236
237 do {
238 pending.remove(iter.next());
239 } while (iter.hasNext());
240 }
241 }
242 } else {
243 ((ReplacementNode)this.replacementNodeStack.peek()).build(builder, namespaceMap, namespaceTagHints);
244 }
245 }
246
247 private static String getTagFor(final Object namespaceURI, final Map current,
248 final Map pending, final Map local, final Map tagHints,
249 final List newTags, final GroovyObject builder) {
250 String tag = findNamespaceTag(pending, namespaceURI); // look in the namespaces whose declaration has already been emitted
251
252 if (tag == null) {
253 tag = findNamespaceTag(current, namespaceURI); // look in the namespaces who will be declared at the next element
254
255 if (tag == null) {
256 // we have to declare the namespace - choose a tag
257 tag = findNamespaceTag(local, namespaceURI); // If the namespace has been decared in the GPath expression use that tag
258
259 if (tag == null || tag.length() == 0) {
260 tag = findNamespaceTag(tagHints, namespaceURI); // If the namespace has been used in the parse documant use that tag
261 }
262
263 if (tag == null || tag.length() == 0) { // otherwise make up a new tag and check it has not been used before
264 int suffix = 0;
265
266 do {
267 final String posibleTag = "tag" + suffix++;
268
269 if (!pending.containsKey(posibleTag) && !current.containsKey(posibleTag) && !local.containsKey(posibleTag)) {
270 tag = posibleTag;
271 }
272 } while (tag == null);
273 }
274
275 final Map newNamespace = new HashMap();
276 newNamespace.put(tag, namespaceURI);
277 builder.getProperty("mkp");
278 builder.invokeMethod("declareNamespace", new Object[]{newNamespace});
279 newTags.add(tag);
280 }
281 }
282
283 return tag;
284 }
285
286 private static String findNamespaceTag(final Map tagMap, final Object namespaceURI) {
287 if (tagMap.containsValue(namespaceURI)) {
288 final Iterator entries = tagMap.entrySet().iterator();
289
290 while (entries.hasNext()) {
291 final Map.Entry entry = (Map.Entry)entries.next();
292
293 if (namespaceURI.equals(entry.getValue())) {
294 return (String)entry.getKey();
295 }
296 }
297 }
298
299 return null;
300 }
301
302 private void buildChildren(final GroovyObject builder, final Map namespaceMap, final Map namespaceTagHints) {
303 final Iterator iter = this.children.iterator();
304
305 while (iter.hasNext()) {
306 final Object child = iter.next();
307
308 if (child instanceof Node) {
309 ((Node)child).build(builder, namespaceMap, namespaceTagHints);
310 } else if (child instanceof Buildable) {
311 ((Buildable)child).build(builder);
312 } else {
313 builder.getProperty("mkp");
314 builder.invokeMethod("yield", new Object[]{child});
315 }
316 }
317 }
318 }