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.components.xpointer;
018:
019: import org.xml.sax.SAXException;
020: import org.xml.sax.Attributes;
021: import org.xml.sax.Locator;
022: import org.apache.cocoon.xml.AbstractXMLPipe;
023: import org.apache.cocoon.components.source.SourceUtil;
024: import org.apache.cocoon.ProcessingException;
025:
026: import java.util.StringTokenizer;
027: import java.util.ArrayList;
028: import java.io.IOException;
029:
030: /**
031: * A custom XPointer scheme that allows to include the content of a specific element without
032: * building a DOM. The element must be specified using an absolute path reference such as
033: * <tt>/html/body</tt>. Namespace prefixes within these element names are supported.
034: *
035: * <p>This xpointer scheme will always be succesful (thus any further xpointer parts will
036: * never be executed).
037: *
038: * <p>The scheme name for this XPointer scheme is 'elementpath' and its namespace is
039: * http://apache.org/cocoon/xpointer.
040: *
041: * <p>See the samples for a usage example.
042: */
043: public class ElementPathPart implements PointerPart {
044: private String expression;
045:
046: public ElementPathPart(String expression) {
047: this .expression = expression;
048: }
049:
050: public boolean process(XPointerContext xpointerContext)
051: throws SAXException {
052: PathInclusionPipe pipe = new PathInclusionPipe(expression,
053: xpointerContext);
054: pipe.setConsumer(xpointerContext.getXmlConsumer());
055: try {
056: SourceUtil.toSAX(xpointerContext.getSource(), pipe);
057: } catch (IOException e) {
058: throw new SAXException(
059: "Exception while trying to XInclude data: "
060: + e.getMessage(), e);
061: } catch (ProcessingException e) {
062: throw new SAXException(
063: "Exception while trying to XInclude data: "
064: + e.getMessage(), e);
065: }
066: return true;
067: }
068:
069: public static class PathInclusionPipe extends AbstractXMLPipe {
070: /** The QNames that must be matched before inclusion can start. */
071: private QName[] elementPath;
072: /** The current element nesting level. */
073: private int level;
074: /** Should we currently be including? */
075: private boolean include;
076: /** The element nesting level since we started inclusion, used to know when to stop inclusion. */
077: private int includeLevel;
078:
079: /** The element nesting level that should currently be matched. */
080: private int levelToMatch;
081: private boolean done;
082:
083: public PathInclusionPipe(String expression,
084: XPointerContext xpointerContext) throws SAXException {
085: // parse the expression to an array of QName objects
086: ArrayList path = new ArrayList();
087: StringTokenizer tokenizer = new StringTokenizer(expression,
088: "/");
089: while (tokenizer.hasMoreTokens()) {
090: String token = tokenizer.nextToken();
091: try {
092: path.add(QName.parse(token, xpointerContext));
093: } catch (SAXException e) {
094: throw new SAXException(
095: "Error in element path xpointer expression \""
096: + expression + "\": "
097: + e.getMessage());
098: }
099: }
100: if (path.size() < 1)
101: throw new SAXException(
102: "Invalid element path xpointer expression \""
103: + expression + "\".");
104:
105: this .elementPath = (QName[]) path.toArray(new QName[path
106: .size()]);
107: this .level = -1;
108: this .include = false;
109: this .levelToMatch = 0;
110: this .done = false;
111: }
112:
113: public void startElement(String namespaceURI, String localName,
114: String raw, Attributes a) throws SAXException {
115: level++;
116:
117: if (include) {
118: super .startElement(namespaceURI, localName, raw, a);
119: return;
120: }
121:
122: if (!done
123: && level == levelToMatch
124: && elementPath[level].matches(namespaceURI,
125: localName)) {
126: levelToMatch++;
127: if (levelToMatch == elementPath.length) {
128: include = true;
129: done = true;
130: includeLevel = level;
131: }
132: }
133: }
134:
135: public void endElement(String uri, String loc, String raw)
136: throws SAXException {
137: if (include && level == includeLevel)
138: include = false;
139:
140: if (include)
141: super .endElement(uri, loc, raw);
142:
143: level--;
144: }
145:
146: public void setDocumentLocator(Locator locator) {
147: if (include)
148: super .setDocumentLocator(locator);
149: }
150:
151: public void startDocument() throws SAXException {
152: if (include)
153: super .startDocument();
154: }
155:
156: public void endDocument() throws SAXException {
157: if (include)
158: super .endDocument();
159: }
160:
161: public void characters(char c[], int start, int len)
162: throws SAXException {
163: if (include)
164: super .characters(c, start, len);
165: }
166:
167: public void ignorableWhitespace(char c[], int start, int len)
168: throws SAXException {
169: if (include)
170: super .ignorableWhitespace(c, start, len);
171: }
172:
173: public void processingInstruction(String target, String data)
174: throws SAXException {
175: if (include)
176: super .processingInstruction(target, data);
177: }
178:
179: public void skippedEntity(String name) throws SAXException {
180: if (include)
181: super .skippedEntity(name);
182: }
183:
184: public void startDTD(String name, String publicId,
185: String systemId) throws SAXException {
186: if (include)
187: super .startDTD(name, publicId, systemId);
188: }
189:
190: public void endDTD() throws SAXException {
191: if (include)
192: super .endDTD();
193: }
194:
195: public void startEntity(String name) throws SAXException {
196: if (include)
197: super .startEntity(name);
198: }
199:
200: public void endEntity(String name) throws SAXException {
201: if (include)
202: super .endEntity(name);
203: }
204:
205: public void startCDATA() throws SAXException {
206: if (include)
207: super .startCDATA();
208: }
209:
210: public void endCDATA() throws SAXException {
211: if (include)
212: super .endCDATA();
213: }
214:
215: public void comment(char ch[], int start, int len)
216: throws SAXException {
217: if (include)
218: super .comment(ch, start, len);
219: }
220:
221: public static class QName {
222: private String namespaceURI;
223: private String localName;
224:
225: public QName(String namespaceURI, String localName) {
226: this .namespaceURI = namespaceURI;
227: this .localName = localName;
228: }
229:
230: public static QName parse(String qName,
231: XPointerContext xpointerContext)
232: throws SAXException {
233: int pos = qName.indexOf(':');
234: if (pos > 0) {
235: String prefix = qName.substring(0, pos);
236: String localName = qName.substring(pos + 1);
237: String namespaceURI = xpointerContext
238: .prefixToNamespace(prefix);
239: if (namespaceURI == null)
240: throw new SAXException("Namespace prefix \""
241: + prefix + "\" not declared.");
242: return new QName(prefix, localName);
243: }
244: return new QName("", qName);
245: }
246:
247: public String getNamespaceURI() {
248: return namespaceURI;
249: }
250:
251: public String getLocalName() {
252: return localName;
253: }
254:
255: public boolean matches(String namespaceURI, String localName) {
256: return this.localName.equals(localName)
257: && this.namespaceURI.equals(namespaceURI);
258: }
259: }
260: }
261: }
|