View Javadoc

1   /*
2    * Copyright 2005 John G. Wilson
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *     http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   *
16   */
17  
18  package groovy.util.slurpersupport;
19  
20  import groovy.lang.Buildable;
21  import groovy.lang.Closure;
22  import groovy.lang.GroovyObject;
23  import groovy.lang.Writable;
24  
25  import java.io.IOException;
26  import java.io.Writer;
27  import java.util.HashMap;
28  import java.util.Iterator;
29  import java.util.LinkedList;
30  import java.util.List;
31  import java.util.Map;
32  import java.util.Stack;
33  
34  
35  /***
36   * @author John Wilson
37   *
38   */
39  
40  public class Node implements Writable {
41    private final String name;
42    private final Map attributes;
43    private final Map attributeNamespaces;
44    private final String namespaceURI;
45    private final List children = new LinkedList();
46    private final Stack replacementNodeStack = new Stack();
47    
48    public Node(final Node parent, final String name, final Map attributes, final Map attributeNamespaces, final String namespaceURI) {
49      this.name = name;
50      this.attributes = attributes;
51      this.attributeNamespaces = attributeNamespaces;
52      this.namespaceURI = namespaceURI;
53    }
54    
55    public String name() {
56      return this.name;
57    }
58    
59    public String namespaceURI() {
60      return this.namespaceURI;
61    }
62    
63    public Map attributes() {
64      return this.attributes;
65    }
66  
67    public List children() {
68      return this.children;
69    }
70  
71    public void addChild(final Object child) {
72      this.children.add(child);
73    }
74    
75    public void replaceNode(final Closure replacementClosure, final GPathResult result) {
76        this.replacementNodeStack.push(new ReplacementNode() {
77                                            public void build(final GroovyObject builder, final Map namespaceMap, final Map namespaceTagHints) {
78                                                final Closure c = (Closure)replacementClosure.clone();
79                                                
80                                                    Node.this.replacementNodeStack.pop(); // disable the replacement whilst the closure is being executed 
81                                                    c.setDelegate(builder);
82                                                    c.call(new Object[]{result});
83                                                    Node.this.replacementNodeStack.push(this);
84                                                }
85                                            });
86    }
87    
88  
89    protected void replaceBody(final Object newValue) {
90        this.children.clear();
91        this.children.add(newValue);
92    }
93  
94    protected void appendNode(final Object newValue, final GPathResult result) {
95        if (newValue instanceof Closure) {
96            this.children.add(new ReplacementNode() {
97                                public void build(final GroovyObject builder, final Map namespaceMap, final Map namespaceTagHints) {
98                                    final Closure c = (Closure)((Closure)newValue).clone();
99                                    
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 }