/*
* Milyn - Copyright (C) 2006 - 2011
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License (version 2.1) as published by the Free Software
* Foundation.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*
* See the GNU Lesser General Public License for more details:
* http://www.gnu.org/licenses/lgpl.txt
*/
package org.milyn.namespace;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import java.util.Stack;
import javax.xml.XMLConstants;
import org.xml.sax.Attributes;
import org.xml.sax.ContentHandler;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.AttributesImpl;
/**
* This class is responsible for managing namespace declarations.
*
* @author zubairov
*/
public class NamespaceDeclarationStack {
private final Stack<Map<String, String>> namespaceStack = new Stack<Map<String, String>>();
private final Stack<XMLReader> readerStack = new Stack<XMLReader>();
public NamespaceDeclarationStack() {
}
public NamespaceDeclarationStack(XMLReader xmlReader) {
pushReader(xmlReader);
}
/**
* Pushing a new element to the stack.
*
* @param qName Element QName.
* @param namespace Element namespace.
* @param attributes optional attributes or null, single element could declare multiple namespaces
* @return modified attributes declaration in case additional prefix mapping should be included
* @throws SAXException
*/
public Attributes pushNamespaces(String qName, String namespace, Attributes attributes) throws SAXException {
if(attributes == null || attributes.getLength() == 0) {
if(namespace == null || XMLConstants.NULL_NS_URI.equals(namespace)) {
namespaceStack.push(Collections.EMPTY_MAP);
return attributes;
}
}
Map<String, String> nsToURI = Collections.EMPTY_MAP;
// Gather namespace declarations from the attributes
if(attributes != null) {
for(int i = 0; i < attributes.getLength() ; i++) {
String attrNS = attributes.getURI(i);
if (attrNS != null && attrNS.equals(XMLConstants.XMLNS_ATTRIBUTE_NS_URI)) {
// Add prefix to the list of declared namespaces
if(nsToURI == Collections.EMPTY_MAP) {
nsToURI = new LinkedHashMap<String, String>();
}
String localName = attributes.getLocalName(i);
if (localName.equals(XMLConstants.XMLNS_ATTRIBUTE)) {
nsToURI.put(XMLConstants.DEFAULT_NS_PREFIX, attributes.getValue(i));
} else {
nsToURI.put(localName, attributes.getValue(i));
}
}
}
}
if(!XMLConstants.NULL_NS_URI.equals(namespace)) {
String[] qNameTokens = qName.split(":");
String prefix;
if(qNameTokens.length == 1) {
prefix = XMLConstants.DEFAULT_NS_PREFIX;
} else {
prefix = qNameTokens[0];
}
if (!prefixAlreadyDeclared(prefix) && !nsToURI.containsKey(prefix)) {
if(nsToURI == Collections.EMPTY_MAP) {
nsToURI = new LinkedHashMap<String, String>();
}
nsToURI.put(prefix, namespace);
}
}
if(!nsToURI.isEmpty() && !readerStack.isEmpty()) {
Set<Map.Entry<String,String>> namespaces = nsToURI.entrySet();
// Now call start prefixes if namespaces are not empty
ContentHandler contentHandler = readerStack.peek().getContentHandler();
if (contentHandler != null) {
for (Map.Entry<String,String> ns : namespaces) {
contentHandler.startPrefixMapping(ns.getKey(), ns.getValue());
}
}
}
namespaceStack.push(nsToURI);
return attributes;
}
/**
* Pop element out of the namespace declaration stack and notifying
* {@link ContentHandler} if required.
*
* @throws SAXException
*/
public void popNamespaces() throws SAXException {
Map<String, String> namespaces = namespaceStack.pop();
if (!namespaces.isEmpty() && !readerStack.isEmpty()) {
Set<String> nsPrefixes = namespaces.keySet();
ContentHandler contentHandler = readerStack.peek().getContentHandler();
if (contentHandler != null) {
for (String prefix : nsPrefixes) {
contentHandler.endPrefixMapping(prefix);
}
}
}
}
/**
* Push a new XMLReader instance onto the XMLReader Stack.
* @param reader The reader instance.
*/
public void pushReader(XMLReader reader) {
readerStack.push(reader);
}
/**
* Pop the current XMLReader off the XMLReader stack.
* @return The reader instance that was popped from the stack.
*/
public XMLReader popReader() {
return readerStack.pop();
}
public String getPrefix(String uri) {
int stackDepth = namespaceStack.size();
for (int i = stackDepth - 1; i >= 0; i--) {
Map<String, String> nsMap = namespaceStack.get(i);
if(!nsMap.isEmpty()) {
Set<Map.Entry<String,String>> nsEntries = nsMap.entrySet();
for (Map.Entry<String,String> nsEntry : nsEntries) {
if(nsEntry.getValue().equals(uri)) {
return nsEntry.getKey();
}
}
}
}
return null;
}
public Map<String, String> getActiveNamespaces() {
Map<String, String> activeNamespaces = new HashMap<String, String>();
int stackDepth = namespaceStack.size();
for (int i = stackDepth - 1; i >= 0; i--) {
Map<String, String> nsMap = namespaceStack.get(i);
if(!nsMap.isEmpty()) {
activeNamespaces.putAll(nsMap);
}
}
return activeNamespaces;
}
public String getUri(String getKey) {
int stackDepth = namespaceStack.size();
for (int i = stackDepth - 1; i >= 0; i--) {
Map<String, String> nsMap = namespaceStack.get(i);
if(!nsMap.isEmpty()) {
Set<Map.Entry<String,String>> nsEntries = nsMap.entrySet();
for (Map.Entry<String,String> nsEntry : nsEntries) {
if(nsEntry.getKey().equals(getKey)) {
return nsEntry.getValue();
}
}
}
}
return null;
}
/**
* This method returns true if namespace with given prefix was already declared higher
* the stack
*
* @param prefix
* @return
*/
private boolean prefixAlreadyDeclared(String prefix) {
for (Map<String, String> set : namespaceStack) {
if (set.containsKey(prefix)) {
return true;
}
}
return false;
}
}