You are viewing a plain text version of this content. The canonical link for it is here.
Posted to cvs@cocoon.apache.org by sy...@apache.org on 2005/08/10 17:44:21 UTC

svn commit: r231269 - in /cocoon: blocks/template/trunk/java/org/apache/cocoon/components/expression/ blocks/template/trunk/java/org/apache/cocoon/components/expression/jxpath/ blocks/template/trunk/java/org/apache/cocoon/template/script/event/ trunk/ ...

Author: sylvain
Date: Wed Aug 10 08:43:57 2005
New Revision: 231269

URL: http://svn.apache.org/viewcvs?rev=231269&view=rev
Log:
Propagate namespace mappings in JXTemplate to JXPath expressions. Refactor and extend NamespacesTable

Added:
    cocoon/trunk/src/java/org/apache/cocoon/util/jxpath/NamespacesTablePointer.java   (with props)
    cocoon/trunk/src/test/org/apache/cocoon/xml/NamespacesTableTestCase.java   (with props)
Modified:
    cocoon/blocks/template/trunk/java/org/apache/cocoon/components/expression/ExpressionContext.java
    cocoon/blocks/template/trunk/java/org/apache/cocoon/components/expression/jxpath/JXPathExpression.java
    cocoon/blocks/template/trunk/java/org/apache/cocoon/template/script/event/EndElement.java
    cocoon/blocks/template/trunk/java/org/apache/cocoon/template/script/event/EndPrefixMapping.java
    cocoon/blocks/template/trunk/java/org/apache/cocoon/template/script/event/StartElement.java
    cocoon/blocks/template/trunk/java/org/apache/cocoon/template/script/event/StartPrefixMapping.java
    cocoon/trunk/src/java/org/apache/cocoon/xml/NamespacesTable.java
    cocoon/trunk/src/java/org/apache/cocoon/xml/RedundantNamespacesFilter.java
    cocoon/trunk/status.xml

Modified: cocoon/blocks/template/trunk/java/org/apache/cocoon/components/expression/ExpressionContext.java
URL: http://svn.apache.org/viewcvs/cocoon/blocks/template/trunk/java/org/apache/cocoon/components/expression/ExpressionContext.java?rev=231269&r1=231268&r2=231269&view=diff
==============================================================================
--- cocoon/blocks/template/trunk/java/org/apache/cocoon/components/expression/ExpressionContext.java (original)
+++ cocoon/blocks/template/trunk/java/org/apache/cocoon/components/expression/ExpressionContext.java Wed Aug 10 08:43:57 2005
@@ -18,12 +18,15 @@
 import java.util.HashMap;
 import java.util.Map;
 
+import org.apache.cocoon.xml.NamespacesTable;
+
 /**
  * @version SVN $Id$
  */
 public class ExpressionContext extends HashMap {
     private ExpressionContext closure;
     private Object contextBean = null;
+    private NamespacesTable namespaces;
 
     public ExpressionContext() {
         this(null);
@@ -31,6 +34,22 @@
 
     public ExpressionContext(ExpressionContext closure) {
         this.closure = closure;
+        if (closure == null) {
+            this.namespaces = new NamespacesTable();
+        } else {
+            // Reuse the parent one. Users of the context should correctly enter and leave namespace scopes.
+            this.namespaces = closure.namespaces;
+        }
+    }
+    
+    /**
+     * Get the namespace table that tracks the applicable namespace prefix mappings
+     * for the expression context.
+     * 
+     * @return the namespaces table
+     */
+    public NamespacesTable getNamespaces() {
+        return this.namespaces;
     }
 
     public Object getContextBean() {

Modified: cocoon/blocks/template/trunk/java/org/apache/cocoon/components/expression/jxpath/JXPathExpression.java
URL: http://svn.apache.org/viewcvs/cocoon/blocks/template/trunk/java/org/apache/cocoon/components/expression/jxpath/JXPathExpression.java?rev=231269&r1=231268&r2=231269&view=diff
==============================================================================
--- cocoon/blocks/template/trunk/java/org/apache/cocoon/components/expression/jxpath/JXPathExpression.java (original)
+++ cocoon/blocks/template/trunk/java/org/apache/cocoon/components/expression/jxpath/JXPathExpression.java Wed Aug 10 08:43:57 2005
@@ -23,6 +23,7 @@
 import org.apache.cocoon.components.expression.ExpressionContext;
 import org.apache.cocoon.components.expression.ExpressionException;
 import org.apache.cocoon.components.expression.jexl.JSIntrospector;
+import org.apache.cocoon.util.jxpath.NamespacesTablePointer;
 import org.apache.commons.jxpath.CompiledExpression;
 import org.apache.commons.jxpath.JXPathContext;
 import org.apache.commons.jxpath.Pointer;
@@ -134,6 +135,7 @@
         JXPathContext jxcontext = JXPathContext.newContext(context.getContextBean());
         jxcontext.setVariables(new VariableAdapter(context));
         jxcontext.setLenient(this.lenient);
+        jxcontext.setNamespaceContextPointer(new NamespacesTablePointer(context.getNamespaces()));
         return jxcontext;
     }
 

Modified: cocoon/blocks/template/trunk/java/org/apache/cocoon/template/script/event/EndElement.java
URL: http://svn.apache.org/viewcvs/cocoon/blocks/template/trunk/java/org/apache/cocoon/template/script/event/EndElement.java?rev=231269&r1=231268&r2=231269&view=diff
==============================================================================
--- cocoon/blocks/template/trunk/java/org/apache/cocoon/template/script/event/EndElement.java (original)
+++ cocoon/blocks/template/trunk/java/org/apache/cocoon/template/script/event/EndElement.java Wed Aug 10 08:43:57 2005
@@ -43,6 +43,9 @@
             Event startEvent, Event endEvent) throws SAXException {
         consumer.endElement(startElement.getNamespaceURI(), startElement
                 .getLocalName(), startElement.getRaw());
+
+        // Send any pending endPrefixMapping events
+        expressionContext.getNamespaces().leaveScope(consumer);
         return getNext();
     }
 }

