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  
33  
34  /***
35   * @author John Wilson
36   *
37   */
38  
39  public class Node implements Writable {
40    private final String name;
41    private final Map attributes;
42    private final Map attributeNamespaces;
43    private final String namespaceURI;
44    private List children = new LinkedList();
45    
46    public Node(final Node parent, final String name, final Map attributes, final Map attributeNamespaces, final String namespaceURI) {
47      this.name = name;
48      this.attributes = attributes;
49      this.attributeNamespaces = attributeNamespaces;
50      this.namespaceURI = namespaceURI;
51    }
52    
53    public String name() {
54      return this.name;
55    }
56    
57    public String namespaceURI() {
58      return this.namespaceURI;
59    }
60    
61    public Map attributes() {
62      return this.attributes;
63    }
64  
65    public List children() {
66      return this.children;
67    }
68  
69    public void addChild(final Object child) {
70      this.children.add(child);
71    }
72  
73    /* (non-Javadoc)
74     * @see org.codehaus.groovy.sandbox.util.slurpersupport.Node#text()
75     */
76    public String text() {
77    final StringBuffer buff = new StringBuffer();
78    final Iterator iter = this.children.iterator();
79    
80      while (iter.hasNext()) {
81      final Object child = iter.next();
82      
83          if (child instanceof Node) {
84              buff.append(((Node)child).text());
85          } else {
86              buff.append(child);
87          }
88      }
89    
90      return buff.toString();
91    }
92  
93    /* (non-Javadoc)
94     * @see org.codehaus.groovy.sandbox.util.slurpersupport.Node#childNodes()
95     */
96    
97    public Iterator childNodes() {
98      return new Iterator() {
99        private final Iterator iter = Node.this.children.iterator();
100       private Object nextElementNodes = getNextElementNodes();
101       
102       public boolean hasNext() {
103         return this.nextElementNodes != null;
104       }
105       
106       public Object next() {
107         try {
108           return this.nextElementNodes;
109         } finally {
110           this.nextElementNodes = getNextElementNodes();
111         }
112       }
113       
114       public void remove() {
115         throw new UnsupportedOperationException();
116       }
117 
118       private Object getNextElementNodes() {
119         while (iter.hasNext()) {
120         final Object node = iter.next();
121         
122           if (node instanceof Node) {
123             return node;
124           }
125         }
126         
127         return null;
128       }
129     };
130   }
131 
132   /* (non-Javadoc)
133    * @see org.codehaus.groovy.sandbox.util.slurpersupport.Node#writeTo(java.io.Writer)
134    */
135   public Writer writeTo(final Writer out) throws IOException {
136   final Iterator iter = this.children.iterator();
137   
138     while (iter.hasNext()) {
139     final Object child = iter.next();
140     
141       if (child instanceof Writable) {
142         ((Writable)child).writeTo(out);
143       } else {
144         out.write(child.toString());
145       }
146     }
147     
148     return out;
149   }
150   
151   public void build(final GroovyObject builder, final Map namespaceMap, final Map namespaceTagHints) {
152   final Closure rest = new Closure(null) {
153                           public Object doCall(final Object o) {
154                             buildChildren(builder, namespaceMap, namespaceTagHints);
155                             
156                             return null;
157                           }
158                         };
159     
160     if (this.namespaceURI.length() == 0 && this.attributeNamespaces.isEmpty()) {
161       builder.invokeMethod(this.name, new Object[]{this.attributes, rest});
162     } else {
163       final List newTags = new LinkedList();
164       builder.getProperty("mkp");
165       final List namespaces = (List)builder.invokeMethod("getNamespaces", new Object[]{});
166       
167       final Map current = (Map)namespaces.get(0);
168       final Map pending = (Map)namespaces.get(1);
169       
170       if (this.attributeNamespaces.isEmpty()) {     
171         builder.getProperty(getTagFor(this.namespaceURI, current, pending, namespaceMap, namespaceTagHints, newTags, builder));
172         builder.invokeMethod(this.name, new Object[]{this.attributes, rest});
173       } else {
174       final Map attributesWithNamespaces = new HashMap(this.attributes);
175       final Iterator attrs = this.attributes.keySet().iterator();
176         
177         while (attrs.hasNext()) {
178         final Object key = attrs.next();
179         final Object attributeNamespaceURI = this.attributeNamespaces.get(key);
180           
181           if (attributeNamespaceURI != null) {
182             attributesWithNamespaces.put(getTagFor(attributeNamespaceURI, current, pending, namespaceMap, namespaceTagHints, newTags, builder) +
183                                          "$" + key, attributesWithNamespaces.remove(key));
184           }
185         }
186         
187         builder.getProperty(getTagFor(this.namespaceURI, current, pending, namespaceMap,namespaceTagHints,  newTags, builder));
188         builder.invokeMethod(this.name, new Object[]{attributesWithNamespaces, rest});
189       }
190       
191       // remove the new tags we had to define for this element
192       if (!newTags.isEmpty()) {
193       final Iterator iter = newTags.iterator();
194       
195         do {
196           pending.remove(iter.next());
197         } while (iter.hasNext());
198       }
199       
200     }   
201   }
202   
203   private static String getTagFor(final Object namespaceURI, final Map current,
204                                   final Map pending, final Map local, final Map tagHints,
205                                   final List newTags, final GroovyObject builder) {
206   String tag = findNamespaceTag(pending, namespaceURI); // look in the namespaces whose declaration has already been emitted
207     
208     if (tag == null) {
209       tag = findNamespaceTag(current, namespaceURI);  // look in the namespaces who will be declared at the next element
210       
211       if (tag == null) {
212         // we have to declare the namespace - choose a tag
213         tag = findNamespaceTag(local, namespaceURI);  // If the namespace has been decared in the GPath expression use that tag
214         
215         if (tag == null || tag.length() == 0) {
216           tag = findNamespaceTag(tagHints, namespaceURI);  // If the namespace has been used in the parse documant use that tag         
217         }
218         
219         if (tag == null || tag.length() == 0) { // otherwise make up a new tag and check it has not been used before
220         int suffix = 0;
221         
222           do {
223             final String posibleTag = "tag" + suffix++;
224             
225             if (!pending.containsKey(posibleTag) && !current.containsKey(posibleTag) && !local.containsKey(posibleTag)) {
226               tag = posibleTag;
227             }
228           } while (tag == null);
229         }
230         
231         final Map newNamespace = new HashMap();
232         newNamespace.put(tag, namespaceURI);
233         builder.getProperty("mkp");
234         builder.invokeMethod("declareNamespace", new Object[]{newNamespace});
235         newTags.add(tag);
236       }
237     }
238     
239     return tag;
240   }
241   
242   private static String findNamespaceTag(final Map tagMap, final Object namespaceURI) {
243     if (tagMap.containsValue(namespaceURI)) {
244     final Iterator entries = tagMap.entrySet().iterator();
245       
246       while (entries.hasNext()) {
247         final Map.Entry entry = (Map.Entry)entries.next();
248         
249         if (namespaceURI.equals(entry.getValue())) {
250           return (String)entry.getKey();
251         }
252       }
253     }
254     
255     return null;
256   }
257   
258   private void buildChildren(final GroovyObject builder, final Map namespaceMap, final Map namespaceTagHints) {
259   final Iterator iter = this.children.iterator();
260   
261     while (iter.hasNext()) {
262     final Object child = iter.next();
263     
264       if (child instanceof Node) {
265         ((Node)child).build(builder, namespaceMap, namespaceTagHints);
266       } else if (child instanceof Buildable) {
267         ((Buildable)child).build(builder);
268       } else {
269         builder.getProperty("mkp");
270         builder.invokeMethod("yield", new Object[]{child});
271       }
272     }
273   }
274 }