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    }