001: /*
002: * Copyright 2005 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: package com.opensymphony.xwork.util.location;
017:
018: import org.w3c.dom.Attr;
019: import org.w3c.dom.Element;
020: import org.w3c.dom.Node;
021: import org.w3c.dom.NodeList;
022: import org.xml.sax.Attributes;
023: import org.xml.sax.ContentHandler;
024: import org.xml.sax.Locator;
025: import org.xml.sax.SAXException;
026: import org.xml.sax.helpers.AttributesImpl;
027:
028: /**
029: * A class to handle location information stored in attributes.
030: * These attributes are typically setup using {@link com.opensymphony.xwork.util.location.LocationAttributes.Pipe}
031: * which augments the SAX stream with additional attributes, e.g.:
032: * <pre>
033: * <root xmlns:loc="http://opensymphony.com/xwork/location"
034: * loc:src="file://path/to/file.xml"
035: * loc:line="1" loc:column="1">
036: * <foo loc:src="file://path/to/file.xml" loc:line="2" loc:column="3"/>
037: * </root>
038: * </pre>
039: *
040: * @see com.opensymphony.xwork.util.location.LocationAttributes.Pipe
041: * @since 2.1.8
042: * @version $Id: LocationAttributes.java 983 2006-04-04 00:37:47Z mrdon $
043: */
044: public class LocationAttributes {
045: /** Prefix for the location namespace */
046: public static final String PREFIX = "loc";
047: /** Namespace URI for location attributes */
048: public static final String URI = "http://opensymphony.com/xwork/location";
049:
050: /** Attribute name for the location URI */
051: public static final String SRC_ATTR = "src";
052: /** Attribute name for the line number */
053: public static final String LINE_ATTR = "line";
054: /** Attribute name for the column number */
055: public static final String COL_ATTR = "column";
056:
057: /** Attribute qualified name for the location URI */
058: public static final String Q_SRC_ATTR = "loc:src";
059: /** Attribute qualified name for the line number */
060: public static final String Q_LINE_ATTR = "loc:line";
061: /** Attribute qualified name for the column number */
062: public static final String Q_COL_ATTR = "loc:column";
063:
064: // Private constructor, we only have static methods
065: private LocationAttributes() {
066: // Nothing
067: }
068:
069: /**
070: * Add location attributes to a set of SAX attributes.
071: *
072: * @param locator the <code>Locator</code> (can be null)
073: * @param attrs the <code>Attributes</code> where locator information should be added
074: * @return
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)}
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 attrs 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 ? elem
183: .getNodeName() : description, srcAttr.getValue(),
184: 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://opensymphony.com/xwork/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 com.opensymphony.xwork.util.location.LocationAttributes
289: */
290: public static class Pipe implements ContentHandler {
291:
292: private Locator locator;
293:
294: private ContentHandler nextHandler;
295:
296: /**
297: * Create a filter. It has to be chained to another handler to be really useful.
298: */
299: public Pipe() {
300: }
301:
302: /**
303: * Create a filter that is chained to another handler.
304: * @param next the next handler in the chain.
305: */
306: public Pipe(ContentHandler next) {
307: nextHandler = next;
308: }
309:
310: public void setDocumentLocator(Locator locator) {
311: this .locator = locator;
312: nextHandler.setDocumentLocator(locator);
313: }
314:
315: public void startDocument() throws SAXException {
316: nextHandler.startDocument();
317: nextHandler.startPrefixMapping(LocationAttributes.PREFIX,
318: LocationAttributes.URI);
319: }
320:
321: public void endDocument() throws SAXException {
322: endPrefixMapping(LocationAttributes.PREFIX);
323: nextHandler.endDocument();
324: }
325:
326: public void startElement(String uri, String loc, String raw,
327: Attributes attrs) throws SAXException {
328: // Add location attributes to the element
329: nextHandler.startElement(uri, loc, raw, LocationAttributes
330: .addLocationAttributes(locator, attrs));
331: }
332:
333: public void endElement(String arg0, String arg1, String arg2)
334: throws SAXException {
335: nextHandler.endElement(arg0, arg1, arg2);
336: }
337:
338: public void startPrefixMapping(String arg0, String arg1)
339: throws SAXException {
340: nextHandler.startPrefixMapping(arg0, arg1);
341: }
342:
343: public void endPrefixMapping(String arg0) throws SAXException {
344: nextHandler.endPrefixMapping(arg0);
345: }
346:
347: public void characters(char[] arg0, int arg1, int arg2)
348: throws SAXException {
349: nextHandler.characters(arg0, arg1, arg2);
350: }
351:
352: public void ignorableWhitespace(char[] arg0, int arg1, int arg2)
353: throws SAXException {
354: nextHandler.ignorableWhitespace(arg0, arg1, arg2);
355: }
356:
357: public void processingInstruction(String arg0, String arg1)
358: throws SAXException {
359: nextHandler.processingInstruction(arg0, arg1);
360: }
361:
362: public void skippedEntity(String arg0) throws SAXException {
363: nextHandler.skippedEntity(arg0);
364: }
365: }
366: }
|