Modified: cocoon/blocks/template/trunk/java/org/apache/cocoon/template/script/event/EndPrefixMapping.java
URL: http://svn.apache.org/viewcvs/cocoon/blocks/template/trunk/java/org/apache/cocoon/template/script/event/EndPrefixMapping.java?rev=231269&r1=231268&r2=231269&view=diff
==============================================================================
--- cocoon/blocks/template/trunk/java/org/apache/cocoon/template/script/event/EndPrefixMapping.java (original)
+++ cocoon/blocks/template/trunk/java/org/apache/cocoon/template/script/event/EndPrefixMapping.java Wed Aug 10 08:43:57 2005
@@ -41,7 +41,8 @@
             ExpressionContext expressionContext,
             ExecutionContext executionContext, MacroContext macroContext,
             Event startEvent, Event endEvent) throws SAXException {
-        consumer.endPrefixMapping(getPrefix());
+        
+        // nothing (endPrefixMapping is sent in EndElement)
         return getNext();
     }
 }

Modified: cocoon/blocks/template/trunk/java/org/apache/cocoon/template/script/event/StartElement.java
URL: http://svn.apache.org/viewcvs/cocoon/blocks/template/trunk/java/org/apache/cocoon/template/script/event/StartElement.java?rev=231269&r1=231268&r2=231269&view=diff
==============================================================================
--- cocoon/blocks/template/trunk/java/org/apache/cocoon/template/script/event/StartElement.java (original)
+++ cocoon/blocks/template/trunk/java/org/apache/cocoon/template/script/event/StartElement.java Wed Aug 10 08:43:57 2005
@@ -119,6 +119,9 @@
                         .getType(), attributeValue);
             }
         }
+        
+        // Send any pending startPrefixMapping events
+        expressionContext.getNamespaces().enterScope(consumer);
         consumer.startElement(getNamespaceURI(), getLocalName(), getRaw(),
                 attrs);
         return getNext();

Modified: cocoon/blocks/template/trunk/java/org/apache/cocoon/template/script/event/StartPrefixMapping.java
URL: http://svn.apache.org/viewcvs/cocoon/blocks/template/trunk/java/org/apache/cocoon/template/script/event/StartPrefixMapping.java?rev=231269&r1=231268&r2=231269&view=diff
==============================================================================
--- cocoon/blocks/template/trunk/java/org/apache/cocoon/template/script/event/StartPrefixMapping.java (original)
+++ cocoon/blocks/template/trunk/java/org/apache/cocoon/template/script/event/StartPrefixMapping.java Wed Aug 10 08:43:57 2005
@@ -47,7 +47,9 @@
             ExpressionContext expressionContext,
             ExecutionContext executionContext, MacroContext macroContext,
             Event startEvent, Event endEvent) throws SAXException {
-        consumer.startPrefixMapping(getPrefix(), getUri());
+        
+        expressionContext.getNamespaces().addDeclaration(getPrefix(), getUri());
+        // the startPrefixMapping event will be sent in StartElement
         return getNext();
     }
 }

