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