blob: c87049cbb2805a8d383ac3c330c111d736bb0368 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.xml;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.apache.excalibur.source.SourceResolver;
import org.apache.excalibur.source.Source;
import org.apache.avalon.framework.logger.Logger;
import java.util.Stack;
import java.util.Collections;
import java.io.IOException;
/**
* Helper class for handling xml:base attributes.
*
* <p>Usage:
* <ul>
* <li>set location of the containing document by calling {@link #setDocumentLocation(String)}.
* This is usually done when getting setDocumentLocator SAX event.
* <li>forward each startElement and endElement event to this object.
* <li>to resolve a relative URL against the current base, call {@link #makeAbsolute(String)}.
* </ul>
*
* <p>External entities are not yet taken into account when determing the current base.
*/
public class XMLBaseSupport {
public static final String XMLBASE_NAMESPACE_URI = "http://www.w3.org/XML/1998/namespace";
public static final String XMLBASE_ATTRIBUTE = "base";
/** Increased on each startElement, decreased on each endElement. */
private int level = 0;
/**
* The stack contains an instance of {@link BaseInfo} for each XML element
* that contained an xml:base attribute (not for the other elements).
*/
private Stack bases = new Stack();
private SourceResolver resolver;
private Logger logger;
public XMLBaseSupport(SourceResolver resolver, Logger logger) {
this.resolver = resolver;
this.logger = logger;
}
public void setDocumentLocation(String loc) throws SAXException {
// -2 is used as level to avoid this BaseInfo to be ever popped of the stack
bases.push(new BaseInfo(loc, -2));
}
public void startElement(String namespaceURI, String localName, String qName, Attributes attrs) throws SAXException {
level++;
String base = attrs.getValue(XMLBASE_NAMESPACE_URI, XMLBASE_ATTRIBUTE);
if (base != null) {
Source baseSource = null;
String baseUrl;
try {
baseSource = resolve(getCurrentBase(), base);
baseUrl = baseSource.getURI();
} finally {
if (baseSource != null) {
resolver.release(baseSource);
}
}
bases.push(new BaseInfo(baseUrl, level));
}
}
public void endElement(String namespaceURI, String localName, String qName) {
if (getCurrentBaseLevel() == level)
bases.pop();
level--;
}
/**
* Warning: do not forget to release the source returned by this method.
*/
private Source resolve(String baseURI, String location) throws SAXException {
try {
Source source;
if (baseURI != null) {
source = resolver.resolveURI(location, baseURI, Collections.EMPTY_MAP);
} else {
source = resolver.resolveURI(location);
}
if (logger.isDebugEnabled()) {
logger.debug("XMLBaseSupport: resolved location " + location +
" against base URI " + baseURI + " to " + source.getURI());
}
return source;
} catch (IOException e) {
throw new SAXException("XMLBaseSupport: problem resolving uri.", e);
}
}
/**
* Makes the given path absolute based on the current base URL. Do not forget to release
* the returned source object!
* @param spec any URL (relative or absolute, containing a scheme or not)
*/
public Source makeAbsolute(String spec) throws SAXException {
return resolve(getCurrentBase(), spec);
}
/**
* Returns the base URI currently in effect, or null if unknown.
*/
public String getCurrentBase() {
if (bases.size() > 0) {
BaseInfo baseInfo = (BaseInfo)bases.peek();
return baseInfo.getUrl();
}
return null;
}
private int getCurrentBaseLevel() {
if (bases.size() > 0) {
BaseInfo baseInfo = (BaseInfo)bases.peek();
return baseInfo.getLevel();
}
return -1;
}
private static final class BaseInfo {
private String url;
private int level;
public BaseInfo(String url, int level) {
this.url = url;
this.level = level;
}
public String getUrl() {
return url;
}
public int getLevel() {
return level;
}
}
}