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.util.location;
018:
019: import org.w3c.dom.Attr;
020: import org.w3c.dom.Element;
021: import org.w3c.dom.Node;
022: import org.w3c.dom.NodeList;
023: import org.xml.sax.Attributes;
024: import org.xml.sax.ContentHandler;
025: import org.xml.sax.Locator;
026: import org.xml.sax.SAXException;
027: import org.xml.sax.helpers.AttributesImpl;
028:
029: /**
030: * A class to handle location information stored in attributes.
031: * These attributes are typically setup using {@link org.apache.cocoon.util.location.LocationAttributes.Pipe}
032: * which augments the SAX stream with additional attributes, e.g.:
033: * <pre>
034: * <root xmlns:loc="http://apache.org/cocoon/location"
035: * loc:src="file://path/to/file.xml"
036: * loc:line="1" loc:column="1">
037: * <foo loc:src="file://path/to/file.xml" loc:line="2" loc:column="3"/>
038: * </root>
039: * </pre>
040: *
041: * @see org.apache.cocoon.util.location.LocationAttributes.Pipe
042: * @since 2.1.8
043: * @version $Id: LocationAttributes.java 446919 2006-09-16 19:19:25Z vgritsenko $
044: */
045: public class LocationAttributes {
046: /** Prefix for the location namespace */
047: public static final String PREFIX = "loc";
048: /** Namespace URI for location attributes */
049: public static final String URI = "http://apache.org/cocoon/location";
050:
051: /** Attribute name for the location URI */
052: public static final String SRC_ATTR = "src";
053: /** Attribute name for the line number */
054: public static final String LINE_ATTR = "line";
055: /** Attribute name for the column number */
056: public static final String COL_ATTR = "column";
057:
058: /** Attribute qualified name for the location URI */
059: public static final String Q_SRC_ATTR = "loc:src";
060: /** Attribute qualified name for the line number */
061: public static final String Q_LINE_ATTR = "loc:line";
062: /** Attribute qualified name for the column number */
063: public static final String Q_COL_ATTR = "loc:column";
064:
065: // Private constructor, we only have static methods
066: private LocationAttributes() {
067: // Nothing
068: }
069:
070: /**
071: * Add location attributes to a set of SAX attributes.
072: *
073: * @param locator the <code>Locator</code> (can be null)
074: * @param attrs the <code>Attributes</code> where locator information should be added
075: */
076: public static Attributes addLocationAttributes(Locator locator,
077: Attributes attrs) {
078: if (locator == null || attrs.getIndex(URI, SRC_ATTR) != -1) {
079: // No location information known, or already has it
080: return attrs;
081: }
082:
083: // Get an AttributeImpl so that we can add new attributes.
084: AttributesImpl newAttrs = attrs instanceof AttributesImpl ? (AttributesImpl) attrs
085: : new AttributesImpl(attrs);
086:
087: newAttrs.addAttribute(URI, SRC_ATTR, Q_SRC_ATTR, "CDATA",
088: locator.getSystemId());
089: newAttrs.addAttribute(URI, LINE_ATTR, Q_LINE_ATTR, "CDATA",
090: Integer.toString(locator.getLineNumber()));
091: newAttrs.addAttribute(URI, COL_ATTR, Q_COL_ATTR, "CDATA",
092: Integer.toString(locator.getColumnNumber()));
093:
094: return newAttrs;
095: }
096:
097: /**
098: * Returns the {@link Location} of an element (SAX flavor).
099: *
100: * @param attrs the element's attributes that hold the location information
101: * @param description a description for the location (can be null)
102: * @return a {@link Location} object
103: */
104: public static Location getLocation(Attributes attrs,
105: String description) {
106: String src = attrs.getValue(URI, SRC_ATTR);
107: if (src == null) {
108: return Location.UNKNOWN;
109: }
110:
111: return new LocationImpl(description, src, getLine(attrs),
112: getColumn(attrs));
113: }
114:
115: /**
116: * Returns the location of an element (SAX flavor). If the location is to be kept
117: * into an object built from this element, consider using {@link #getLocation(Attributes, String)}
118: * and the {@link Locatable} interface.
119: *
120: * @param attrs the element's attributes that hold the location information
121: * @return a location string as defined by {@link Location#toString()}.
122: */
123: public static String getLocationString(Attributes attrs) {
124: String src = attrs.getValue(URI, SRC_ATTR);
125: if (src == null) {
126: return LocationUtils.UNKNOWN_STRING;
127: }
128:
129: return src + ":" + attrs.getValue(URI, LINE_ATTR) + ":"
130: + attrs.getValue(URI, COL_ATTR);
131: }
132:
133: /**
134: * Returns the URI of an element (SAX flavor)
135: *
136: * @param attrs the element's attributes that hold the location information
137: * @return the element's URI or "<code>[unknown location]</code>" if <code>attrs</code>
138: * has no location information.
139: */
140: public static String getURI(Attributes attrs) {
141: String src = attrs.getValue(URI, SRC_ATTR);
142: return src != null ? src : LocationUtils.UNKNOWN_STRING;
143: }
144:
145: /**
146: * Returns the line number of an element (SAX flavor)
147: *
148: * @param attrs the element's attributes that hold the location information
149: * @return the element's line number or <code>-1</code> if <code>attrs</code>
150: * has no location information.
151: */
152: public static int getLine(Attributes attrs) {
153: String line = attrs.getValue(URI, LINE_ATTR);
154: return line != null ? Integer.parseInt(line) : -1;
155: }
156:
157: /**
158: * Returns the column number of an element (SAX flavor)
159: *
160: * @param attrs the element's attributes that hold the location information
161: * @return the element's column number or <code>-1</code> if <code>attrs</code>
162: * has no location information.
163: */
164: public static int getColumn(Attributes attrs) {
165: String col = attrs.getValue(URI, COL_ATTR);
166: return col != null ? Integer.parseInt(col) : -1;
167: }
168:
169: /**
170: * Returns the {@link Location} of an element (DOM flavor).
171: *
172: * @param elem the element that holds the location information
173: * @param description a description for the location (if <code>null</code>, the element's name is used)
174: * @return a {@link Location} object
175: */
176: public static Location getLocation(Element elem, String description) {
177: Attr srcAttr = elem.getAttributeNodeNS(URI, SRC_ATTR);
178: if (srcAttr == null) {
179: return Location.UNKNOWN;
180: }
181:
182: return new LocationImpl(description == null ? "<"
183: + elem.getNodeName() + ">" : description, srcAttr
184: .getValue(), getLine(elem), getColumn(elem));
185: }
186:
187: /**
188: * Same as <code>getLocation(elem, null)</code>.
189: */
190: public static Location getLocation(Element elem) {
191: return getLocation(elem, null);
192: }
193:
194: /**
195: * Returns the location of an element that has been processed by this pipe (DOM flavor).
196: * If the location is to be kept into an object built from this element, consider using
197: * {@link #getLocation(Element)} and the {@link Locatable} interface.
198: *
199: * @param elem the element that holds the location information
200: * @return a location string as defined by {@link Location#toString()}.
201: */
202: public static String getLocationString(Element elem) {
203: Attr srcAttr = elem.getAttributeNodeNS(URI, SRC_ATTR);
204: if (srcAttr == null) {
205: return LocationUtils.UNKNOWN_STRING;
206: }
207:
208: return srcAttr.getValue() + ":"
209: + elem.getAttributeNS(URI, LINE_ATTR) + ":"
210: + elem.getAttributeNS(URI, COL_ATTR);
211: }
212:
213: /**
214: * Returns the URI of an element (DOM flavor)
215: *
216: * @param elem the element that holds the location information
217: * @return the element's URI or "<code>[unknown location]</code>" if <code>elem</code>
218: * has no location information.
219: */
220: public static String getURI(Element elem) {
221: Attr attr = elem.getAttributeNodeNS(URI, SRC_ATTR);
222: return attr != null ? attr.getValue()
223: : LocationUtils.UNKNOWN_STRING;
224: }
225:
226: /**
227: * Returns the line number of an element (DOM flavor)
228: *
229: * @param elem the element that holds the location information
230: * @return the element's line number or <code>-1</code> if <code>elem</code>
231: * has no location information.
232: */
233: public static int getLine(Element elem) {
234: Attr attr = elem.getAttributeNodeNS(URI, LINE_ATTR);
235: return attr != null ? Integer.parseInt(attr.getValue()) : -1;
236: }
237:
238: /**
239: * Returns the column number of an element (DOM flavor)
240: *
241: * @param elem the element that holds the location information
242: * @return the element's column number or <code>-1</code> if <code>elem</code>
243: * has no location information.
244: */
245: public static int getColumn(Element elem) {
246: Attr attr = elem.getAttributeNodeNS(URI, COL_ATTR);
247: return attr != null ? Integer.parseInt(attr.getValue()) : -1;
248: }
249:
250: /**
251: * Remove the location attributes from a DOM element.
252: *
253: * @param elem the element to remove the location attributes from.
254: * @param recurse if <code>true</code>, also remove location attributes on descendant elements.
255: */
256: public static void remove(Element elem, boolean recurse) {
257: elem.removeAttributeNS(URI, SRC_ATTR);
258: elem.removeAttributeNS(URI, LINE_ATTR);
259: elem.removeAttributeNS(URI, COL_ATTR);
260: if (recurse) {
261: NodeList children = elem.getChildNodes();
262: for (int i = 0; i < children.getLength(); i++) {
263: Node child = children.item(i);
264: if (child.getNodeType() == Node.ELEMENT_NODE) {
265: remove((Element) child, recurse);
266: }
267: }
268: }
269: }
270:
271: /**
272: * A SAX filter that adds the information available from the <code>Locator</code> as attributes.
273: * The purpose of having location as attributes is to allow this information to survive transformations
274: * of the document (an XSL could copy these attributes over) or conversion of SAX events to a DOM.
275: * <p>
276: * The location is added as 3 attributes in a specific namespace to each element.
277: * <pre>
278: * <root xmlns:loc="http://apache.org/cocoon/location"
279: * loc:src="file://path/to/file.xml"
280: * loc:line="1" loc:column="1">
281: * <foo loc:src="file://path/to/file.xml" loc:line="2" loc:column="3"/>
282: * </root>
283: * </pre>
284: * <strong>Note:</strong> Although this adds a lot of information to the serialized form of the document,
285: * the overhead in SAX events is not that big, as attribute names are interned, and all <code>src</code>
286: * attributes point to the same string.
287: *
288: * @see org.apache.cocoon.util.location.LocationAttributes
289: * @since 2.1.8
290: */
291: public static class Pipe implements ContentHandler {
292:
293: private Locator locator;
294:
295: private ContentHandler nextHandler;
296:
297: /**
298: * Create a filter. It has to be chained to another handler to be really useful.
299: */
300: public Pipe() {
301: }
302:
303: /**
304: * Create a filter that is chained to another handler.
305: * @param next the next handler in the chain.
306: */
307: public Pipe(ContentHandler next) {
308: nextHandler = next;
309: }
310:
311: public void setDocumentLocator(Locator locator) {
312: this .locator = locator;
313: nextHandler.setDocumentLocator(locator);
314: }
315:
316: public void startDocument() throws SAXException {
317: nextHandler.startDocument();
318: nextHandler.startPrefixMapping(LocationAttributes.PREFIX,
319: LocationAttributes.URI);
320: }
321:
322: public void endDocument() throws SAXException {
323: endPrefixMapping(LocationAttributes.PREFIX);
324: nextHandler.endDocument();
325: }
326:
327: public void startElement(String uri, String loc, String raw,
328: Attributes attrs) throws SAXException {
329: // Add location attributes to the element
330: nextHandler.startElement(uri, loc, raw, LocationAttributes
331: .addLocationAttributes(locator, attrs));
332: }
333:
334: public void endElement(String arg0, String arg1, String arg2)
335: throws SAXException {
336: nextHandler.endElement(arg0, arg1, arg2);
337: }
338:
339: public void startPrefixMapping(String arg0, String arg1)
340: throws SAXException {
341: nextHandler.startPrefixMapping(arg0, arg1);
342: }
343:
344: public void endPrefixMapping(String arg0) throws SAXException {
345: nextHandler.endPrefixMapping(arg0);
346: }
347:
348: public void characters(char[] arg0, int arg1, int arg2)
349: throws SAXException {
350: nextHandler.characters(arg0, arg1, arg2);
351: }
352:
353: public void ignorableWhitespace(char[] arg0, int arg1, int arg2)
354: throws SAXException {
355: nextHandler.ignorableWhitespace(arg0, arg1, arg2);
356: }
357:
358: public void processingInstruction(String arg0, String arg1)
359: throws SAXException {
360: nextHandler.processingInstruction(arg0, arg1);
361: }
362:
363: public void skippedEntity(String arg0) throws SAXException {
364: nextHandler.skippedEntity(arg0);
365: }
366: }
367: }
|