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 }