001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017: package org.apache.cocoon.xml;
018:
019: import org.xml.sax.Attributes;
020: import org.xml.sax.SAXException;
021: import org.apache.excalibur.source.SourceResolver;
022: import org.apache.excalibur.source.Source;
023: import org.apache.avalon.framework.logger.Logger;
024:
025: import java.util.Stack;
026: import java.util.Collections;
027: import java.io.IOException;
028:
029: /**
030: * Helper class for handling xml:base attributes.
031: *
032: * <p>Usage:
033: * <ul>
034: * <li>set location of the containing document by calling {@link #setDocumentLocation(String)}.
035: * This is usually done when getting setDocumentLocator SAX event.
036: * <li>forward each startElement and endElement event to this object.
037: * <li>to resolve a relative URL against the current base, call {@link #makeAbsolute(String)}.
038: * </ul>
039: *
040: * <p>External entities are not yet taken into account when determing the current base.
041: */
042: public class XMLBaseSupport {
043: public static final String XMLBASE_NAMESPACE_URI = "http://www.w3.org/XML/1998/namespace";
044: public static final String XMLBASE_ATTRIBUTE = "base";
045:
046: /** Increased on each startElement, decreased on each endElement. */
047: private int level = 0;
048: /**
049: * The stack contains an instance of {@link BaseInfo} for each XML element
050: * that contained an xml:base attribute (not for the other elements).
051: */
052: private Stack bases = new Stack();
053: private SourceResolver resolver;
054: private Logger logger;
055:
056: public XMLBaseSupport(SourceResolver resolver, Logger logger) {
057: this .resolver = resolver;
058: this .logger = logger;
059: }
060:
061: public void setDocumentLocation(String loc) throws SAXException {
062: // -2 is used as level to avoid this BaseInfo to be ever popped of the stack
063: bases.push(new BaseInfo(loc, -2));
064: }
065:
066: public void startElement(String namespaceURI, String localName,
067: String qName, Attributes attrs) throws SAXException {
068: level++;
069: String base = attrs.getValue(XMLBASE_NAMESPACE_URI,
070: XMLBASE_ATTRIBUTE);
071: if (base != null) {
072: Source baseSource = null;
073: String baseUrl;
074: try {
075: baseSource = resolve(getCurrentBase(), base);
076: baseUrl = baseSource.getURI();
077: } finally {
078: if (baseSource != null) {
079: resolver.release(baseSource);
080: }
081: }
082: bases.push(new BaseInfo(baseUrl, level));
083: }
084: }
085:
086: public void endElement(String namespaceURI, String localName,
087: String qName) {
088: if (getCurrentBaseLevel() == level)
089: bases.pop();
090: level--;
091: }
092:
093: /**
094: * Warning: do not forget to release the source returned by this method.
095: */
096: private Source resolve(String baseURI, String location)
097: throws SAXException {
098: try {
099: Source source;
100: if (baseURI != null) {
101: source = resolver.resolveURI(location, baseURI,
102: Collections.EMPTY_MAP);
103: } else {
104: source = resolver.resolveURI(location);
105: }
106: if (logger.isDebugEnabled()) {
107: logger.debug("XMLBaseSupport: resolved location "
108: + location + " against base URI " + baseURI
109: + " to " + source.getURI());
110: }
111: return source;
112: } catch (IOException e) {
113: throw new SAXException(
114: "XMLBaseSupport: problem resolving uri.", e);
115: }
116: }
117:
118: /**
119: * Makes the given path absolute based on the current base URL. Do not forget to release
120: * the returned source object!
121: * @param spec any URL (relative or absolute, containing a scheme or not)
122: */
123: public Source makeAbsolute(String spec) throws SAXException {
124: return resolve(getCurrentBase(), spec);
125: }
126:
127: private String getCurrentBase() {
128: if (bases.size() > 0) {
129: BaseInfo baseInfo = (BaseInfo) bases.peek();
130: return baseInfo.getUrl();
131: }
132: return null;
133: }
134:
135: private int getCurrentBaseLevel() {
136: if (bases.size() > 0) {
137: BaseInfo baseInfo = (BaseInfo) bases.peek();
138: return baseInfo.getLevel();
139: }
140: return -1;
141: }
142:
143: private static final class BaseInfo {
144: private String url;
145: private int level;
146:
147: public BaseInfo(String url, int level) {
148: this .url = url;
149: this .level = level;
150: }
151:
152: public String getUrl() {
153: return url;
154: }
155:
156: public int getLevel() {
157: return level;
158: }
159: }
160: }
|