Added: cocoon/trunk/src/java/org/apache/cocoon/util/jxpath/NamespacesTablePointer.java
URL: http://svn.apache.org/viewcvs/cocoon/trunk/src/java/org/apache/cocoon/util/jxpath/NamespacesTablePointer.java?rev=231269&view=auto
==============================================================================
--- cocoon/trunk/src/java/org/apache/cocoon/util/jxpath/NamespacesTablePointer.java (added)
+++ cocoon/trunk/src/java/org/apache/cocoon/util/jxpath/NamespacesTablePointer.java Wed Aug 10 08:43:57 2005
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2005 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.cocoon.util.jxpath;
+
+import org.apache.cocoon.xml.NamespacesTable;
+import org.apache.commons.jxpath.ri.QName;
+import org.apache.commons.jxpath.ri.model.NodeIterator;
+import org.apache.commons.jxpath.ri.model.NodePointer;
+
+/**
+ * A JXPath <code>Pointer</code> that tracks namespaces defined by a {@link NamespacesTable}.
+ * This class is to be used to inform JXPath of the namespaces declared in a host environment
+ * (e.g. JXTemplateGenerator) using
+ * <a href="http://jakarta.apache.org/commons/jxpath/apidocs/org/apache/commons/jxpath/JXPathContext.html#setNamespaceContextPointer(org.apache.commons.jxpath.Pointer)">JXPathContext.setNamespaceContextPointer()</a>.
+ * 
+ * @since 2.1.8
+ * @version $Id$
+ */
+public class NamespacesTablePointer extends NodePointer {
+    
+    private NamespacesTable namespaces;
+
+    public NamespacesTablePointer(NamespacesTable namespaces) {
+        super(null);
+        this.namespaces = namespaces;
+    }
+
+    public String getNamespaceURI(String prefix) {
+        return namespaces.getUri(prefix);
+    }
+
+    protected String getDefaultNamespaceURI() {
+        return namespaces.getUri("");
+    }
+
+    public NodeIterator namespaceIterator() {
+        return null;
+    }
+    
+    //-------------------------------------------------------------------------
+    // Dummy implementation of abstract methods
+    //-------------------------------------------------------------------------
+
+    public boolean isLeaf() {
+        return true;
+    }
+
+    public boolean isCollection() {
+        return false;
+    }
+
+    public int getLength() {
+        return 0;
+    }
+
+    public QName getName() {
+        return null;
+    }
+
+    public Object getBaseValue() {
+        return null;
+    }
+
+    public Object getImmediateNode() {
+        return null;
+    }
+
+    public void setValue(Object value) {
+        // ignore
+    }
+
+    public int compareChildNodePointers(NodePointer arg0, NodePointer arg1) {
+        return -1;
+    }
+}

Propchange: cocoon/trunk/src/java/org/apache/cocoon/util/jxpath/NamespacesTablePointer.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: cocoon/trunk/src/java/org/apache/cocoon/util/jxpath/NamespacesTablePointer.java
------------------------------------------------------------------------------
    svn:keywords = Id

Modified: cocoon/trunk/src/java/org/apache/cocoon/xml/NamespacesTable.java
URL: http://svn.apache.org/viewcvs/cocoon/trunk/src/java/org/apache/cocoon/xml/NamespacesTable.java?rev=231269&r1=231268&r2=231269&view=diff
==============================================================================
--- cocoon/trunk/src/java/org/apache/cocoon/xml/NamespacesTable.java (original)
+++ cocoon/trunk/src/java/org/apache/cocoon/xml/NamespacesTable.java Wed Aug 10 08:43:57 2005
@@ -15,30 +15,69 @@
  */
 package org.apache.cocoon.xml;
 
