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 }