001: package net.sf.saxon;
002:
003: import net.sf.saxon.event.IDFilter;
004: import net.sf.saxon.event.Stripper;
005: import net.sf.saxon.functions.URIQueryParameters;
006: import net.sf.saxon.om.AllElementStripper;
007: import net.sf.saxon.trans.DynamicError;
008: import net.sf.saxon.trans.XPathException;
009: import net.sf.saxon.value.Whitespace;
010: import org.xml.sax.InputSource;
011: import org.xml.sax.XMLReader;
012:
013: import javax.xml.parsers.SAXParserFactory;
014: import javax.xml.transform.Source;
015: import javax.xml.transform.sax.SAXSource;
016: import java.io.File;
017: import java.io.Serializable;
018: import java.net.MalformedURLException;
019: import java.net.URI;
020: import java.net.URISyntaxException;
021: import java.net.URL;
022:
023: /**
024: * This class provides the service of converting a URI into an InputSource.
025: * It is used to get stylesheet modules referenced by xsl:import and xsl:include,
026: * and source documents referenced by the document() function. The standard version
027: * handles anything that the java URL class will handle.
028: * You can write a subclass to handle other kinds of URI, e.g. references to things in
029: * a database.
030: * @author Michael H. Kay
031: */
032:
033: public class StandardURIResolver implements NonDelegatingURIResolver,
034: Serializable {
035:
036: // TODO: support the data: URI scheme. (Requires unescaping of the URI, then parsing the content as XML)
037:
038: private Configuration config = null;
039: protected boolean recognizeQueryParameters = false;
040:
041: /**
042: * Create a StandardURIResolver, with no reference to a TransformerFactory
043: */
044: public StandardURIResolver() {
045: this (null);
046: }
047:
048: /**
049: * Create a StandardURIResolver, with a reference to a TransformerFactory
050: * @param config The Configuration object.
051: * This is used to get a SAX Parser for a source XML document
052: */
053:
054: public StandardURIResolver(Configuration config) {
055: this .config = config;
056: }
057:
058: /**
059: * Indicate that query parameters (such as ?validation=strict) are to be recognized
060: * @param recognize Set to true if query parameters in the URI are to be recognized and acted upon.
061: * The default (for compatibility and interoperability reasons) is false.
062: */
063:
064: public void setRecognizeQueryParameters(boolean recognize) {
065: recognizeQueryParameters = recognize;
066: }
067:
068: /**
069: * Determine whether query parameters (such as ?validation=strict) are to be recognized
070: * @return true if query parameters are recognized and interpreted by Saxon.
071: */
072:
073: public boolean queryParametersAreRecognized() {
074: return recognizeQueryParameters;
075: }
076:
077: /**
078: * Resolve a URI
079: * @param href The relative or absolute URI. May be an empty string. May contain
080: * a fragment identifier starting with "#", which must be the value of an ID attribute
081: * in the referenced XML document.
082: * @param base The base URI that should be used. May be null if uri is absolute.
083: * @return a Source object representing an XML document
084: */
085:
086: public Source resolve(String href, String base)
087: throws XPathException {
088:
089: // System.err.println("StandardURIResolver, href=" + href + ", base=" + base);
090:
091: String relativeURI = href;
092: String id = null;
093:
094: // Extract any fragment identifier. Note, this code is no longer used to
095: // resolve fragment identifiers in URI references passed to the document()
096: // function: the code of the document() function handles these itself.
097:
098: int hash = href.indexOf('#');
099: if (hash >= 0) {
100: relativeURI = href.substring(0, hash);
101: id = href.substring(hash + 1);
102: // System.err.println("StandardURIResolver, href=" + href + ", id=" + id);
103: }
104:
105: URIQueryParameters params = null;
106: URI url;
107: URI relative;
108: try {
109: relativeURI = escapeSpaces(relativeURI);
110: relative = new URI(relativeURI);
111: } catch (URISyntaxException err) {
112: throw new DynamicError("Invalid relative URI "
113: + Err.wrap(relativeURI), err);
114: }
115:
116: String query = relative.getQuery();
117: if (query != null && recognizeQueryParameters) {
118: params = new URIQueryParameters(query, config);
119: int q = relativeURI.indexOf('?');
120: relativeURI = relativeURI.substring(0, q);
121: }
122:
123: Source source = null;
124: if (recognizeQueryParameters && relativeURI.endsWith(".ptree")) {
125: source = getPTreeSource(relativeURI, base);
126: }
127:
128: if (source == null) {
129: try {
130: url = makeAbsolute(relativeURI, base);
131: } catch (URISyntaxException err) {
132: // System.err.println("Recovering from " + err);
133: // last resort: if the base URI is null, or is itself a relative URI, we
134: // try to expand it relative to the current working directory
135: String expandedBase = tryToExpand(base);
136: if (!expandedBase.equals(base)) { // prevent infinite recursion
137: return resolve(href, expandedBase);
138: }
139: //err.printStackTrace();
140: throw new DynamicError("Invalid URI "
141: + Err.wrap(relativeURI) + " - base "
142: + Err.wrap(base), err);
143: }
144:
145: source = new SAXSource();
146: ((SAXSource) source).setInputSource(new InputSource(url
147: .toString()));
148: source.setSystemId(url.toString());
149:
150: if (params != null) {
151: XMLReader parser = params.getXMLReader();
152: if (parser != null) {
153: ((SAXSource) source).setXMLReader(parser);
154: }
155: }
156:
157: if (((SAXSource) source).getXMLReader() == null) {
158: if (config == null) {
159: try {
160: ((SAXSource) source)
161: .setXMLReader(SAXParserFactory
162: .newInstance().newSAXParser()
163: .getXMLReader());
164: } catch (Exception err) {
165: throw new DynamicError(err);
166: }
167: } else {
168: //((SAXSource)source).setXMLReader(config.getSourceParser());
169: // Leave the Sender to allocate an XMLReader, so that it can be returned to the pool after use
170: }
171: }
172: }
173:
174: if (params != null) {
175: int stripSpace = params.getStripSpace();
176: switch (stripSpace) {
177: case Whitespace.ALL: {
178: Stripper stripper = AllElementStripper.getInstance();
179: stripper.setStripAll();
180: source = AugmentedSource.makeAugmentedSource(source);
181: ((AugmentedSource) source).addFilter(stripper);
182: break;
183: }
184: case Whitespace.IGNORABLE:
185: case Whitespace.NONE:
186: source = AugmentedSource.makeAugmentedSource(source);
187: ((AugmentedSource) source).setStripSpace(stripSpace);
188: }
189: }
190:
191: if (id != null) {
192: IDFilter filter = new IDFilter(id);
193: source = AugmentedSource.makeAugmentedSource(source);
194: ((AugmentedSource) source).addFilter(filter);
195: }
196:
197: if (params != null) {
198: Integer validation = params.getValidationMode();
199: if (validation != null) {
200: source = AugmentedSource.makeAugmentedSource(source);
201: ((AugmentedSource) source)
202: .setSchemaValidationMode(validation.intValue());
203: }
204: }
205:
206: return source;
207: }
208:
209: /**
210: * Combine the relative URI and base URI
211: */
212:
213: public static URI makeAbsolute(String relativeURI, String base)
214: throws DynamicError, URISyntaxException {
215: URI url;
216: relativeURI = escapeSpaces(relativeURI);
217: base = escapeSpaces(base);
218: try {
219: if (base == null) {
220: url = new URI(relativeURI);
221: if (!url.isAbsolute()) {
222: String expandedBase = tryToExpand(base);
223: if (!expandedBase.equals(base)) { // prevent infinite recursion
224: return makeAbsolute(relativeURI, expandedBase);
225: }
226: }
227: // System.err.println("Resolved " + relativeURI + " as " + url.toString());
228: } else {
229: // System.err.println("Resolving " + relativeURI + " against " + base);
230: URI baseURL = new URI(base);
231: // System.err.println("Base URI " + base);
232: url = (relativeURI.length() == 0 ? baseURL : baseURL
233: .resolve(relativeURI));
234: // Note: an older version of this method incorrectly double-escaped percent signs,
235: // for example %20 was escaped to %2520. We removed them by hand, as follows. But
236: // the problem seems to have gone away (test mdocs31)
237: // String u = url.toString();
238: // int pc = u.indexOf("%25");
239: // if (pc >= 0) {
240: // while (pc>=0) {
241: // u = u.substring(0, pc+1) + u.substring(pc+3);
242: // pc = u.indexOf("%25");
243: // }
244: // url = new URI(u);
245: // }
246: // System.err.println("Resolved URI " + url);
247: }
248: } catch (IllegalArgumentException err0) {
249: // can be thrown by resolve() when given a bad URI
250: throw new DynamicError("Invalid URI "
251: + Err.wrap(relativeURI) + " - base "
252: + Err.wrap(base));
253: }
254: return url;
255: }
256:
257: /**
258: * Replace spaces by %20
259: */
260:
261: public static String escapeSpaces(String s) {
262: // It's not entirely clear why we have to escape spaces by hand, and not other special characters;
263: // it's just that tests with a variety of filenames show that this approach seems to work.
264: if (s == null)
265: return s;
266: int i = s.indexOf(' ');
267: if (i < 0) {
268: return s;
269: }
270: return (i == 0 ? "" : s.substring(0, i))
271: + "%20"
272: + (i == s.length() - 1 ? "" : escapeSpaces(s
273: .substring(i + 1)));
274: }
275:
276: /**
277: * If a system ID can't be parsed as a URL, we'll try to expand it as a relative
278: * URI using the current directory as the base URI: MHK addition.
279: */
280:
281: public static String tryToExpand(String systemId) {
282: if (systemId == null) {
283: systemId = "";
284: }
285: try {
286: new URL(systemId);
287: return systemId; // all is well
288: } catch (MalformedURLException err) {
289: String dir;
290: try {
291: dir = System.getProperty("user.dir");
292: } catch (Exception geterr) {
293: // this doesn't work when running an applet
294: return systemId;
295: }
296: if (!(dir.endsWith("/") || systemId.startsWith("/"))) {
297: dir = dir + '/';
298: }
299:
300: try {
301: URL currentDirectoryURL = new File(dir).toURL();
302: URL baseURL = new URL(currentDirectoryURL, systemId);
303: // System.err.println("SAX Driver: expanded " + systemId + " to " + baseURL);
304: return baseURL.toString();
305: } catch (MalformedURLException err2) {
306: // go with the original one
307: return systemId;
308: }
309: }
310: }
311:
312: /**
313: * Handle a PTree source file (Saxon-SA only)
314: */
315:
316: protected Source getPTreeSource(String href, String base)
317: throws XPathException {
318: return null;
319: }
320:
321: }
322:
323: //
324: // The contents of this file are subject to the Mozilla Public License Version 1.0 (the "License");
325: // you may not use this file except in compliance with the License. You may obtain a copy of the
326: // License at http://www.mozilla.org/MPL/
327: //
328: // Software distributed under the License is distributed on an "AS IS" basis,
329: // WITHOUT WARRANTY OF ANY KIND, either express or implied.
330: // See the License for the specific language governing rights and limitations under the License.
331: //
332: // The Original Code is: all this file.
333: //
334: // The Initial Developer of the Original Code is Michael H. Kay.
335: //
336: // Portions created by (your name) are Copyright (C) (your legal entity). All Rights Reserved.
337: //
338: // Contributor(s): none.
339: //
|