1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package org.apache.struts.flow.core.location;
17
18 import org.w3c.dom.Attr;
19 import org.w3c.dom.Element;
20 import org.w3c.dom.Node;
21 import org.w3c.dom.NodeList;
22 import org.xml.sax.Attributes;
23 import org.xml.sax.ContentHandler;
24 import org.xml.sax.Locator;
25 import org.xml.sax.SAXException;
26 import org.xml.sax.helpers.AttributesImpl;
27
28 /***
29 * A class to handle location information stored in attributes.
30 * These attributes are typically setup using {@link org.apache.struts.flow.core.location.LocationAttributes.Pipe}
31 * which augments the SAX stream with additional attributes, e.g.:
32 * <pre>
33 * <root xmlns:loc="http://apache.org/cocoon/location"
34 * loc:src="file://path/to/file.xml"
35 * loc:line="1" loc:column="1">
36 * <foo loc:src="file://path/to/file.xml" loc:line="2" loc:column="3"/>
37 * </root>
38 * </pre>
39 *
40 * @see org.apache.struts.flow.core.location.LocationAttributes.Pipe
41 * @since 2.1.8
42 * @version $Id: LocationAttributes.java 328390 2005-10-25 15:37:05Z sylvain $
43 */
44 public class LocationAttributes {
45 /*** Prefix for the location namespace */
46 public static final String PREFIX = "loc";
47 /*** Namespace URI for location attributes */
48 public static final String URI = "http://apache.org/cocoon/location";
49
50 /*** Attribute name for the location URI */
51 public static final String SRC_ATTR = "src";
52 /*** Attribute name for the line number */
53 public static final String LINE_ATTR = "line";
54 /*** Attribute name for the column number */
55 public static final String COL_ATTR = "column";
56
57 /*** Attribute qualified name for the location URI */
58 public static final String Q_SRC_ATTR = "loc:src";
59 /*** Attribute qualified name for the line number */
60 public static final String Q_LINE_ATTR = "loc:line";
61 /*** Attribute qualified name for the column number */
62 public static final String Q_COL_ATTR = "loc:column";
63
64
65 private LocationAttributes() {
66
67 }
68
69 /***
70 * Add location attributes to a set of SAX attributes.
71 *
72 * @param locator the <code>Locator</code> (can be null)
73 * @param attrs the <code>Attributes</code> where locator information should be added
74 * @return
75 */
76 public static Attributes addLocationAttributes(Locator locator, Attributes attrs) {
77 if (locator == null || attrs.getIndex(URI, SRC_ATTR) != -1) {
78
79 return attrs;
80 }
81
82
83 AttributesImpl newAttrs = attrs instanceof AttributesImpl ?
84 (AttributesImpl)attrs : new AttributesImpl(attrs);
85
86 newAttrs.addAttribute(URI, SRC_ATTR, Q_SRC_ATTR, "CDATA", locator.getSystemId());
87 newAttrs.addAttribute(URI, LINE_ATTR, Q_LINE_ATTR, "CDATA", Integer.toString(locator.getLineNumber()));
88 newAttrs.addAttribute(URI, COL_ATTR, Q_COL_ATTR, "CDATA", Integer.toString(locator.getColumnNumber()));
89
90 return newAttrs;
91 }
92
93 /***
94 * Returns the {@link Location} of an element (SAX flavor).
95 *
96 * @param attrs the element's attributes that hold the location information
97 * @param description a description for the location (can be null)
98 * @return a {@link Location} object
99 */
100 public static Location getLocation(Attributes attrs, String description) {
101 String src = attrs.getValue(URI, SRC_ATTR);
102 if (src == null) {
103 return Location.UNKNOWN;
104 }
105
106 return new LocationImpl(description, src, getLine(attrs), getColumn(attrs));
107 }
108
109 /***
110 * Returns the location of an element (SAX flavor). If the location is to be kept
111 * into an object built from this element, consider using {@link #getLocation(Attributes)}
112 * and the {@link Locatable} interface.
113 *
114 * @param attrs the element's attributes that hold the location information
115 * @return a location string as defined by {@link Location#toString()}.
116 */
117 public static String getLocationString(Attributes attrs) {
118 String src = attrs.getValue(URI, SRC_ATTR);
119 if (src == null) {
120 return LocationUtils.UNKNOWN_STRING;
121 }
122
123 return src + ":" + attrs.getValue(URI, LINE_ATTR) + ":" + attrs.getValue(URI, COL_ATTR);
124 }
125
126 /***
127 * Returns the URI of an element (SAX flavor)
128 *
129 * @param attrs the element's attributes that hold the location information
130 * @return the element's URI or "<code>[unknown location]</code>" if <code>attrs</code>
131 * has no location information.
132 */
133 public static String getURI(Attributes attrs) {
134 String src = attrs.getValue(URI, SRC_ATTR);
135 return src != null ? src : LocationUtils.UNKNOWN_STRING;
136 }
137
138 /***
139 * Returns the line number of an element (SAX flavor)
140 *
141 * @param attrs the element's attributes that hold the location information
142 * @return the element's line number or <code>-1</code> if <code>attrs</code>
143 * has no location information.
144 */
145 public static int getLine(Attributes attrs) {
146 String line = attrs.getValue(URI, LINE_ATTR);
147 return line != null ? Integer.parseInt(line) : -1;
148 }
149
150 /***
151 * Returns the column number of an element (SAX flavor)
152 *
153 * @param attrs the element's attributes that hold the location information
154 * @return the element's column number or <code>-1</code> if <code>attrs</code>
155 * has no location information.
156 */
157 public static int getColumn(Attributes attrs) {
158 String col = attrs.getValue(URI, COL_ATTR);
159 return col != null ? Integer.parseInt(col) : -1;
160 }
161
162 /***
163 * Returns the {@link Location} of an element (DOM flavor).
164 *
165 * @param attrs the element that holds the location information
166 * @param description a description for the location (if <code>null</code>, the element's name is used)
167 * @return a {@link Location} object
168 */
169 public static Location getLocation(Element elem, String description) {
170 Attr srcAttr = elem.getAttributeNodeNS(URI, SRC_ATTR);
171 if (srcAttr == null) {
172 return Location.UNKNOWN;
173 }
174
175 return new LocationImpl(description == null ? elem.getNodeName() : description,
176 srcAttr.getValue(), getLine(elem), getColumn(elem));
177 }
178
179 /***
180 * Same as <code>getLocation(elem, null)</code>.
181 */
182 public static Location getLocation(Element elem) {
183 return getLocation(elem, null);
184 }
185
186
187 /***
188 * Returns the location of an element that has been processed by this pipe (DOM flavor).
189 * If the location is to be kept into an object built from this element, consider using
190 * {@link #getLocation(Element)} and the {@link Locatable} interface.
191 *
192 * @param elem the element that holds the location information
193 * @return a location string as defined by {@link Location#toString()}.
194 */
195 public static String getLocationString(Element elem) {
196 Attr srcAttr = elem.getAttributeNodeNS(URI, SRC_ATTR);
197 if (srcAttr == null) {
198 return LocationUtils.UNKNOWN_STRING;
199 }
200
201 return srcAttr.getValue() + ":" + elem.getAttributeNS(URI, LINE_ATTR) + ":" + elem.getAttributeNS(URI, COL_ATTR);
202 }
203
204 /***
205 * Returns the URI of an element (DOM flavor)
206 *
207 * @param elem the element that holds the location information
208 * @return the element's URI or "<code>[unknown location]</code>" if <code>elem</code>
209 * has no location information.
210 */
211 public static String getURI(Element elem) {
212 Attr attr = elem.getAttributeNodeNS(URI, SRC_ATTR);
213 return attr != null ? attr.getValue() : LocationUtils.UNKNOWN_STRING;
214 }
215
216 /***
217 * Returns the line number of an element (DOM flavor)
218 *
219 * @param elem the element that holds the location information
220 * @return the element's line number or <code>-1</code> if <code>elem</code>
221 * has no location information.
222 */
223 public static int getLine(Element elem) {
224 Attr attr = elem.getAttributeNodeNS(URI, LINE_ATTR);
225 return attr != null ? Integer.parseInt(attr.getValue()) : -1;
226 }
227
228 /***
229 * Returns the column number of an element (DOM flavor)
230 *
231 * @param elem the element that holds the location information
232 * @return the element's column number or <code>-1</code> if <code>elem</code>
233 * has no location information.
234 */
235 public static int getColumn(Element elem) {
236 Attr attr = elem.getAttributeNodeNS(URI, COL_ATTR);
237 return attr != null ? Integer.parseInt(attr.getValue()) : -1;
238 }
239
240 /***
241 * Remove the location attributes from a DOM element.
242 *
243 * @param elem the element to remove the location attributes from.
244 * @param recurse if <code>true</code>, also remove location attributes on descendant elements.
245 */
246 public static void remove(Element elem, boolean recurse) {
247 elem.removeAttributeNS(URI, SRC_ATTR);
248 elem.removeAttributeNS(URI, LINE_ATTR);
249 elem.removeAttributeNS(URI, COL_ATTR);
250 if (recurse) {
251 NodeList children = elem.getChildNodes();
252 for (int i = 0; i < children.getLength(); i++) {
253 Node child = children.item(i);
254 if (child.getNodeType() == Node.ELEMENT_NODE) {
255 remove((Element)child, recurse);
256 }
257 }
258 }
259 }
260
261 /***
262 * A SAX filter that adds the information available from the <code>Locator</code> as attributes.
263 * The purpose of having location as attributes is to allow this information to survive transformations
264 * of the document (an XSL could copy these attributes over) or conversion of SAX events to a DOM.
265 * <p>
266 * The location is added as 3 attributes in a specific namespace to each element.
267 * <pre>
268 * <root xmlns:loc="http://apache.org/cocoon/location"
269 * loc:src="file://path/to/file.xml"
270 * loc:line="1" loc:column="1">
271 * <foo loc:src="file://path/to/file.xml" loc:line="2" loc:column="3"/>
272 * </root>
273 * </pre>
274 * <strong>Note:</strong> Although this adds a lot of information to the serialized form of the document,
275 * the overhead in SAX events is not that big, as attribute names are interned, and all <code>src</code>
276 * attributes point to the same string.
277 *
278 * @see org.apache.struts.flow.core.location.LocationAttributes
279 * @since 2.1.8
280 */
281 public static class Pipe implements ContentHandler {
282
283 private Locator locator;
284
285 private ContentHandler nextHandler;
286
287 /***
288 * Create a filter. It has to be chained to another handler to be really useful.
289 */
290 public Pipe() {
291 }
292
293 /***
294 * Create a filter that is chained to another handler.
295 * @param next the next handler in the chain.
296 */
297 public Pipe(ContentHandler next) {
298 nextHandler = next;
299 }
300
301 public void setDocumentLocator(Locator locator) {
302 this.locator = locator;
303 nextHandler.setDocumentLocator(locator);
304 }
305
306 public void startDocument() throws SAXException {
307 nextHandler.startDocument();
308 nextHandler.startPrefixMapping(LocationAttributes.PREFIX, LocationAttributes.URI);
309 }
310
311 public void endDocument() throws SAXException {
312 endPrefixMapping(LocationAttributes.PREFIX);
313 nextHandler.endDocument();
314 }
315
316 public void startElement(String uri, String loc, String raw, Attributes attrs) throws SAXException {
317
318 nextHandler.startElement(uri, loc, raw, LocationAttributes.addLocationAttributes(locator, attrs));
319 }
320
321 public void endElement(String arg0, String arg1, String arg2) throws SAXException {
322 nextHandler.endElement(arg0, arg1, arg2);
323 }
324
325 public void startPrefixMapping(String arg0, String arg1) throws SAXException {
326 nextHandler.startPrefixMapping(arg0, arg1);
327 }
328
329 public void endPrefixMapping(String arg0) throws SAXException {
330 nextHandler.endPrefixMapping(arg0);
331 }
332
333 public void characters(char[] arg0, int arg1, int arg2) throws SAXException {
334 nextHandler.characters(arg0, arg1, arg2);
335 }
336
337 public void ignorableWhitespace(char[] arg0, int arg1, int arg2) throws SAXException {
338 nextHandler.ignorableWhitespace(arg0, arg1, arg2);
339 }
340
341 public void processingInstruction(String arg0, String arg1) throws SAXException {
342 nextHandler.processingInstruction(arg0, arg1);
343 }
344
345 public void skippedEntity(String arg0) throws SAXException {
346 nextHandler.skippedEntity(arg0);
347 }
348 }
349 }