001: /*
002: * Copyright 2003 The Apache Software Foundation.
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016:
017: package velosurf.util;
018:
019: import java.util.List;
020: import java.util.ArrayList;
021: import java.net.URL;
022: import java.io.FileInputStream;
023: import java.io.InputStream;
024: import java.io.BufferedReader;
025: import java.io.InputStreamReader;
026: import java.io.StringReader;
027:
028: import javax.servlet.ServletContext;
029:
030: import org.jdom.Document;
031: import org.jdom.Element;
032: import org.jdom.Content;
033: import org.jdom.Text;
034: import org.jdom.input.SAXBuilder;
035:
036: /** <p>A basic JDOM XInclude resolver that will also work with a document base inside WEB-INF
037: * and with war archives.</p>
038: *
039: * @author <a href="mailto:claude.brisson@gmail.com">Claude Brisson</a>
040: */
041:
042: public class XIncludeResolver {
043: /** base directory */
044: private String base = null;
045: /** servlet context */
046: private ServletContext context = null;
047:
048: /**
049: * Constructor for a webapp resolver.
050: * @param base base directory
051: * @param ctx servlet context
052: */
053: public XIncludeResolver(String base, ServletContext ctx) {
054: this .base = base;
055: this .context = ctx;
056: }
057:
058: /**
059: * Constructor outside a webapp.
060: * @param base base directory
061: */
062: public XIncludeResolver(String base) {
063: this .base = base;
064: }
065:
066: /**
067: * Resolve includes in the document.
068: * @param doc document to be resolved
069: * @return document with includes resolved
070: * @throws Exception
071: */
072: public Document resolve(Document doc) throws Exception {
073: Element root = doc.getRootElement();
074: /* special case where the root element is an XInclude element */
075: if (isXIncludeElement(root)) {
076: int pos = doc.indexOf(root);
077: List<Content> resolved = include(root);
078: /* doc.detachRootElement(); */
079: doc.setContent(pos, resolved);
080: } else {
081: resolveChildren(root);
082: }
083: return doc;
084: }
085:
086: /**
087: * Check whether this element is an include element.
088: * @param element element to check
089: * @return true if this element is an include element
090: */
091: private boolean isXIncludeElement(Element element) {
092: return element.getName().equals("include")
093: && element.getNamespacePrefix().equals("xi");
094: }
095:
096: /**
097: * Resolve children XML elements.
098: * @param parent parent XML element
099: * @throws Exception
100: */
101: private void resolveChildren(Element parent) throws Exception {
102: List<Element> children = (List<Element>) parent.getChildren();
103: for (int i = 0; i < children.size(); i++) {
104: Element child = (Element) children.get(i);
105: if (isXIncludeElement((Element) child)) {
106: int pos = parent.indexOf(child);
107: List<Content> resolved = include((Element) child);
108: parent.setContent(pos, resolved);
109: } else {
110: resolveChildren(child);
111: }
112: }
113: }
114:
115: /**
116: * Performs the real include.
117: * @param xinclude xinclude element
118: * @return included content
119: * @throws Exception
120: */
121: private List<Content> include(Element xinclude) throws Exception {
122: List<Content> result = null;
123: String href = xinclude.getAttributeValue("href");
124: String base = xinclude.getAttributeValue("base", this .base);
125: boolean parse = true;
126: String p = xinclude.getAttributeValue("parse");
127: if (p != null) {
128: parse = "xml".equals(p);
129: }
130: assert (href != null && base != null);
131: String content = null;
132: int i = href.indexOf(':');
133: if (i != -1) {
134: /* absolute URL... */
135: Logger.info("XInclude: including element " + href);
136: content = readStream(new URL(href).openStream());
137: } else {
138: if (!href.startsWith("/")) {
139: if (base.length() == 0) {
140: base = "./";
141: } else if (!base.endsWith("/")) {
142: base += "/";
143: }
144: href = base + href;
145: }
146: Logger
147: .info("XInclude: including element "
148: + href
149: + (context == null ? " (using absolute pathname)"
150: : " (using provided servlet context, pathname is relative to Webapp root)"));
151: InputStream stream = (context == null ? new FileInputStream(
152: href)
153: : context.getResourceAsStream(href));
154: if (stream == null) {
155: throw new Exception("XInclude: included file " + href
156: + " not found!");
157: }
158: content = readStream(stream);
159: }
160: if (parse) {
161: content = "<?xml version=\"1.0\" encoding=\"UTF-8\" ?><root xmlns:xi=\"http://www.w3.org/2001/XInclude\">"
162: + content + "</root>";
163: Document parsed = resolve(new SAXBuilder()
164: .build(new StringReader(content))); /* TODO yet to prevent cyclic inclusions...*/
165: result = (List<Content>) parsed.getRootElement()
166: .removeContent();
167: } else {
168: result = new ArrayList<Content>();
169: result.add(new Text(content));
170: }
171: return result;
172: }
173:
174: /**
175: * Read a stream in a string.
176: * @param stream stream
177: * @return accumulated string
178: * @throws Exception
179: */
180: private String readStream(InputStream stream) throws Exception {
181: StringBuilder result = new StringBuilder();
182: BufferedReader reader = new BufferedReader(
183: new InputStreamReader(stream));
184: String line;
185: while ((line = reader.readLine()) != null) {
186: result.append(line);
187: result.append('\n');
188: }
189: return result.toString();
190: }
191: }
|