+import org.xml.sax.ContentHandler;
 import org.xml.sax.SAXException;
 
 /**
- * This utility class is used to keep track namespaces declarations and resolve
- * namespaces names.
+ * Keeps track of namespaces declarations and resolve namespaces names.
+ * <p>
+ * This class also provides a very convenient and safe way of handling
+ * namespace declarations in SAX pipes. It also allows to filter duplicate namespace
+ * declarations that too often clutter up XML documents that went through
+ * several transformations, and avoid useless namespace declarations that aren't followed
+ * by element events.
+ * <p>
+ * Usage example in a SAX pipe:
+ * <pre>
+ *   NamespacesTable namespaces = new NamespacesTable();
+ *   ContentHandler nextHandler;
  *
- * @author <a href="mailto:pier@apache.org">Pierpaolo Fumagalli</a>
- *         (Apache Software Foundation)
- * @version CVS $Id: NamespacesTable.java,v 1.3 2004/03/05 13:03:01 bdelacretaz Exp $
+ *   public void startPrefixMapping(String prefix, String uri) throws SAXException {
+ *       namespaces.addDeclaration(prefix, uri);
+ *   }
+ *   
+ *   public void startElement(...) throws SAXException {
+ *       // automatically start mappings for this scope
+ *       namespaces.enterScope(nextHandler);
+ *       nextHandler.startElement(...);
+ *   }
+ *   
+ *   public void endElement(...) throws SAXException {
+ *       nextHandler.endElement(...);
+ *       // automatically end mappings for this scope
+ *       namespaces.leaveScope(nextHandler);
+ *   }
+ *   
+ *   public void endPrefixMapping(String prefix) throws SAXException {
+ *       // Ignore, it is handled by the call to leaveScope above.
+ *   }
+ * </pre>
+ * 
+ * @version $Id$
  */
 public class NamespacesTable {
-    /** The initial namespace declaration. */
-    private Entry entry=null;
+    /** The last namespace declaration. */
+    private Entry lastEntry = null;
+    
+    private boolean filterDuplicate = true;
 
     /**
      * Construct a new <code>NamespacesTable</code> instance.
      */
     public NamespacesTable() {
-        super();
-        this.entry=Entry.create("","");
-        // Set the previous declaration of this namespace as self, so it will
-        // not be possible to remove it :)
-        this.entry.previousDeclaration=this.entry;
+        clear();
+    }
+    
+    /**
+     * Clear and reinitialize this namespace table before reuse.
+     *
+     * @since 2.1.8
+     */
+    public void clear() {
+        this.lastEntry=Entry.create("","");
         this.addDeclaration("xml", "http://www.w3.org/XML/1998/namespace");
+        // Lock this scope
+        this.lastEntry.closedScopes = 1;
     }
 
     /**
@@ -47,28 +86,24 @@
      * @return The newly added <code>Declaration</code>.
      */
     public Declaration addDeclaration(String prefix, String uri) {
-        Entry e=Entry.create(prefix,uri);
-        Entry previous=null;
-        Entry current=this.entry;
-        while (current!=null) {
-            if (current.prefixHash==e.prefixHash) {
-                // Set the current entry to be the previous declaration for the
-                // specified prefix and remove it from the chain.
-                e.previousDeclaration=current;
-                e.nextEntry=current.nextEntry;
-                current.nextEntry=null;
-                // Set the new entry in the chain
-                if (previous==null) this.entry=e;
-                else previous.nextEntry=e;
-                return(e);
-            } else {
-                previous=current;
-                current=current.nextEntry;
+        // Find a previous declaration of the same prefix
+        Entry dup = this.lastEntry;
+        while (dup != null && !dup.prefix.equals(prefix)) {
+            dup = dup.previous;
+        }
+        
+        if (dup != null) {
+            if (filterDuplicate && dup.uri.equals(uri)) {
+                return dup;
             }
+            dup.overriden = true;
         }
-        if (previous==null) this.entry=e;
-        else previous.nextEntry=e;
-        return(e);
+        
+        Entry e = Entry.create(prefix, uri);
+        e.previous = this.lastEntry;
+        e.overrides = dup;
+        this.lastEntry = e;
+        return e;
     }
 
     /**
@@ -80,26 +115,149 @@
      * @return The removed <code>Declaration</code> or <b>null</b>.
      */
     public Declaration removeDeclaration(String prefix) {
-        int hash=prefix.hashCode();
-        Entry previous=null;
-        Entry current=this.entry;
-        while (current!=null) {
-            if (current.prefixHash==hash) {
-                if (current.previousDeclaration==null) {
-                    if (previous==null) this.entry=current.nextEntry;
-                    else previous.nextEntry=current.nextEntry;
-                } else {
-                    current.previousDeclaration.nextEntry=current.nextEntry;
-                    if (previous==null) this.entry=current.previousDeclaration;
-                    else previous.nextEntry=current.previousDeclaration;
+        
+        Entry current = this.lastEntry;
+        Entry afterCurrent = null;
+        while(current != null) {
+            if (current.closedScopes > 0) {
+                // Don't undeclare mappings not declared in this scope
+                return null;
+            }
+            
+            if (current.prefix.equals(prefix)) {
+                // Got it
+                // Remove it from the chain
+                if (afterCurrent != null) {
+                    afterCurrent.previous = current.previous;
                 }
-                return(current);
-            } else {
-                previous=current;
-                current=current.nextEntry;
+                // And report closed scopes on the previous entry
+                current.previous.closedScopes += current.closedScopes;
+                Entry overrides = current.overrides;
+                if (overrides != null) {
+                    // No more overriden
+                    overrides.overriden = false;
+                }
+                
+                return current;
             }
+            
+            afterCurrent = current;
+            current = current.previous;
         }
-        return(null);
+        
+        // Not found
+        return null;
+    }
+    
+    /**
+     * Enter a new scope, with no declared mappings.
+     * 
+     * @see #getCurrentScopeDeclarations()
+     * @since 2.1.8
+     */
+    public void enterScope() {
+        this.lastEntry.closedScopes++;
+    }
+    
+    /**
+     * Start all declared mappings of the current scope and enter a new scope.
+     * Typically called in a SAX handler <em>before</em> sending a <code>startElement()</code>
+     * event.
+     * 
+     * @param handler the handler that will receive startPrefixMapping events.
+     * @throws SAXException
+     * @since 2.1.8
+     */
+    public void enterScope(ContentHandler handler) throws SAXException {
+        Entry current = this.lastEntry;
+        while (current != null && current.closedScopes == 0) {
+            handler.startPrefixMapping(current.prefix, current.uri);
+            current = current.previous;
+        }
+        enterScope();
+    }
+    
+    /**
+     * Leave a scope. If <code>autoRemove</code> is true, all declared mappings for the
+     * scope that is left are automatically removed, without having to explicitely call
+     * {@link #removeDeclaration(String)}.
+     * 
+     * @param autoRemove if <code>true</code>, remove all mappings for the current scope.
+     * @since 2.1.8
+     */
+    public void leaveScope(boolean autoUndeclare) {
+        Entry current = this.lastEntry;
+        if (current.closedScopes <= 0) {
+            throw new IllegalStateException("Misbalanced enter and leaving of scope.");
+        }
+        current.closedScopes--;
+        if (autoUndeclare) {
+            while (current != null && current.closedScopes == 0) {
+                Entry overrides = current.overrides;
+                if (overrides != null) {
+                    // No more overriden
+                    overrides.overriden = false;
+                }
+                current = current.previous;
+            }
+        }
+        this.lastEntry = current;
+    }
+    
+    /**
+     * Leave a scope and end all declared mappings of the new scope.
+     * Typically called in a SAX handler <em>after</em> sending a <code>endElement()</code>
+     * event.
+     * 
+     * @param handler the handler that will receive endPrefixMapping events.
+     * @throws SAXException
+     * @since 2.1.8
+     */
+    public void leaveScope(ContentHandler handler) throws SAXException {
+        Entry current = this.lastEntry;
+        if (current.closedScopes <= 0) {
+            throw new IllegalStateException("Misbalanced enter and leaving of scope.");
+        }
+        while (current != null && current.closedScopes == 0) {
+            handler.endPrefixMapping(current.prefix);
+            Entry overrides = current.overrides;
+            if (overrides != null) {
+                // No more overriden
+                overrides.overriden = false;
+            }
+            current = current.previous;
+        }
+
+        current.closedScopes--;
+        this.lastEntry = current;
+    }
+    
+    private static final Declaration[] NO_DECLS = new Declaration[0];
+
+    /**
+     * Get the declarations that were declared within the current scope.
+     * 
+     * @return the declarations (never null)
+     * @since 2.1.8
+     */
+    public Declaration[] getCurrentScopeDeclarations() {
+        int count = 0;
+        Entry current = this.lastEntry;
+        while (current != null && current.closedScopes == 0) {
+            count++;
+            current = current.previous;
+        }
+
+        if (count == 0) return NO_DECLS;
+
+        Declaration[] decls = new Declaration[count];
+        count = 0;
+        current = this.lastEntry;
+        while (current != null && current.closedScopes == 0) {
+            decls[count++] = current;
+            current = current.previous;
+        }
+        return decls;
     }
 
     /**
@@ -107,13 +265,16 @@
      * prefix was not mapped.
      */
     public String getUri(String prefix) {
-        int hash=prefix.hashCode();
-        Entry current=this.entry;
-        while (current!=null) {
-            if(current.prefixHash==hash) return(current.uri);
-            else current=current.nextEntry;
+        Entry current = this.lastEntry;
+        while (current != null) {
+            if (current.prefix.equals(prefix)) {
+                return current.uri;
+            }
+            current = current.previous;
         }
-        return(null);
+        
+        // Not found
+        return null;
     }
 
     /**
@@ -125,21 +286,25 @@
      * @return A <b>non-null</b> <code>String</code> array.
      */
     public String[] getPrefixes(String uri) {
-        int hash=uri.hashCode();
-        Entry current=this.entry;
+
+        Entry current=this.lastEntry;
         int count=0;
         while (current!=null) {
-            if(current.uriHash==hash) count++;
-            current=current.nextEntry;
+            if(!current.overriden && current.uri.equals(uri))
+                count++;
+            current=current.previous;
         }
         if (count==0) return(new String[0]);
+
         String prefixes[]=new String[count];
         count=0;
+        current = this.lastEntry;
         while (current!=null) {
-            if(current.uriHash==hash) prefixes[count++]=current.prefix;
-            current=current.nextEntry;
+            if(!current.overriden && current.uri.equals(uri))
+                prefixes[count++] = current.prefix;
+            current = current.previous;
         }
-        return(prefixes);
+        return prefixes;
     }
 
 
@@ -148,13 +313,13 @@
      * <b>null</b>.
      */
     public String getPrefix(String uri) {
-        int hash=uri.hashCode();
-        Entry current=this.entry;
-        while (current!=null) {
-            if(current.uriHash==hash) return(current.prefix);
-            current=current.nextEntry;
+        Entry current = this.lastEntry;
+        while (current != null) {
+            if(!current.overriden && current.uri.equals(uri))
+                return current.prefix;
+            current = current.previous;
         }
-        return(null);
+        return null;
     }
 
     /**
@@ -233,37 +398,32 @@
 
     /** The internal entry structure for this table. */
     private static class Entry implements Declaration {
-        /** The URI hashcode. */
-        protected int uriHash=0;
-        /** The prefix hashcode. */
-        protected int prefixHash=0;
         /** The URI string. */
         protected String uri="";
         /** The prefix string. */
         protected String prefix="";
-        /** The previous declaration for the same prefix. */
-        protected Entry previousDeclaration;
-        /** The declaration following this one in the table. */
-        protected Entry nextEntry;
+        /** The previous declaration. */
+        protected Entry previous;
+        protected Entry overrides;
+        protected int closedScopes = 0;
+        protected boolean overriden = false;
 
         /** Create a new namespace declaration. */
         protected static Entry create(String prefix, String uri) {
             // Create a new entry
-            Entry e=new Entry();
-            // Set the prefix string and hash code.
-            if (prefix!=null) e.prefix=prefix;
-            e.prefixHash=e.prefix.hashCode();
-            // Set the uri string and hash code.
-            if (uri!=null) e.uri=uri;
-            e.uriHash=e.uri.hashCode();
+            Entry e = new Entry();
+            // Set the prefix string.
+            if (prefix != null) e.prefix=prefix;
+            // Set the uri string.
+            if (uri != null) e.uri=uri;
             // Return the entry
-            return(e);
+            return e;
         }
 
         /** Return the namespace URI. */
-        public String getUri() { return(this.uri); }
+        public String getUri() { return this.uri; }
         /** Return the namespace prefix. */
-        public String getPrefix() { return(this.prefix); }
+        public String getPrefix() { return this.prefix; }
     }
 
     /** The default namespace-aware name declaration implementation */
@@ -278,13 +438,13 @@
         protected String raw;
 
         /** Return the namespace URI. */
-        public String getUri() { return(this.uri); }
+        public String getUri() { return this.uri; }
         /** Return the namespace prefix. */
-        public String getPrefix() { return(this.prefix); }
+        public String getPrefix() { return this.prefix; }
         /** Return the namespace local name. */
-        public String getLocalName() { return(this.local); }
+        public String getLocalName() { return this.local; }
         /** Return the namespace raw name. */
-        public String getQName() { return(this.raw); }
+        public String getQName() { return this.raw; }
     }
 
     /**

Modified: cocoon/trunk/src/java/org/apache/cocoon/xml/RedundantNamespacesFilter.java
URL: http://svn.apache.org/viewcvs/cocoon/trunk/src/java/org/apache/cocoon/xml/RedundantNamespacesFilter.java?rev=231269&r1=231268&r2=231269&view=diff
==============================================================================
--- cocoon/trunk/src/java/org/apache/cocoon/xml/RedundantNamespacesFilter.java (original)
+++ cocoon/trunk/src/java/org/apache/cocoon/xml/RedundantNamespacesFilter.java Wed Aug 10 08:43:57 2005
@@ -15,8 +15,6 @@
  */
 package org.apache.cocoon.xml;
 
-import java.util.Enumeration;
-
 import org.xml.sax.Attributes;
 import org.xml.sax.SAXException;
 
@@ -28,13 +26,15 @@
  * no element inbetween) that can be produced by some components (e.g. JXTG or
  * BrowserUpdateTransformer). Such empty scopes confuse the Xalan serializer which
  * then produces weird namespace declarations (<code>xmlns:%@$#^@#="%@$#^@#"</code>).
+ * <p>
+ * This is a the most simple use of {@link NamespaceHelper}.
  * 
  * @version CVS $Id$
  */
 public class RedundantNamespacesFilter extends AbstractXMLPipe {
     
     /** Layered storage for all namespace declarations */
-    private NamespaceSupport ns = new NamespaceSupport();
+    private NamespacesTable ns = new NamespacesTable();
     
     /**
      * No-arg constructor. Requires an explicit call to
@@ -55,31 +55,19 @@
     }
     
     public void startPrefixMapping(String prefix, String uri) throws SAXException {
-        if (!uri.equals(ns.getURI(prefix))) {
-            // New declaration: store it
-            ns.declarePrefix(prefix, uri);
-        }
+        // Just declare it: duplicate declarations are ignorede by NamespacesTable
+        ns.addDeclaration(prefix, uri);
     }
 
     public void startElement(String uri, String loc, String raw, Attributes a) throws SAXException {
         // Declare namespaces for this scope, if any
-        Enumeration prefixes = ns.getDeclaredPrefixes();
-        while (prefixes.hasMoreElements()) {
-            String prefix = (String) prefixes.nextElement();
-            super.startPrefixMapping(prefix, ns.getURI(prefix));
-        }
-        ns.pushContext();
+        ns.enterScope(this.contentHandler);
         super.startElement(uri, loc, raw, a);
     }
 
     public void endElement(String uri, String loc, String raw) throws SAXException {
         super.endElement(uri, loc, raw);
-        ns.popContext();
-        // Undeclare namespaces for this scope, if any
-        Enumeration prefixes = ns.getDeclaredPrefixes();
-        while (prefixes.hasMoreElements()) {
-            super.endPrefixMapping((String) prefixes.nextElement());
-        }
+        ns.leaveScope(this.contentHandler);
     }
 
     public void endPrefixMapping(String prefix) throws SAXException {

Added: cocoon/trunk/src/test/org/apache/cocoon/xml/NamespacesTableTestCase.java
URL: http://svn.apache.org/viewcvs/cocoon/trunk/src/test/org/apache/cocoon/xml/NamespacesTableTestCase.java?rev=231269&view=auto
==============================================================================
--- cocoon/trunk/src/test/org/apache/cocoon/xml/NamespacesTableTestCase.java (added)
+++ cocoon/trunk/src/test/org/apache/cocoon/xml/NamespacesTableTestCase.java Wed Aug 10 08:43:57 2005
@@ -0,0 +1,117 @@
+package org.apache.cocoon.xml;
+
+import org.xml.sax.helpers.DefaultHandler;
+
+import junit.framework.TestCase;
+
+public class NamespacesTableTestCase extends TestCase {
+    public NamespacesTableTestCase(String name) {
+        super(name);
+    }
+        
+    public void testSimple() {
+        NamespacesTable ns = new NamespacesTable();
+        
+        ns.addDeclaration("ns1", "http://ns1");
+        ns.addDeclaration("ns2", "http://ns2");
+        
+        ns.enterScope();
+        
+        assertEquals("http://ns1", ns.getUri("ns1"));
+        assertEquals("ns1", ns.getPrefix("http://ns1"));
+        
+        assertEquals("http://ns2", ns.getUri("ns2"));
+        assertEquals("ns2", ns.getPrefix("http://ns2"));
+        
+        assertNull(ns.getPrefix(ns.getPrefix("http://ns3")));
+        assertNull(ns.getUri("ns3"));
+        
+        ns.leaveScope(false);
+        assertNotNull(ns.removeDeclaration("ns1"));
+        assertNotNull(ns.removeDeclaration("ns2"));
+        assertNull(ns.removeDeclaration("ns3"));
+    }
+    
+    public void testWrongUndeclare() {
+        NamespacesTable ns = new NamespacesTable();
+        
+        ns.enterScope();
+        ns.addDeclaration("ns1", "http://ns1");
+        ns.addDeclaration("ns2", "http://ns2");
+        
+        ns.enterScope(); // increments closedScopes on ns2
+        
+        ns.addDeclaration("ns3", "http://ns3");
+        
+        try {
+            ns.leaveScope(false);
+        } catch(IllegalStateException e) {
+            return;
+        }
+        
+        fail();
+    }
+    
+    public void testOverride() {
+        NamespacesTable ns = new NamespacesTable();
+        
+        ns.addDeclaration("ns1", "http://ns1");
+        ns.enterScope();
+        ns.addDeclaration("ns1", "http://otherns1");
+        ns.enterScope();
+        ns.addDeclaration("ns1", "http://yetanotherns1");
+        ns.enterScope();
+        
+        assertEquals("http://yetanotherns1", ns.getUri("ns1"));
+        assertEquals(0, ns.getPrefixes("http://ns1").length);
+        
+        ns.leaveScope(true);
+        ns.leaveScope(true);
+        
+        assertEquals("http://ns1", ns.getUri("ns1"));
+        assertEquals(1, ns.getPrefixes("http://ns1").length);
+        
+        ns.leaveScope(true);
+        assertNull(ns.getUri("ns1"));
+    }
+    
+    public void testMultiDeclaration() {
+        NamespacesTable ns = new NamespacesTable();
+        ns.addDeclaration("ns1", "http://ns1");
+        ns.enterScope();
+        // two in the same scope
+        ns.addDeclaration("ns2", "http://ns1");
+        ns.addDeclaration("ns3", "http://ns1");
+        ns.enterScope();
+        
+        String[] prefixes = ns.getPrefixes("http://ns1");
+        assertEquals(3, prefixes.length);
+        assertEquals("ns3", prefixes[0]);
+        assertEquals("ns2", prefixes[1]);
+        assertEquals("ns1", prefixes[2]);
+    }
+    
+    public void testStreamDeclarations() throws Exception {
+        NamespacesTable ns = new NamespacesTable();
+        ns.addDeclaration("ns1", "http://ns1");
+        ns.enterScope();
+        ns.addDeclaration("ns2", "http://ns2");
+        ns.enterScope(new DefaultHandler() {
+            public void startPrefixMapping(String prefix, String uri) throws org.xml.sax.SAXException {
+                assertEquals("ns2", prefix);
+                assertEquals("http://ns2", uri);
+            };
+        });
+        
+        // Enter and leave a nested scope
+        ns.addDeclaration("ns3", "http://ns3");
+        ns.enterScope();
+        ns.leaveScope(true);
+        
+        ns.leaveScope(new DefaultHandler() {
+            public void endPrefixMapping(String prefix) throws org.xml.sax.SAXException {
+                assertEquals("ns2", prefix);
+            };
+        });
+    }
+}

Propchange: cocoon/trunk/src/test/org/apache/cocoon/xml/NamespacesTableTestCase.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: cocoon/trunk/src/test/org/apache/cocoon/xml/NamespacesTableTestCase.java
------------------------------------------------------------------------------
    svn:keywords = Id

Modified: cocoon/trunk/status.xml
URL: http://svn.apache.org/viewcvs/cocoon/trunk/status.xml?rev=231269&r1=231268&r2=231269&view=diff
==============================================================================
--- cocoon/trunk/status.xml (original)
+++ cocoon/trunk/status.xml Wed Aug 10 08:43:57 2005
@@ -518,6 +518,9 @@
    </action>
   </release>
   <release version="2.1.8" date="TBD">
+    <action dev="SW" type="fix">
+     JXTemplate: ensure JXPath expressions can use namespace prefix mappings declared in the template document.
+    </action>
     <action dev="CZ" type="update">
      Updated Axis to 1.2 and wsdl4j to 1.5.1.
     </action>