001: package net.sf.saxon.pull;
002:
003: import net.sf.saxon.om.*;
004: import net.sf.saxon.trans.XPathException;
005:
006: import java.util.ArrayList;
007: import java.util.Iterator;
008: import java.util.List;
009:
010: /**
011: * PullNamespaceReducer is a PullFilter responsible for removing duplicate namespace
012: * declarations. It also performs namespace fixup: that is, it ensures that the
013: * namespaces used in element and attribute names are all declared.
014: *
015: * <p>This class is derived from, and contains much common code with, the NamespaceReducer
016: * in the push pipeline. (In the push version, however, namespace fixup is not
017: * performed by the NamespaceReducer, but by the ComplexContentOutputter).</p>
018: * @see net.sf.saxon.event.NamespaceReducer
019: */
020:
021: public class PullNamespaceReducer extends PullFilter implements
022: NamespaceResolver {
023:
024: // We keep track of namespaces to avoid outputting duplicate declarations. The namespaces
025: // vector holds a list of all namespaces currently declared (organised as integer namespace codes).
026: // The countStack contains an entry for each element currently open; the
027: // value on the countStack is an Integer giving the number of namespaces added to the main
028: // namespace stack by that element.
029:
030: private int[] allNamespaces = new int[50]; // all namespace codes currently declared
031: private int allNamespacesSize = 0; // all namespaces currently declared
032: private int[] countStack = new int[50];
033: private int depth = 0;
034: private int[] localNamespaces;
035: private int localNamespacesSize = 0;
036: private int nameCode; // the namecode of the current element
037:
038: private NamespaceDeclarations declaredNamespaces;
039: private AttributeCollection attributeCollection;
040:
041: // Creating an element does not automatically inherit the namespaces of the containing element.
042: // TODO: disinheriting namespaces is not yet supported by the pull pipeline
043: //private boolean[] disinheritStack = new boolean[50];
044:
045: private int[] pendingUndeclarations = null;
046:
047: public PullNamespaceReducer(PullProvider base) {
048: super (base);
049: }
050:
051: /**
052: * next(): handle next event.
053: * The START_ELEMENT event removes redundant namespace declarations, and
054: * possibly adds an xmlns="" undeclaration.
055: */
056:
057: public int next() throws XPathException {
058:
059: int event = super .next();
060:
061: switch (event) {
062: case START_ELEMENT:
063: startElement();
064: break;
065: case END_ELEMENT:
066: endElement();
067: break;
068: case PROCESSING_INSTRUCTION:
069: case ATTRIBUTE:
070: case NAMESPACE:
071: nameCode = super .getNameCode();
072: break;
073: default:
074: nameCode = -1;
075: }
076: return event;
077: }
078:
079: private void startElement() throws XPathException {
080:
081: // If the parent element specified inherit=no, keep a list of namespaces that need to be
082: // undeclared
083:
084: // if (depth>0 && disinheritStack[depth-1]) {
085: // pendingUndeclarations = new int[namespacesSize];
086: // System.arraycopy(namespaces, 0, pendingUndeclarations, 0, namespacesSize);
087: // } else {
088: // pendingUndeclarations = null;
089: // }
090:
091: // Record the current height of the namespace list so it can be reset at endElement time
092:
093: countStack[depth] = 0;
094: //disinheritStack[depth] = (properties & ReceiverOptions.DISINHERIT_NAMESPACES) != 0;
095: if (++depth >= countStack.length) {
096: int[] newstack = new int[depth * 2];
097: System.arraycopy(countStack, 0, newstack, 0, depth);
098: //boolean[] disStack2 = new boolean[depth*2];
099: //System.arraycopy(disinheritStack, 0, disStack2, 0, depth);
100: countStack = newstack;
101: //disinheritStack = disStack2;
102: }
103:
104: // Get the list of namespaces associated with this element
105:
106: NamespaceDeclarations declarations = super
107: .getNamespaceDeclarations();
108: localNamespaces = declarations.getNamespaceCodes(nsBuffer);
109: localNamespacesSize = 0;
110: for (int i = 0; i < localNamespaces.length; i++) {
111: if (localNamespaces[i] == -1) {
112: break;
113: } else {
114: if (isNeeded(localNamespaces[i])) {
115: addGlobalNamespace(localNamespaces[i]);
116: countStack[depth - 1]++;
117: localNamespaces[localNamespacesSize++] = localNamespaces[i];
118: }
119: }
120: }
121:
122: // Namespace fixup: ensure that the element namespace is output
123:
124: nameCode = checkProposedPrefix(super .getNameCode(), 0);
125:
126: // int elementNS = getNamePool().allocateNamespaceCode(getNameCode());
127: // if (isNeeded(elementNS)) {
128: // appendNamespace(elementNS);
129: // }
130:
131: // Namespace fixup: ensure that all namespaces used in attribute names are declared
132:
133: attributeCollection = super .getAttributes();
134: boolean modified = false;
135: for (int i = 0; i < attributeCollection.getLength(); i++) {
136: int nc = attributeCollection.getNameCode(i);
137: if ((nc & ~NamePool.FP_MASK) != 0) {
138: // Only need to do checking for an attribute that's namespaced
139: int newnc = checkProposedPrefix(nc, i + 1);
140: if (nc != newnc) {
141: if (!modified) {
142: attributeCollection = copyAttributeCollection(attributeCollection);
143: modified = true;
144: }
145: ((AttributeCollectionImpl) attributeCollection)
146: .setAttribute(i, newnc, attributeCollection
147: .getTypeAnnotation(i),
148: attributeCollection.getValue(i),
149: attributeCollection
150: .getLocationId(i),
151: attributeCollection
152: .getProperties(i));
153: }
154: }
155: }
156:
157: if (localNamespacesSize < localNamespaces.length) {
158: localNamespaces[localNamespacesSize] = -1; // add a terminator
159: }
160:
161: declaredNamespaces = new NamespaceDeclarationsImpl(
162: getNamePool(), localNamespaces);
163: countStack[depth - 1] = localNamespacesSize;
164: }
165:
166: private int[] nsBuffer = new int[20];
167:
168: private void addLocalNamespace(int nc) {
169: if (localNamespacesSize < localNamespaces.length) {
170: localNamespaces[localNamespacesSize++] = nc;
171: } else {
172: if (localNamespacesSize == 0) {
173: localNamespaces = new int[10];
174: } else {
175: int[] nc2 = new int[localNamespacesSize * 2];
176: System.arraycopy(localNamespaces, 0, nc2, 0,
177: localNamespacesSize);
178: localNamespaces = nc2;
179: localNamespaces[localNamespacesSize++] = nc;
180: }
181: }
182: addGlobalNamespace(nc);
183: }
184:
185: /**
186: * Determine whether a namespace declaration is needed
187: */
188:
189: private boolean isNeeded(int nscode) {
190: if (nscode == NamespaceConstant.XML_NAMESPACE_CODE) {
191: // Ignore the XML namespace
192: return false;
193: }
194:
195: // First cancel any pending undeclaration of this namespace prefix (there may be more than one)
196:
197: if (pendingUndeclarations != null) {
198: for (int p = 0; p < pendingUndeclarations.length; p++) {
199: if ((nscode >> 16) == (pendingUndeclarations[p] >> 16)) {
200: pendingUndeclarations[p] = -1;
201: //break;
202: }
203: }
204: }
205:
206: for (int i = allNamespacesSize - 1; i >= 0; i--) {
207: if (allNamespaces[i] == nscode) {
208: // it's a duplicate so we don't need it
209: return false;
210: }
211: if ((allNamespaces[i] >> 16) == (nscode >> 16)) {
212: // same prefix, different URI, so we do need it
213: return true;
214: }
215: }
216:
217: // we need it unless it's a redundant xmlns=""
218: return (nscode != NamespaceConstant.NULL_NAMESPACE_CODE);
219:
220: // startContent: Add any namespace undeclarations needed to stop
221: // namespaces being inherited from parent elements
222:
223: // if (pendingUndeclarations != null) {
224: // for (int i=0; i<pendingUndeclarations.length; i++) {
225: // int nscode1 = pendingUndeclarations[i];
226: // if (nscode1 != -1) {
227: // namespace(nscode1 & 0xffff0000, 0);
228: // // relies on the namespace() method to prevent duplicate undeclarations
229: // }
230: // }
231: // }
232: //pendingUndeclarations = null;
233: }
234:
235: /**
236: * Check that the prefix for an element or attribute is acceptable, allocating a substitute
237: * prefix if not. The prefix is acceptable unless a namespace declaration has been
238: * written that assignes this prefix to a different namespace URI. This method
239: * also checks that the element or attribute namespace has been declared, and declares it
240: * if not.
241: */
242:
243: private int checkProposedPrefix(int nameCode, int seq) {
244: NamePool namePool = getNamePool();
245: int nscode = namePool.getNamespaceCode(nameCode);
246: if (nscode == -1) {
247: // avoid calling allocate where possible, because it's synchronized
248: nscode = namePool.allocateNamespaceCode(nameCode);
249: }
250: int nsprefix = nscode >> 16;
251:
252: for (int i = allNamespacesSize - 1; i >= 0; i--) {
253: if (nsprefix == (allNamespaces[i] >> 16)) {
254: // same prefix
255: if ((nscode & 0xffff) == (allNamespaces[i] & 0xffff)) {
256: // same URI
257: return nameCode; // all is well
258: } else {
259: // same prefix is bound to a different URI. Action depends on whether the declaration
260: // is local to this element or at an outer level
261: if (i + localNamespacesSize >= allNamespacesSize) {
262: // the prefix is already defined locally, so allocate a new one
263: String prefix = getSubstitutePrefix(nscode, seq);
264:
265: int newNameCode = namePool.allocate(prefix,
266: namePool.getURI(nameCode), namePool
267: .getLocalName(nameCode));
268: int newNSCode = namePool
269: .allocateNamespaceCode(newNameCode);
270: addLocalNamespace(newNSCode);
271: return newNameCode;
272: } else {
273: // the prefix has been used on an outer level, but we can reuse it here
274: addLocalNamespace(nscode);
275: return nameCode;
276: }
277: }
278: }
279: }
280: // there is no declaration of this prefix: declare it now
281: if (nscode != NamespaceConstant.NULL_NAMESPACE_CODE) {
282: addLocalNamespace(nscode);
283: }
284: return nameCode;
285: }
286:
287: /**
288: * It is possible for a single output element to use the same prefix to refer to different
289: * namespaces. In this case we have to generate an alternative prefix for uniqueness. The
290: * one we generate is based on the sequential position of the element/attribute: this is
291: * designed to ensure both uniqueness (with a high probability) and repeatability
292: */
293:
294: private String getSubstitutePrefix(int nscode, int seq) {
295: String prefix = getNamePool()
296: .getPrefixFromNamespaceCode(nscode);
297: return prefix + '_' + seq;
298: }
299:
300: /**
301: * Add a namespace declaration to the stack
302: */
303:
304: private void addGlobalNamespace(int nscode) {
305: // expand the stack if necessary
306: if (allNamespacesSize + 1 >= allNamespaces.length) {
307: int[] newlist = new int[allNamespacesSize * 2];
308: System.arraycopy(allNamespaces, 0, newlist, 0,
309: allNamespacesSize);
310: allNamespaces = newlist;
311: }
312: allNamespaces[allNamespacesSize++] = nscode;
313: }
314:
315: /**
316: * Get the nameCode identifying the name of the current node. This method
317: * can be used after the {@link #START_ELEMENT}, {@link #PROCESSING_INSTRUCTION},
318: * {@link #ATTRIBUTE}, or {@link #NAMESPACE} events. With some PullProvider implementations,
319: * <b>but not this one</b>, it can also be used after {@link #END_ELEMENT}: a client that
320: * requires the information at that point (for example, to do serialization) should insert an
321: * {@link ElementNameTracker} into the pipeline.
322: * If called at other times, the result is undefined and may result in an IllegalStateException.
323: * If called when the current node is an unnamed namespace node (a node representing the default namespace)
324: * the returned value is -1.
325: *
326: * @return the nameCode. The nameCode can be used to obtain the prefix, local name,
327: * and namespace URI from the name pool.
328: */
329:
330: public int getNameCode() {
331: return nameCode;
332: }
333:
334: /**
335: * Get the attributes associated with the current element. This method must
336: * be called only after a START_ELEMENT event has been notified. The contents
337: * of the returned AttributeCollection are guaranteed to remain unchanged
338: * until the next START_ELEMENT event, but may be modified thereafter. The object
339: * should not be modified by the client.
340: * <p/>
341: * <p>Attributes may be read before or after reading the namespaces of an element,
342: * but must not be read after the first child node has been read, or after calling
343: * one of the methods skipToEnd(), getStringValue(), or getTypedValue().</p>
344: *
345: * @return an AttributeCollection representing the attributes of the element
346: * that has just been notified.
347: */
348:
349: public AttributeCollection getAttributes() throws XPathException {
350: return attributeCollection;
351: }
352:
353: private AttributeCollectionImpl copyAttributeCollection(
354: AttributeCollection in) {
355: AttributeCollectionImpl out = new AttributeCollectionImpl(
356: getNamePool());
357: for (int i = 0; i < in.getLength(); i++) {
358: out.addAttribute(in.getNameCode(i),
359: in.getTypeAnnotation(i), in.getValue(i), in
360: .getLocationId(i), in.getProperties(i));
361: }
362: return out;
363: }
364:
365: /**
366: * Get the namespace declarations associated with the current element. This method must
367: * be called only after a START_ELEMENT event has been notified. In the case of a top-level
368: * START_ELEMENT event (that is, an element that either has no parent node, or whose parent
369: * is not included in the sequence being read), the NamespaceDeclarations object returned
370: * will contain a namespace declaration for each namespace that is in-scope for this element
371: * node. In the case of a non-top-level element, the NamespaceDeclarations will contain
372: * a set of namespace declarations and undeclarations, representing the differences between
373: * this element and its parent.
374: * <p/>
375: * <p>It is permissible for this method to return namespace declarations that are redundant.</p>
376: * <p/>
377: * <p>The NamespaceDeclarations object is guaranteed to remain unchanged until the next START_ELEMENT
378: * event, but may then be overwritten. The object should not be modified by the client.</p>
379: * <p/>
380: * <p>Namespaces may be read before or after reading the attributes of an element,
381: * but must not be read after the first child node has been read, or after calling
382: * one of the methods skipToEnd(), getStringValue(), or getTypedValue().</p>*
383: */
384:
385: public NamespaceDeclarations getNamespaceDeclarations()
386: throws XPathException {
387: return declaredNamespaces;
388: }
389:
390: /**
391: * endElement: Discard the namespaces declared on this element.
392: */
393:
394: public void endElement() throws XPathException {
395: if (depth-- == 0) {
396: throw new IllegalStateException(
397: "Attempt to output end tag with no matching start tag");
398: }
399:
400: int nscount = countStack[depth];
401: allNamespacesSize -= nscount;
402: }
403:
404: /**
405: * Get the URI code corresponding to a given prefix code, by searching the
406: * in-scope namespaces. This is a service provided to subclasses.
407: * @param prefixCode the 16-bit prefix code required
408: * @return the 16-bit URI code, or -1 if the prefix is not found
409: */
410:
411: protected short getURICode(short prefixCode) {
412: for (int i = allNamespacesSize - 1; i >= 0; i--) {
413: if ((allNamespaces[i] >> 16) == (prefixCode)) {
414: return (short) (allNamespaces[i] & 0xffff);
415: }
416: }
417: if (prefixCode == 0) {
418: return 0; // by default, no prefix means no namespace URI
419: } else {
420: return -1;
421: }
422: }
423:
424: /**
425: * Get the namespace URI corresponding to a given prefix. Return null
426: * if the prefix is not in scope.
427: *
428: * @param prefix the namespace prefix
429: * @param useDefault true if the default namespace is to be used when the
430: * prefix is ""
431: * @return the uri for the namespace, or null if the prefix is not in scope
432: */
433:
434: public String getURIForPrefix(String prefix, boolean useDefault) {
435: NamePool pool = getNamePool();
436: if ("".equals(prefix) && !useDefault) {
437: return "";
438: } else if ("xml".equals(prefix)) {
439: return NamespaceConstant.XML;
440: } else {
441: short prefixCode = pool.getCodeForPrefix(prefix);
442: short uriCode = getURICode(prefixCode);
443: if (uriCode == -1) {
444: return null;
445: }
446: return pool.getURIFromURICode(uriCode);
447: }
448: }
449:
450: /**
451: * Get an iterator over all the prefixes declared in this namespace context. This will include
452: * the default namespace (prefix="") and the XML namespace where appropriate
453: */
454:
455: public Iterator iteratePrefixes() {
456: NamePool pool = getNamePool();
457: List prefixes = new ArrayList(allNamespacesSize);
458: for (int i = allNamespacesSize - 1; i >= 0; i--) {
459: String prefix = pool
460: .getPrefixFromNamespaceCode(allNamespaces[i]);
461: if (!prefixes.contains(prefix)) {
462: prefixes.add(prefix);
463: }
464: }
465: prefixes.add("xml");
466: return prefixes.iterator();
467: }
468:
469: }
470:
471: //
472: // The contents of this file are subject to the Mozilla Public License Version 1.0 (the "License");
473: // you may not use this file except in compliance with the License. You may obtain a copy of the
474: // License at http://www.mozilla.org/MPL/
475: //
476: // Software distributed under the License is distributed on an "AS IS" basis,
477: // WITHOUT WARRANTY OF ANY KIND, either express or implied.
478: // See the License for the specific language governing rights and limitations under the License.
479: //
480: // The Original Code is: all this file.
481: //
482: // The Initial Developer of the Original Code is Michael H. Kay.
483: //
484: // Portions created by (your name) are Copyright (C) (your legal entity). All Rights Reserved.
485: //
486: // Contributor(s): none.
487: //
|