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 033 034 /** 035 * @author John Wilson 036 * 037 */ 038 039 public class Node implements Writable { 040 private final String name; 041 private final Map attributes; 042 private final Map attributeNamespaces; 043 private final String namespaceURI; 044 private List children = new LinkedList(); 045 046 public Node(final Node parent, final String name, final Map attributes, final Map attributeNamespaces, final String namespaceURI) { 047 this.name = name; 048 this.attributes = attributes; 049 this.attributeNamespaces = attributeNamespaces; 050 this.namespaceURI = namespaceURI; 051 } 052 053 public String name() { 054 return this.name; 055 } 056 057 public String namespaceURI() { 058 return this.namespaceURI; 059 } 060 061 public Map attributes() { 062 return this.attributes; 063 } 064 065 public List children() { 066 return this.children(); 067 } 068 069 public void addChild(final Object child) { 070 this.children.add(child); 071 } 072 073 /* (non-Javadoc) 074 * @see org.codehaus.groovy.sandbox.util.slurpersupport.Node#text() 075 */ 076 public String text() { 077 final StringBuffer buff = new StringBuffer(); 078 final Iterator iter = this.children.iterator(); 079 080 while (iter.hasNext()) { 081 buff.append(iter.next()); 082 } 083 084 return buff.toString(); 085 } 086 087 /* (non-Javadoc) 088 * @see org.codehaus.groovy.sandbox.util.slurpersupport.Node#childNodes() 089 */ 090 091 public Iterator childNodes() { 092 return new Iterator() { 093 private final Iterator iter = Node.this.children.iterator(); 094 private Object nextElementNodes = getNextElementNodes(); 095 096 public boolean hasNext() { 097 return this.nextElementNodes != null; 098 } 099 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 }