View Javadoc

1   /*
2    * Copyright 2005 The Apache Software Foundation.
3    * 
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    * 
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    * 
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
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   * &lt;root xmlns:loc="http://apache.org/cocoon/location"
34   *       loc:src="file://path/to/file.xml"
35   *       loc:line="1" loc:column="1"&gt;
36   *   &lt;foo loc:src="file://path/to/file.xml" loc:line="2" loc:column="3"/&gt;
37   * &lt;/root&gt;
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      // Private constructor, we only have static methods
65      private LocationAttributes() {
66          // Nothing
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              // No location information known, or already has it
79              return attrs;
80          }
81          
82          // Get an AttributeImpl so that we can add new attributes.
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      * &lt;root xmlns:loc="http://apache.org/cocoon/location"
269      *       loc:src="file://path/to/file.xml"
270      *       loc:line="1" loc:column="1"&gt;
271      *   &lt;foo loc:src="file://path/to/file.xml" loc:line="2" loc:column="3"/&gt;
272      * &lt;/root&gt;
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             // Add location attributes to the element
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 }