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: * @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 final List children = new LinkedList();
045: private final Stack replacementNodeStack = new Stack();
046:
047: public Node(final Node parent, final String name,
048: final Map attributes, final Map attributeNamespaces,
049: final String namespaceURI) {
050: this .name = name;
051: this .attributes = attributes;
052: this .attributeNamespaces = attributeNamespaces;
053: this .namespaceURI = namespaceURI;
054: }
055:
056: public String name() {
057: return this .name;
058: }
059:
060: public String namespaceURI() {
061: return this .namespaceURI;
062: }
063:
064: public Map attributes() {
065: return this .attributes;
066: }
067:
068: public List children() {
069: return this .children;
070: }
071:
072: public void addChild(final Object child) {
073: this .children.add(child);
074: }
075:
076: public void replaceNode(final Closure replacementClosure,
077: final GPathResult result) {
078: this .replacementNodeStack.push(new ReplacementNode() {
079: public void build(final GroovyObject builder,
080: final Map namespaceMap, final Map namespaceTagHints) {
081: final Closure c = (Closure) replacementClosure.clone();
082:
083: Node.this .replacementNodeStack.pop(); // disable the replacement whilst the closure is being executed
084: c.setDelegate(builder);
085: c.call(new Object[] { result });
086: Node.this .replacementNodeStack.push(this );
087: }
088: });
089: }
090:
091: protected void replaceBody(final Object newValue) {
092: this .children.clear();
093: this .children.add(newValue);
094: }
095:
096: protected void appendNode(final Object newValue,
097: final GPathResult result) {
098: if (newValue instanceof Closure) {
099: this .children.add(new ReplacementNode() {
100: public void build(final GroovyObject builder,
101: final Map namespaceMap,
102: final Map namespaceTagHints) {
103: final Closure c = (Closure) ((Closure) newValue)
104: .clone();
105:
106: c.setDelegate(builder);
107: c.call(new Object[] { result });
108: }
109: });
110: } else {
111: this .children.add(newValue);
112: }
113: }
114:
115: /* (non-Javadoc)
116: * @see org.codehaus.groovy.sandbox.util.slurpersupport.Node#text()
117: */
118: public String text() {
119: final StringBuffer buff = new StringBuffer();
120: final Iterator iter = this .children.iterator();
121:
122: while (iter.hasNext()) {
123: final Object child = iter.next();
124:
125: if (child instanceof Node) {
126: buff.append(((Node) child).text());
127: } else {
128: buff.append(child);
129: }
130: }
131:
132: return buff.toString();
133: }
134:
135: /* (non-Javadoc)
136: * @see org.codehaus.groovy.sandbox.util.slurpersupport.Node#childNodes()
137: */
138:
139: public Iterator childNodes() {
140: return new Iterator() {
141: private final Iterator iter = Node.this .children.iterator();
142: private Object nextElementNodes = getNextElementNodes();
143:
144: public boolean hasNext() {
145: return this .nextElementNodes != null;
146: }
147:
148: public Object next() {
149: try {
150: return this .nextElementNodes;
151: } finally {
152: this .nextElementNodes = getNextElementNodes();
153: }
154: }
155:
156: public void remove() {
157: throw new UnsupportedOperationException();
158: }
159:
160: private Object getNextElementNodes() {
161: while (iter.hasNext()) {
162: final Object node = iter.next();
163:
164: if (node instanceof Node) {
165: return node;
166: }
167: }
168:
169: return null;
170: }
171: };
172: }
173:
174: /* (non-Javadoc)
175: * @see org.codehaus.groovy.sandbox.util.slurpersupport.Node#writeTo(java.io.Writer)
176: */
177: public Writer writeTo(final Writer out) throws IOException {
178: if (this .replacementNodeStack.empty()) {
179: final Iterator iter = this .children.iterator();
180:
181: while (iter.hasNext()) {
182: final Object child = iter.next();
183:
184: if (child instanceof Writable) {
185: ((Writable) child).writeTo(out);
186: } else {
187: out.write(child.toString());
188: }
189: }
190:
191: return out;
192:
193: } else {
194: return ((Writable) this .replacementNodeStack.peek())
195: .writeTo(out);
196: }
197: }
198:
199: public void build(final GroovyObject builder,
200: final Map namespaceMap, final Map namespaceTagHints) {
201: if (this .replacementNodeStack.empty()) {
202: final Closure rest = new Closure(null) {
203: public Object doCall(final Object o) {
204: buildChildren(builder, namespaceMap,
205: namespaceTagHints);
206:
207: return null;
208: }
209: };
210:
211: if (this .namespaceURI.length() == 0
212: && this .attributeNamespaces.isEmpty()) {
213: builder.invokeMethod(this .name, new Object[] {
214: this .attributes, rest });
215: } else {
216: final List newTags = new LinkedList();
217: builder.getProperty("mkp");
218: final List namespaces = (List) builder.invokeMethod(
219: "getNamespaces", new Object[] {});
220:
221: final Map current = (Map) namespaces.get(0);
222: final Map pending = (Map) namespaces.get(1);
223:
224: if (this .attributeNamespaces.isEmpty()) {
225: builder.getProperty(getTagFor(this .namespaceURI,
226: current, pending, namespaceMap,
227: namespaceTagHints, newTags, builder));
228: builder.invokeMethod(this .name, new Object[] {
229: this .attributes, rest });
230: } else {
231: final Map attributesWithNamespaces = new HashMap(
232: this .attributes);
233: final Iterator attrs = this .attributes.keySet()
234: .iterator();
235:
236: while (attrs.hasNext()) {
237: final Object key = attrs.next();
238: final Object attributeNamespaceURI = this .attributeNamespaces
239: .get(key);
240:
241: if (attributeNamespaceURI != null) {
242: attributesWithNamespaces.put(
243: getTagFor(attributeNamespaceURI,
244: current, pending,
245: namespaceMap,
246: namespaceTagHints, newTags,
247: builder)
248: + "$" + key,
249: attributesWithNamespaces
250: .remove(key));
251: }
252: }
253:
254: builder.getProperty(getTagFor(this .namespaceURI,
255: current, pending, namespaceMap,
256: namespaceTagHints, newTags, builder));
257: builder.invokeMethod(this .name, new Object[] {
258: attributesWithNamespaces, rest });
259: }
260:
261: // remove the new tags we had to define for this element
262: if (!newTags.isEmpty()) {
263: final Iterator iter = newTags.iterator();
264:
265: do {
266: pending.remove(iter.next());
267: } while (iter.hasNext());
268: }
269: }
270: } else {
271: ((ReplacementNode) this .replacementNodeStack.peek()).build(
272: builder, namespaceMap, namespaceTagHints);
273: }
274: }
275:
276: private static String getTagFor(final Object namespaceURI,
277: final Map current, final Map pending, final Map local,
278: final Map tagHints, final List newTags,
279: final GroovyObject builder) {
280: String tag = findNamespaceTag(pending, namespaceURI); // look in the namespaces whose declaration has already been emitted
281:
282: if (tag == null) {
283: tag = findNamespaceTag(current, namespaceURI); // look in the namespaces who will be declared at the next element
284:
285: if (tag == null) {
286: // we have to declare the namespace - choose a tag
287: tag = findNamespaceTag(local, namespaceURI); // If the namespace has been decared in the GPath expression use that tag
288:
289: if (tag == null || tag.length() == 0) {
290: tag = findNamespaceTag(tagHints, namespaceURI); // If the namespace has been used in the parse documant use that tag
291: }
292:
293: if (tag == null || tag.length() == 0) { // otherwise make up a new tag and check it has not been used before
294: int suffix = 0;
295:
296: do {
297: final String posibleTag = "tag" + suffix++;
298:
299: if (!pending.containsKey(posibleTag)
300: && !current.containsKey(posibleTag)
301: && !local.containsKey(posibleTag)) {
302: tag = posibleTag;
303: }
304: } while (tag == null);
305: }
306:
307: final Map newNamespace = new HashMap();
308: newNamespace.put(tag, namespaceURI);
309: builder.getProperty("mkp");
310: builder.invokeMethod("declareNamespace",
311: new Object[] { newNamespace });
312: newTags.add(tag);
313: }
314: }
315:
316: return tag;
317: }
318:
319: private static String findNamespaceTag(final Map tagMap,
320: final Object namespaceURI) {
321: if (tagMap.containsValue(namespaceURI)) {
322: final Iterator entries = tagMap.entrySet().iterator();
323:
324: while (entries.hasNext()) {
325: final Map.Entry entry = (Map.Entry) entries.next();
326:
327: if (namespaceURI.equals(entry.getValue())) {
328: return (String) entry.getKey();
329: }
330: }
331: }
332:
333: return null;
334: }
335:
336: private void buildChildren(final GroovyObject builder,
337: final Map namespaceMap, final Map namespaceTagHints) {
338: final Iterator iter = this .children.iterator();
339:
340: while (iter.hasNext()) {
341: final Object child = iter.next();
342:
343: if (child instanceof Node) {
344: ((Node) child).build(builder, namespaceMap,
345: namespaceTagHints);
346: } else if (child instanceof Buildable) {
347: ((Buildable) child).build(builder);
348: } else {
349: builder.getProperty("mkp");
350: builder.invokeMethod("yield", new Object[] { child });
351: }
352: }
353: }
354: }
|