KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > jdom > output > SAXOutputter


1 /*--
2
3  $Id: SAXOutputter.java,v 1.38 2004/12/11 00:15:24 jhunter Exp $
4
5  Copyright (C) 2000-2004 Jason Hunter & Brett McLaughlin.
6  All rights reserved.
7
8  Redistribution and use in source and binary forms, with or without
9  modification, are permitted provided that the following conditions
10  are met:
11
12  1. Redistributions of source code must retain the above copyright
13     notice, this list of conditions, and the following disclaimer.
14
15  2. Redistributions in binary form must reproduce the above copyright
16     notice, this list of conditions, and the disclaimer that follows
17     these conditions in the documentation and/or other materials
18     provided with the distribution.
19
20  3. The name "JDOM" must not be used to endorse or promote products
21     derived from this software without prior written permission. For
22     written permission, please contact <request_AT_jdom_DOT_org>.
23
24  4. Products derived from this software may not be called "JDOM", nor
25     may "JDOM" appear in their name, without prior written permission
26     from the JDOM Project Management <request_AT_jdom_DOT_org>.
27
28  In addition, we request (but do not require) that you include in the
29  end-user documentation provided with the redistribution and/or in the
30  software itself an acknowledgement equivalent to the following:
31      "This product includes software developed by the
32       JDOM Project (http://www.jdom.org/)."
33  Alternatively, the acknowledgment may be graphical using the logos
34  available at http://www.jdom.org/images/logos.
35
36  THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
37  WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
38  OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
39  DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT
40  CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
41  SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
42  LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
43  USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
44  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
45  OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
46  OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
47  SUCH DAMAGE.
48
49  This software consists of voluntary contributions made by many
50  individuals on behalf of the JDOM Project and was originally
51  created by Jason Hunter <jhunter_AT_jdom_DOT_org> and
52  Brett McLaughlin <brett_AT_jdom_DOT_org>. For more information
53  on the JDOM Project, please see <http://www.jdom.org/>.
54
55  */

56
57 package org.jdom.output;
58
59 import java.io.*;
60 import java.lang.reflect.*;
61 import java.util.*;
62
63 import org.jdom.*;
64 import org.xml.sax.*;
65 import org.xml.sax.ext.*;
66 import org.xml.sax.helpers.*;
67
68 /**
69  * Outputs a JDOM document as a stream of SAX2 events.
70  * <p>
71  * Most ContentHandler callbacks are supported. Both
72  * <code>ignorableWhitespace()</code> and <code>skippedEntity()</code> have not
73  * been implemented. The <code>{@link JDOMLocator}</code> class returned by
74  * <code>{@link #getLocator}</code> exposes the current node being operated
75  * upon.
76  * <p>
77  * At this time, it is not possible to access notations and unparsed entity
78  * references in a DTD from JDOM. Therefore, <code>DTDHandler</code> callbacks
79  * have not been implemented yet.
80  * <p>
81  * The <code>ErrorHandler</code> callbacks have not been implemented, since
82  * these are supposed to be invoked when the document is parsed and at this
83  * point the document exists in memory and is known to have no errors. </p>
84  *
85  * @version $Revision: 1.38 $, $Date: 2004/12/11 00:15:24 $
86  * @author Brett McLaughlin
87  * @author Jason Hunter
88  * @author Fred Trimble
89  * @author Bradley S. Huffman
90  */

91 public class SAXOutputter {
92
93     private static final String JavaDoc CVS_ID =
94       "@(#) $RCSfile: SAXOutputter.java,v $ $Revision: 1.38 $ $Date: 2004/12/11 00:15:24 $ $Name: $";
95
96     /** Shortcut for SAX namespaces core feature */
97     private static final String JavaDoc NAMESPACES_SAX_FEATURE =
98                         "http://xml.org/sax/features/namespaces";
99
100     /** Shortcut for SAX namespace-prefixes core feature */
101     private static final String JavaDoc NS_PREFIXES_SAX_FEATURE =
102                         "http://xml.org/sax/features/namespace-prefixes";
103
104     /** Shortcut for SAX validation core feature */
105     private static final String JavaDoc VALIDATION_SAX_FEATURE =
106                         "http://xml.org/sax/features/validation";
107
108     /** Shortcut for SAX-ext. lexical handler property */
109     private static final String JavaDoc LEXICAL_HANDLER_SAX_PROPERTY =
110                         "http://xml.org/sax/properties/lexical-handler";
111
112     /** Shortcut for SAX-ext. declaration handler property */
113     private static final String JavaDoc DECL_HANDLER_SAX_PROPERTY =
114                         "http://xml.org/sax/properties/declaration-handler";
115
116     /**
117      * Shortcut for SAX-ext. lexical handler alternate property.
118      * Although this property URI is not the one defined by the SAX
119      * "standard", some parsers use it instead of the official one.
120      */

121     private static final String JavaDoc LEXICAL_HANDLER_ALT_PROPERTY =
122                         "http://xml.org/sax/handlers/LexicalHandler";
123
124     /** Shortcut for SAX-ext. declaration handler alternate property */
125     private static final String JavaDoc DECL_HANDLER_ALT_PROPERTY =
126                         "http://xml.org/sax/handlers/DeclHandler";
127
128     /**
129      * Array to map JDOM attribute type (as entry index) to SAX
130      * attribute type names.
131      */

132     private static final String JavaDoc[] attrTypeToNameMap = new String JavaDoc[] {
133         "CDATA", // Attribute.UNDEFINED_ATTRIBUTE, as per SAX 2.0 spec.
134
"CDATA", // Attribute.CDATA_TYPE
135
"ID", // Attribute.ID_TYPE
136
"IDREF", // Attribute.IDREF_TYPE
137
"IDREFS", // Attribute.IDREFS_TYPE
138
"ENTITY", // Attribute.ENTITY_TYPE
139
"ENTITIES", // Attribute.ENTITIES_TYPE
140
"NMTOKEN", // Attribute.NMTOKEN_TYPE
141
"NMTOKENS", // Attribute.NMTOKENS_TYPE
142
"NOTATION", // Attribute.NOTATION_TYPE
143
"NMTOKEN", // Attribute.ENUMERATED_TYPE, as per SAX 2.0 spec.
144
};
145
146     /** registered <code>ContentHandler</code> */
147     private ContentHandler contentHandler;
148
149     /** registered <code>ErrorHandler</code> */
150     private ErrorHandler errorHandler;
151
152     /** registered <code>DTDHandler</code> */
153     private DTDHandler dtdHandler;
154
155     /** registered <code>EntityResolver</code> */
156     private EntityResolver entityResolver;
157
158     /** registered <code>LexicalHandler</code> */
159     private LexicalHandler lexicalHandler;
160
161     /** registered <code>DeclHandler</code> */
162     private DeclHandler declHandler;
163
164     /**
165      * Whether to report attribute namespace declarations as xmlns attributes.
166      * Defaults to <code>false</code> as per SAX specifications.
167      *
168      * @see <a HREF="http://www.megginson.com/SAX/Java/namespaces.html">
169      * SAX namespace specifications</a>
170      */

171     private boolean declareNamespaces = false;
172
173     /**
174      * Whether to report DTD events to DeclHandlers and LexicalHandlers.
175      * Defaults to <code>true</code>.
176      */

177     private boolean reportDtdEvents = true;
178
179     /**
180      * A SAX Locator that points at the JDOM node currently being
181      * outputted.
182      */

183     private JDOMLocator locator = null;
184
185     /**
186      * This will create a <code>SAXOutputter</code> without any
187      * registered handler. The application is then responsible for
188      * registering them using the <code>setXxxHandler()</code> methods.
189      */

190     public SAXOutputter() {
191     }
192
193     /**
194      * This will create a <code>SAXOutputter</code> with the
195      * specified <code>ContentHandler</code>.
196      *
197      * @param contentHandler contains <code>ContentHandler</code>
198      * callback methods
199      */

200     public SAXOutputter(ContentHandler contentHandler) {
201         this(contentHandler, null, null, null, null);
202     }
203
204     /**
205      * This will create a <code>SAXOutputter</code> with the
206      * specified SAX2 handlers. At this time, only <code>ContentHandler</code>
207      * and <code>EntityResolver</code> are supported.
208      *
209      * @param contentHandler contains <code>ContentHandler</code>
210      * callback methods
211      * @param errorHandler contains <code>ErrorHandler</code> callback methods
212      * @param dtdHandler contains <code>DTDHandler</code> callback methods
213      * @param entityResolver contains <code>EntityResolver</code>
214      * callback methods
215      */

216     public SAXOutputter(ContentHandler contentHandler,
217                         ErrorHandler errorHandler,
218                         DTDHandler dtdHandler,
219                         EntityResolver entityResolver) {
220         this(contentHandler, errorHandler, dtdHandler, entityResolver, null);
221     }
222
223     /**
224      * This will create a <code>SAXOutputter</code> with the
225      * specified SAX2 handlers. At this time, only <code>ContentHandler</code>
226      * and <code>EntityResolver</code> are supported.
227      *
228      * @param contentHandler contains <code>ContentHandler</code>
229      * callback methods
230      * @param errorHandler contains <code>ErrorHandler</code> callback methods
231      * @param dtdHandler contains <code>DTDHandler</code> callback methods
232      * @param entityResolver contains <code>EntityResolver</code>
233      * callback methods
234      * @param lexicalHandler contains <code>LexicalHandler</code> callbacks.
235      */

236     public SAXOutputter(ContentHandler contentHandler,
237                         ErrorHandler errorHandler,
238                         DTDHandler dtdHandler,
239                         EntityResolver entityResolver,
240                         LexicalHandler lexicalHandler) {
241         this.contentHandler = contentHandler;
242         this.errorHandler = errorHandler;
243         this.dtdHandler = dtdHandler;
244         this.entityResolver = entityResolver;
245         this.lexicalHandler = lexicalHandler;
246     }
247
248     /**
249      * This will set the <code>ContentHandler</code>.
250      *
251      * @param contentHandler contains <code>ContentHandler</code>
252      * callback methods.
253      */

254     public void setContentHandler(ContentHandler contentHandler) {
255         this.contentHandler = contentHandler;
256     }
257
258     /**
259      * Returns the registered <code>ContentHandler</code>.
260      *
261      * @return the current <code>ContentHandler</code> or
262      * <code>null</code> if none was registered.
263      */

264     public ContentHandler getContentHandler() {
265         return this.contentHandler;
266     }
267
268     /**
269      * This will set the <code>ErrorHandler</code>.
270      *
271      * @param errorHandler contains <code>ErrorHandler</code> callback methods.
272      */

273     public void setErrorHandler(ErrorHandler errorHandler) {
274         this.errorHandler = errorHandler;
275     }
276
277     /**
278      * Return the registered <code>ErrorHandler</code>.
279      *
280      * @return the current <code>ErrorHandler</code> or
281      * <code>null</code> if none was registered.
282      */

283     public ErrorHandler getErrorHandler() {
284         return this.errorHandler;
285     }
286
287     /**
288      * This will set the <code>DTDHandler</code>.
289      *
290      * @param dtdHandler contains <code>DTDHandler</code> callback methods.
291      */

292     public void setDTDHandler(DTDHandler dtdHandler) {
293         this.dtdHandler = dtdHandler;
294     }
295
296     /**
297      * Return the registered <code>DTDHandler</code>.
298      *
299      * @return the current <code>DTDHandler</code> or
300      * <code>null</code> if none was registered.
301      */

302     public DTDHandler getDTDHandler() {
303         return this.dtdHandler;
304     }
305
306     /**
307      * This will set the <code>EntityResolver</code>.
308      *
309      * @param entityResolver contains EntityResolver callback methods.
310      */

311     public void setEntityResolver(EntityResolver entityResolver) {
312         this.entityResolver = entityResolver;
313     }
314
315     /**
316      * Return the registered <code>EntityResolver</code>.
317      *
318      * @return the current <code>EntityResolver</code> or
319      * <code>null</code> if none was registered.
320      */

321     public EntityResolver getEntityResolver() {
322         return this.entityResolver;
323     }
324
325     /**
326      * This will set the <code>LexicalHandler</code>.
327      *
328      * @param lexicalHandler contains lexical callback methods.
329      */

330     public void setLexicalHandler(LexicalHandler lexicalHandler) {
331         this.lexicalHandler = lexicalHandler;
332     }
333
334     /**
335      * Return the registered <code>LexicalHandler</code>.
336      *
337      * @return the current <code>LexicalHandler</code> or
338      * <code>null</code> if none was registered.
339      */

340     public LexicalHandler getLexicalHandler() {
341         return this.lexicalHandler;
342     }
343
344     /**
345      * This will set the <code>DeclHandler</code>.
346      *
347      * @param declHandler contains declaration callback methods.
348      */

349     public void setDeclHandler(DeclHandler declHandler) {
350         this.declHandler = declHandler;
351     }
352
353     /**
354      * Return the registered <code>DeclHandler</code>.
355      *
356      * @return the current <code>DeclHandler</code> or
357      * <code>null</code> if none was registered.
358      */

359     public DeclHandler getDeclHandler() {
360         return this.declHandler;
361     }
362
363     /**
364      * Returns whether attribute namespace declarations shall be reported as
365      * "xmlns" attributes.
366      *
367      * @return whether attribute namespace declarations shall be reported as
368      * "xmlns" attributes.
369      */

370     public boolean getReportNamespaceDeclarations() {
371         return declareNamespaces;
372     }
373
374     /**
375      * This will define whether attribute namespace declarations shall be
376      * reported as "xmlns" attributes. This flag defaults to <code>false</code>
377      * and behaves as the "namespace-prefixes" SAX core feature.
378      *
379      * @param declareNamespaces whether attribute namespace declarations
380      * shall be reported as "xmlns" attributes.
381      */

382     public void setReportNamespaceDeclarations(boolean declareNamespaces) {
383         this.declareNamespaces = declareNamespaces;
384     }
385
386     /**
387      * Returns whether DTD events will be reported.
388      *
389      * @return whether DTD events will be reported
390      */

391     public boolean getReportDTDEvents() {
392         return reportDtdEvents;
393     }
394
395     /**
396      * This will define whether to report DTD events to SAX DeclHandlers
397      * and LexicalHandlers if these handlers are registered and the
398      * document to output includes a DocType declaration.
399      *
400      * @param reportDtdEvents whether to notify DTD events.
401      */

402     public void setReportDTDEvents(boolean reportDtdEvents) {
403         this.reportDtdEvents = reportDtdEvents;
404     }
405
406     /**
407      * This will set the state of a SAX feature.
408      * <p>
409      * All XMLReaders are required to support setting to true and to false.
410      * </p>
411      * <p>
412      * SAXOutputter currently supports the following SAX core features:
413      * <dl>
414      * <dt><code>http://xml.org/sax/features/namespaces</code></dt>
415      * <dd><strong>description:</strong> <code>true</code> indicates
416      * namespace URIs and unprefixed local names for element and
417      * attribute names will be available</dd>
418      * <dd><strong>access:</strong> read/write, but always
419      * <code>true</code>!</dd>
420      * <dt><code>http://xml.org/sax/features/namespace-prefixes</code></dt>
421      * <dd><strong>description:</strong> <code>true</code> indicates
422      * XML 1.0 names (with prefixes) and attributes (including xmlns*
423      * attributes) will be available</dd>
424      * <dd><strong>access:</strong> read/write</dd>
425      * <dt><code>http://xml.org/sax/features/validation</code></dt>
426      * <dd><strong>description:</strong> controls whether SAXOutputter
427      * is reporting DTD-related events; if <code>true</code>, the
428      * DocType internal subset will be parsed to fire DTD events</dd>
429      * <dd><strong>access:</strong> read/write, defaults to
430      * <code>true</code></dd>
431      * </dl>
432      * </p>
433      *
434      * @param name <code>String</code> the feature name, which is a
435      * fully-qualified URI.
436      * @param value <code>boolean</code> the requested state of the
437      * feature (true or false).
438      *
439      * @throws SAXNotRecognizedException when SAXOutputter does not
440      * recognize the feature name.
441      * @throws SAXNotSupportedException when SAXOutputter recognizes
442      * the feature name but cannot set the requested value.
443      */

444     public void setFeature(String JavaDoc name, boolean value)
445                 throws SAXNotRecognizedException, SAXNotSupportedException {
446         if (NS_PREFIXES_SAX_FEATURE.equals(name)) {
447             // Namespace prefix declarations.
448
this.setReportNamespaceDeclarations(value);
449         }
450         else {
451             if (NAMESPACES_SAX_FEATURE.equals(name)) {
452                 if (value != true) {
453                     // Namespaces feature always supported by SAXOutputter.
454
throw new SAXNotSupportedException(name);
455                 }
456                 // Else: true is OK!
457
}
458             else {
459                 if (VALIDATION_SAX_FEATURE.equals(name)) {
460                     // Report DTD events.
461
this.setReportDTDEvents(value);
462                 }
463                 else {
464                     // Not a supported feature.
465
throw new SAXNotRecognizedException(name);
466                 }
467             }
468         }
469     }
470
471     /**
472      * This will look up the value of a SAX feature.
473      *
474      * @param name <code>String</code> the feature name, which is a
475      * fully-qualified URI.
476      * @return <code>boolean</code> the current state of the feature
477      * (true or false).
478      *
479      * @throws SAXNotRecognizedException when SAXOutputter does not
480      * recognize the feature name.
481      * @throws SAXNotSupportedException when SAXOutputter recognizes
482      * the feature name but determine its value at this time.
483      */

484     public boolean getFeature(String JavaDoc name)
485                 throws SAXNotRecognizedException, SAXNotSupportedException {
486         if (NS_PREFIXES_SAX_FEATURE.equals(name)) {
487             // Namespace prefix declarations.
488
return (this.declareNamespaces);
489         }
490         else {
491             if (NAMESPACES_SAX_FEATURE.equals(name)) {
492                 // Namespaces feature always supported by SAXOutputter.
493
return (true);
494             }
495             else {
496                 if (VALIDATION_SAX_FEATURE.equals(name)) {
497                     // Report DTD events.
498
return (this.reportDtdEvents);
499                 }
500                 else {
501                     // Not a supported feature.
502
throw new SAXNotRecognizedException(name);
503                 }
504             }
505         }
506     }
507
508     /**
509      * This will set the value of a SAX property.
510      * This method is also the standard mechanism for setting extended
511      * handlers.
512      * <p>
513      * SAXOutputter currently supports the following SAX properties:
514      * <dl>
515      * <dt><code>http://xml.org/sax/properties/lexical-handler</code></dt>
516      * <dd><strong>data type:</strong>
517      * <code>org.xml.sax.ext.LexicalHandler</code></dd>
518      * <dd><strong>description:</strong> An optional extension handler for
519      * lexical events like comments.</dd>
520      * <dd><strong>access:</strong> read/write</dd>
521      * <dt><code>http://xml.org/sax/properties/declaration-handler</code></dt>
522      * <dd><strong>data type:</strong>
523      * <code>org.xml.sax.ext.DeclHandler</code></dd>
524      * <dd><strong>description:</strong> An optional extension handler for
525      * DTD-related events other than notations and unparsed entities.</dd>
526      * <dd><strong>access:</strong> read/write</dd>
527      * </dl>
528      * </p>
529      *
530      * @param name <code>String</code> the property name, which is a
531      * fully-qualified URI.
532      * @param value <code>Object</code> the requested value for the property.
533      *
534      * @throws SAXNotRecognizedException when SAXOutputter does not recognize
535      * the property name.
536      * @throws SAXNotSupportedException when SAXOutputter recognizes the
537      * property name but cannot set the requested value.
538      */

539     public void setProperty(String JavaDoc name, Object JavaDoc value)
540                 throws SAXNotRecognizedException, SAXNotSupportedException {
541         if ((LEXICAL_HANDLER_SAX_PROPERTY.equals(name)) ||
542             (LEXICAL_HANDLER_ALT_PROPERTY.equals(name))) {
543             this.setLexicalHandler((LexicalHandler)value);
544         }
545         else {
546             if ((DECL_HANDLER_SAX_PROPERTY.equals(name)) ||
547                 (DECL_HANDLER_ALT_PROPERTY.equals(name))) {
548                 this.setDeclHandler((DeclHandler)value);
549             }
550             else {
551                 throw new SAXNotRecognizedException(name);
552             }
553         }
554     }
555
556     /**
557      * This will look up the value of a SAX property.
558      *
559      * @param name <code>String</code> the property name, which is a
560      * fully-qualified URI.
561      * @return <code>Object</code> the current value of the property.
562      *
563      * @throws SAXNotRecognizedException when SAXOutputter does not recognize
564      * the property name.
565      * @throws SAXNotSupportedException when SAXOutputter recognizes the
566      * property name but cannot determine its value at this time.
567      */

568     public Object JavaDoc getProperty(String JavaDoc name)
569                 throws SAXNotRecognizedException, SAXNotSupportedException {
570         if ((LEXICAL_HANDLER_SAX_PROPERTY.equals(name)) ||
571             (LEXICAL_HANDLER_ALT_PROPERTY.equals(name))) {
572             return this.getLexicalHandler();
573         }
574         else {
575             if ((DECL_HANDLER_SAX_PROPERTY.equals(name)) ||
576                 (DECL_HANDLER_ALT_PROPERTY.equals(name))) {
577                 return this.getDeclHandler();
578             }
579             else {
580                 throw new SAXNotRecognizedException(name);
581             }
582         }
583     }
584
585
586     /**
587      * This will output the <code>JDOM Document</code>, firing off the
588      * SAX events that have been registered.
589      *
590      * @param document <code>JDOM Document</code> to output.
591      *
592      * @throws JDOMException if any error occurred.
593      */

594     public void output(Document document) throws JDOMException {
595         if (document == null) {
596             return;
597         }
598
599         // contentHandler.setDocumentLocator()
600
documentLocator(document);
601
602         // contentHandler.startDocument()
603
startDocument();
604
605         // Fire DTD events
606
if (this.reportDtdEvents) {
607            dtdEvents(document);
608         }
609
610         // Handle root element, as well as any root level
611
// processing instructions and comments
612
Iterator i = document.getContent().iterator();
613         while (i.hasNext()) {
614             Object JavaDoc obj = i.next();
615
616             // update locator
617
locator.setNode(obj);
618
619             if (obj instanceof Element) {
620                 // process root element and its content
621
element(document.getRootElement(), new NamespaceStack());
622             }
623             else if (obj instanceof ProcessingInstruction) {
624                 // contentHandler.processingInstruction()
625
processingInstruction((ProcessingInstruction) obj);
626             }
627             else if (obj instanceof Comment) {
628                 // lexicalHandler.comment()
629
comment(((Comment) obj).getText());
630             }
631         }
632
633         // contentHandler.endDocument()
634
endDocument();
635     }
636
637     /**
638      * This will output a list of JDOM nodes as a document, firing
639      * off the SAX events that have been registered.
640      * <p>
641      * <strong>Warning</strong>: This method may output ill-formed XML
642      * documents if the list contains top-level objects that are not
643      * legal at the document level (e.g. Text or CDATA nodes, multiple
644      * Element nodes, etc.). Thus, it should only be used to output
645      * document portions towards ContentHandlers capable of accepting
646      * such ill-formed documents (such as XSLT processors).</p>
647      *
648      * @param nodes <code>List</code> of JDOM nodes to output.
649      *
650      * @throws JDOMException if any error occurred.
651      *
652      * @see #output(org.jdom.Document)
653      */

654     public void output(List nodes) throws JDOMException {
655         if ((nodes == null) || (nodes.size() == 0)) {
656             return;
657         }
658
659         // contentHandler.setDocumentLocator()
660
documentLocator(null);
661
662         // contentHandler.startDocument()
663
startDocument();
664
665         // Process node list.
666
elementContent(nodes, new NamespaceStack());
667
668         // contentHandler.endDocument()
669
endDocument();
670     }
671
672     /**
673      * This will output a single JDOM element as a document, firing
674      * off the SAX events that have been registered.
675      *
676      * @param node the <code>Element</code> node to output.
677      *
678      * @throws JDOMException if any error occurred.
679      */

680     public void output(Element node) throws JDOMException {
681         if (node == null) {
682             return;
683         }
684
685         // contentHandler.setDocumentLocator()
686
documentLocator(null);
687
688         // contentHandler.startDocument()
689
startDocument();
690
691         // Output node.
692
elementContent(node, new NamespaceStack());
693
694         // contentHandler.endDocument()
695
endDocument();
696     }
697
698     /**
699      * This will output a list of JDOM nodes as a fragment of an XML
700      * document, firing off the SAX events that have been registered.
701      * <p>
702      * <strong>Warning</strong>: This method does not call the
703      * {@link ContentHandler#setDocumentLocator},
704      * {@link ContentHandler#startDocument} and
705      * {@link ContentHandler#endDocument} callbacks on the
706      * {@link #setContentHandler ContentHandler}. The user shall
707      * invoke these methods directly prior/after outputting the
708      * document fragments.</p>
709      *
710      * @param nodes <code>List</code> of JDOM nodes to output.
711      *
712      * @throws JDOMException if any error occurred.
713      *
714      * @see #outputFragment(org.jdom.Content)
715      */

716     public void outputFragment(List nodes) throws JDOMException {
717         if ((nodes == null) || (nodes.size() == 0)) {
718             return;
719         }
720
721         // Output node list as a document fragment.
722
elementContent(nodes, new NamespaceStack());
723     }
724
725     /**
726      * This will output a single JDOM nodes as a fragment of an XML
727      * document, firing off the SAX events that have been registered.
728      * <p>
729      * <strong>Warning</strong>: This method does not call the
730      * {@link ContentHandler#setDocumentLocator},
731      * {@link ContentHandler#startDocument} and
732      * {@link ContentHandler#endDocument} callbacks on the
733      * {@link #setContentHandler ContentHandler}. The user shall
734      * invoke these methods directly prior/after outputting the
735      * document fragments.</p>
736      *
737      * @param node the <code>Content</code> node to output.
738      *
739      * @throws JDOMException if any error occurred.
740      *
741      * @see #outputFragment(java.util.List)
742      */

743     public void outputFragment(Content node) throws JDOMException {
744         if (node == null) {
745             return;
746         }
747
748         // Output single node as a document fragment.
749
elementContent(node, new NamespaceStack());
750     }
751
752     /**
753      * This parses a DTD declaration to fire the related events towards
754      * the registered handlers.
755      *
756      * @param document <code>JDOM Document</code> the DocType is to
757      * process.
758      */

759     private void dtdEvents(Document document) throws JDOMException {
760         DocType docType = document.getDocType();
761
762         // Fire DTD-related events only if handlers have been registered.
763
if ((docType != null) &&
764             ((dtdHandler != null) || (declHandler != null))) {
765
766             // Build a dummy XML document that only references the DTD...
767
String JavaDoc dtdDoc = new XMLOutputter().outputString(docType);
768
769             try {
770                 // And parse it to fire DTD events.
771
createDTDParser().parse(new InputSource(
772                                                 new StringReader(dtdDoc)));
773
774                 // We should never reach this point as the document is
775
// ill-formed; it does not have any root element.
776
}
777             catch (SAXParseException e) {
778                 // Expected exception: There's no root element in document.
779
}
780             catch (SAXException e) {
781                 throw new JDOMException("DTD parsing error", e);
782             }
783             catch (IOException e) {
784                 throw new JDOMException("DTD parsing error", e);
785             }
786         }
787     }
788
789     /**
790      * <p>
791      * This method tells you the line of the XML file being parsed.
792      * For an in-memory document, it's meaningless. The location
793      * is only valid for the current parsing lifecycle, but
794      * the document has already been parsed. Therefore, it returns
795      * -1 for both line and column numbers.
796      * </p>
797      *
798      * @param document JDOM <code>Document</code>.
799      */

800     private void documentLocator(Document document) {
801         locator = new JDOMLocator();
802         String JavaDoc publicID = null;
803         String JavaDoc systemID = null;
804
805         if (document != null) {
806             DocType docType = document.getDocType();
807             if (docType != null) {
808                 publicID = docType.getPublicID();
809                 systemID = docType.getSystemID();
810             }
811         }
812         locator.setPublicId(publicID);
813         locator.setSystemId(systemID);
814         locator.setLineNumber(-1);
815         locator.setColumnNumber(-1);
816
817         contentHandler.setDocumentLocator(locator);
818     }
819
820     /**
821      * <p>
822      * This method is always the second method of all callbacks in
823      * all handlers to be invoked (setDocumentLocator is always first).
824      * </p>
825      */

826     private void startDocument() throws JDOMException {
827         try {
828             contentHandler.startDocument();
829         }
830         catch (SAXException se) {
831             throw new JDOMException("Exception in startDocument", se);
832         }
833     }
834
835     /**
836      * <p>
837      * Always the last method of all callbacks in all handlers
838      * to be invoked.
839      * </p>
840      */

841     private void endDocument() throws JDOMException {
842         try {
843             contentHandler.endDocument();
844
845             // reset locator
846
locator = null;
847         }
848         catch (SAXException se) {
849             throw new JDOMException("Exception in endDocument", se);
850         }
851     }
852
853     /**
854      * <p>
855      * This will invoke the <code>ContentHandler.processingInstruction</code>
856      * callback when a processing instruction is encountered.
857      * </p>
858      *
859      * @param pi <code>ProcessingInstruction</code> containing target and data.
860      */

861     private void processingInstruction(ProcessingInstruction pi)
862                            throws JDOMException {
863         if (pi != null) {
864             String JavaDoc target = pi.getTarget();
865             String JavaDoc data = pi.getData();
866             try {
867                 contentHandler.processingInstruction(target, data);
868             }
869             catch (SAXException se) {
870                 throw new JDOMException(
871                     "Exception in processingInstruction", se);
872             }
873         }
874     }
875
876     /**
877      * <p>
878      * This will recursively invoke all of the callbacks for a particular
879      * element.
880      * </p>
881      *
882      * @param element <code>Element</code> used in callbacks.
883      * @param namespaces <code>List</code> stack of Namespaces in scope.
884      */

885     private void element(Element element, NamespaceStack namespaces)
886                            throws JDOMException {
887         // used to check endPrefixMapping
888
int previouslyDeclaredNamespaces = namespaces.size();
889
890         // contentHandler.startPrefixMapping()
891
Attributes nsAtts = startPrefixMapping(element, namespaces);
892
893         // contentHandler.startElement()
894
startElement(element, nsAtts);
895
896         // handle content in the element
897
elementContent(element.getContent(), namespaces);
898
899         // update locator
900
locator.setNode(element);
901
902         // contentHandler.endElement()
903
endElement(element);
904
905         // contentHandler.endPrefixMapping()
906
endPrefixMapping(namespaces, previouslyDeclaredNamespaces);
907     }
908
909     /**
910      * <p>
911      * This will invoke the <code>ContentHandler.startPrefixMapping</code>
912      * callback
913      * when a new namespace is encountered in the <code>Document</code>.
914      * </p>
915      *
916      * @param element <code>Element</code> used in callbacks.
917      * @param namespaces <code>List</code> stack of Namespaces in scope.
918      *
919      * @return <code>Attributes</code> declaring the namespaces local to
920      * <code>element</code> or <code>null</code>.
921      */

922     private Attributes startPrefixMapping(Element element,
923                                           NamespaceStack namespaces)
924                                                    throws JDOMException {
925         AttributesImpl nsAtts = null; // The namespaces as xmlns attributes
926

927         Namespace ns = element.getNamespace();
928         if (ns != Namespace.XML_NAMESPACE) {
929             String JavaDoc prefix = ns.getPrefix();
930             String JavaDoc uri = namespaces.getURI(prefix);
931             if (!ns.getURI().equals(uri)) {
932                 namespaces.push(ns);
933                 nsAtts = this.addNsAttribute(nsAtts, ns);
934                 try {
935                     contentHandler.startPrefixMapping(prefix, ns.getURI());
936                 }
937                 catch (SAXException se) {
938                    throw new JDOMException(
939                        "Exception in startPrefixMapping", se);
940                 }
941             }
942         }
943
944         // Fire additional namespace declarations
945
List additionalNamespaces = element.getAdditionalNamespaces();
946         if (additionalNamespaces != null) {
947             Iterator itr = additionalNamespaces.iterator();
948             while (itr.hasNext()) {
949                 ns = (Namespace)itr.next();
950                 String JavaDoc prefix = ns.getPrefix();
951                 String JavaDoc uri = namespaces.getURI(prefix);
952                 if (!ns.getURI().equals(uri)) {
953                     namespaces.push(ns);
954                     nsAtts = this.addNsAttribute(nsAtts, ns);
955                     try {
956                         contentHandler.startPrefixMapping(prefix, ns.getURI());
957                     }
958                     catch (SAXException se) {
959                         throw new JDOMException(
960                             "Exception in startPrefixMapping", se);
961                     }
962                 }
963             }
964         }
965         return nsAtts;
966     }
967
968     /**
969      * <p>
970      * This will invoke the <code>endPrefixMapping</code> callback in the
971      * <code>ContentHandler</code> when a namespace is goes out of scope
972      * in the <code>Document</code>.
973      * </p>
974      *
975      * @param namespaces <code>List</code> stack of Namespaces in scope.
976      * @param previouslyDeclaredNamespaces number of previously declared
977      * namespaces
978      */

979     private void endPrefixMapping(NamespaceStack namespaces,
980                                   int previouslyDeclaredNamespaces)
981                                                 throws JDOMException {
982         while (namespaces.size() > previouslyDeclaredNamespaces) {
983             String JavaDoc prefix = namespaces.pop();
984             try {
985                 contentHandler.endPrefixMapping(prefix);
986             }
987             catch (SAXException se) {
988                 throw new JDOMException("Exception in endPrefixMapping", se);
989             }
990         }
991     }
992
993     /**
994      * <p>
995      * This will invoke the <code>startElement</code> callback
996      * in the <code>ContentHandler</code>.
997      * </p>
998      *
999      * @param element <code>Element</code> used in callbacks.
1000     * @param nsAtts <code>List</code> of namespaces to declare with
1001     * the element or <code>null</code>.
1002     */

1003    private void startElement(Element element, Attributes nsAtts)
1004                      throws JDOMException {
1005        String JavaDoc namespaceURI = element.getNamespaceURI();
1006        String JavaDoc localName = element.getName();
1007        String JavaDoc rawName = element.getQualifiedName();
1008
1009        // Allocate attribute list.
1010
AttributesImpl atts = (nsAtts != null)?
1011                              new AttributesImpl(nsAtts): new AttributesImpl();
1012
1013        List attributes = element.getAttributes();
1014        Iterator i = attributes.iterator();
1015        while (i.hasNext()) {
1016            Attribute a = (Attribute) i.next();
1017            atts.addAttribute(a.getNamespaceURI(),
1018                              a.getName(),
1019                              a.getQualifiedName(),
1020                              getAttributeTypeName(a.getAttributeType()),
1021                              a.getValue());
1022        }
1023
1024        try {
1025            contentHandler.startElement(namespaceURI, localName, rawName, atts);
1026        }
1027        catch (SAXException se) {
1028            throw new JDOMException("Exception in startElement", se);
1029        }
1030    }
1031
1032    /**
1033     * <p>
1034     * This will invoke the <code>endElement</code> callback
1035     * in the <code>ContentHandler</code>.
1036     * </p>
1037     *
1038     * @param element <code>Element</code> used in callbacks.
1039     */

1040    private void endElement(Element element) throws JDOMException {
1041        String JavaDoc namespaceURI = element.getNamespaceURI();
1042        String JavaDoc localName = element.getName();
1043        String JavaDoc rawName = element.getQualifiedName();
1044
1045        try {
1046            contentHandler.endElement(namespaceURI, localName, rawName);
1047        }
1048        catch (SAXException se) {
1049            throw new JDOMException("Exception in endElement", se);
1050        }
1051    }
1052
1053    /**
1054     * <p>
1055     * This will invoke the callbacks for the content of an element.
1056     * </p>
1057     *
1058     * @param content element content as a <code>List</code> of nodes.
1059     * @param namespaces <code>List</code> stack of Namespaces in scope.
1060     */

1061    private void elementContent(List content, NamespaceStack namespaces)
1062                      throws JDOMException {
1063        for (Iterator i=content.iterator(); i.hasNext(); ) {
1064            Object JavaDoc obj = i.next();
1065
1066            if (obj instanceof Content) {
1067                this.elementContent((Content)obj, namespaces);
1068            }
1069            else {
1070                // Not a valid element child. This could happen with
1071
// application-provided lists which may contain non
1072
// JDOM objects.
1073
handleError(new JDOMException(
1074                                        "Invalid element content: " + obj));
1075            }
1076        }
1077    }
1078
1079    /**
1080     * <p>
1081     * This will invoke the callbacks for the content of an element.
1082     * </p>
1083     *
1084     * @param node a <code>Content</code> node.
1085     * @param namespaces <code>List</code> stack of Namespaces in scope.
1086     */

1087    private void elementContent(Content node, NamespaceStack namespaces)
1088                      throws JDOMException {
1089        // update locator
1090
locator.setNode(node);
1091
1092        if (node instanceof Element) {
1093            element((Element) node, namespaces);
1094        }
1095        else if (node instanceof CDATA) {
1096            cdata(((CDATA) node).getText());
1097        }
1098        else if (node instanceof Text) {
1099            // contentHandler.characters()
1100
characters(((Text) node).getText());
1101        }
1102        else if (node instanceof ProcessingInstruction) {
1103            // contentHandler.processingInstruction()
1104
processingInstruction((ProcessingInstruction) node);
1105        }
1106        else if (node instanceof Comment) {
1107            // lexicalHandler.comment()
1108
comment(((Comment) node).getText());
1109        }
1110        else if (node instanceof EntityRef) {
1111            // contentHandler.skippedEntity()
1112
entityRef((EntityRef) node);
1113        }
1114        else {
1115            // Not a valid element child. This could happen with
1116
// application-provided lists which may contain non
1117
// JDOM objects.
1118
handleError(new JDOMException("Invalid element content: " + node));
1119        }
1120    }
1121
1122    /**
1123     * <p>
1124     * This will be called for each chunk of CDATA section encountered.
1125     * </p>
1126     *
1127     * @param cdataText all text in the CDATA section, including whitespace.
1128     */

1129    private void cdata(String JavaDoc cdataText) throws JDOMException {
1130        try {
1131            if (lexicalHandler != null) {
1132                lexicalHandler.startCDATA();
1133                characters(cdataText);
1134                lexicalHandler.endCDATA();
1135            }
1136            else {
1137                characters(cdataText);
1138            }
1139        }
1140        catch (SAXException se) {
1141            throw new JDOMException("Exception in CDATA", se);
1142        }
1143    }
1144
1145    /**
1146     * <p>
1147     * This will be called for each chunk of character data encountered.
1148     * </p>
1149     *
1150     * @param elementText all text in an element, including whitespace.
1151     */

1152    private void characters(String JavaDoc elementText) throws JDOMException {
1153        char[] c = elementText.toCharArray();
1154        try {
1155            contentHandler.characters(c, 0, c.length);
1156        }
1157        catch (SAXException se) {
1158            throw new JDOMException("Exception in characters", se);
1159        }
1160    }
1161
1162    /**
1163     * <p>
1164     * This will be called for each chunk of comment data encontered.
1165     * </p>
1166     *
1167     * @param commentText all text in a comment, including whitespace.
1168     */

1169    private void comment(String JavaDoc commentText) throws JDOMException {
1170        if (lexicalHandler != null) {
1171            char[] c = commentText.toCharArray();
1172            try {
1173                lexicalHandler.comment(c, 0, c.length);
1174            } catch (SAXException se) {
1175                throw new JDOMException("Exception in comment", se);
1176            }
1177        }
1178    }
1179
1180    /**
1181     * <p>
1182     * This will invoke the <code>ContentHandler.skippedEntity</code>
1183     * callback when an entity reference is encountered.
1184     * </p>
1185     *
1186     * @param entity <code>EntityRef</code>.
1187     */

1188    private void entityRef(EntityRef entity) throws JDOMException {
1189        if (entity != null) {
1190            try {
1191                // No need to worry about appending a '%' character as
1192
// we do not support parameter entities
1193
contentHandler.skippedEntity(entity.getName());
1194            }
1195            catch (SAXException se) {
1196                throw new JDOMException("Exception in entityRef", se);
1197            }
1198        }
1199    }
1200
1201
1202    /**
1203     * <p>
1204     * Appends a namespace declaration in the form of a xmlns attribute to
1205     * an attribute list, crerating this latter if needed.
1206     * </p>
1207     *
1208     * @param atts <code>AttributeImpl</code> where to add the attribute.
1209     * @param ns <code>Namespace</code> the namespace to declare.
1210     *
1211     * @return <code>AttributeImpl</code> the updated attribute list.
1212     */

1213    private AttributesImpl addNsAttribute(AttributesImpl atts, Namespace ns) {
1214        if (this.declareNamespaces) {
1215            if (atts == null) {
1216                atts = new AttributesImpl();
1217            }
1218
1219            String JavaDoc prefix = ns.getPrefix();
1220            if (prefix.equals("")) {
1221                atts.addAttribute("", // namespace
1222
"", // local name
1223
"xmlns", // qualified name
1224
"CDATA", // type
1225
ns.getURI()); // value
1226
}
1227            else {
1228                atts.addAttribute("", // namespace
1229
"", // local name
1230
"xmlns:" + ns.getPrefix(), // qualified name
1231
"CDATA", // type
1232
ns.getURI()); // value
1233
}
1234        }
1235        return atts;
1236    }
1237
1238    /**
1239     * <p>
1240     * Returns the SAX 2.0 attribute type string from the type of
1241     * a JDOM Attribute.
1242     * </p>
1243     *
1244     * @param type <code>int</code> the type of the JDOM attribute.
1245     *
1246     * @return <code>String</code> the SAX 2.0 attribute type string.
1247     *
1248     * @see org.jdom.Attribute#getAttributeType
1249     * @see org.xml.sax.Attributes#getType
1250     */

1251    private static String JavaDoc getAttributeTypeName(int type) {
1252        if ((type < 0) || (type >= attrTypeToNameMap.length)) {
1253            type = Attribute.UNDECLARED_TYPE;
1254        }
1255        return attrTypeToNameMap[type];
1256    }
1257
1258    /**
1259     * <p>
1260     * Notifies the registered {@link ErrorHandler SAX error handler}
1261     * (if any) of an input processing error. The error handler can
1262     * choose to absorb the error and let the processing continue.
1263     * </p>
1264     *
1265     * @param exception <code>JDOMException</code> containing the
1266     * error information; will be wrapped in a
1267     * {@link SAXParseException} when reported to
1268     * the SAX error handler.
1269     *
1270     * @throws JDOMException if no error handler has been registered
1271     * or if the error handler fired a
1272     * {@link SAXException}.
1273     */

1274    private void handleError(JDOMException exception) throws JDOMException {
1275        if (errorHandler != null) {
1276            try {
1277                errorHandler.error(new SAXParseException(
1278                                exception.getMessage(), null, exception));
1279            }
1280            catch (SAXException se) {
1281               if (se.getException() instanceof JDOMException) {
1282                   throw (JDOMException)(se.getException());
1283               }
1284               else {
1285                   throw new JDOMException(se.getMessage(), se);
1286               }
1287            }
1288        }
1289        else {
1290            throw exception;
1291        }
1292    }
1293
1294    /**
1295     * <p>
1296     * Creates a SAX XMLReader.
1297     * </p>
1298     *
1299     * @return <code>XMLReader</code> a SAX2 parser.
1300     *
1301     * @throws Exception if no parser can be created.
1302     */

1303    protected XMLReader createParser() throws Exception JavaDoc {
1304        XMLReader parser = null;
1305
1306        // Try using JAXP...
1307
// Note we need JAXP 1.1, and if JAXP 1.0 is all that's
1308
// available then the getXMLReader call fails and we skip
1309
// to the hard coded default parser
1310
try {
1311            Class JavaDoc factoryClass =
1312                    Class.forName("javax.xml.parsers.SAXParserFactory");
1313
1314            // factory = SAXParserFactory.newInstance();
1315
Method newParserInstance =
1316                    factoryClass.getMethod("newInstance", null);
1317            Object JavaDoc factory = newParserInstance.invoke(null, null);
1318
1319            // jaxpParser = factory.newSAXParser();
1320
Method newSAXParser = factoryClass.getMethod("newSAXParser", null);
1321            Object JavaDoc jaxpParser = newSAXParser.invoke(factory, null);
1322
1323            // parser = jaxpParser.getXMLReader();
1324
Class JavaDoc parserClass = jaxpParser.getClass();
1325            Method getXMLReader =
1326                    parserClass.getMethod("getXMLReader", null);
1327            parser = (XMLReader)getXMLReader.invoke(jaxpParser, null);
1328        } catch (ClassNotFoundException JavaDoc e) {
1329            //e.printStackTrace();
1330
} catch (InvocationTargetException e) {
1331            //e.printStackTrace();
1332
} catch (NoSuchMethodException JavaDoc e) {
1333            //e.printStackTrace();
1334
} catch (IllegalAccessException JavaDoc e) {
1335            //e.printStackTrace();
1336
}
1337
1338        // Check to see if we got a parser yet, if not, try to use a
1339
// hard coded default
1340
if (parser == null) {
1341            parser = XMLReaderFactory.createXMLReader(
1342                                "org.apache.xerces.parsers.SAXParser");
1343        }
1344        return parser;
1345    }
1346
1347    /**
1348     * <p>
1349     * This will create a SAX XMLReader capable of parsing a DTD and
1350     * configure it so that the DTD parsing events are routed to the
1351     * handlers registered onto this SAXOutputter.
1352     * </p>
1353     *
1354     * @return <code>XMLReader</code> a SAX2 parser.
1355     *
1356     * @throws JDOMException if no parser can be created.
1357     */

1358    private XMLReader createDTDParser() throws JDOMException {
1359        XMLReader parser = null;
1360
1361        // Get a parser instance
1362
try
1363        {
1364            parser = createParser();
1365        }
1366        catch (Exception JavaDoc ex1) {
1367           throw new JDOMException("Error in SAX parser allocation", ex1);
1368        }
1369
1370        // Register handlers
1371
if (this.getDTDHandler() != null) {
1372            parser.setDTDHandler(this.getDTDHandler());
1373        }
1374        if (this.getEntityResolver() != null) {
1375            parser.setEntityResolver(this.getEntityResolver());
1376        }
1377        if (this.getLexicalHandler() != null) {
1378            try {
1379                parser.setProperty(LEXICAL_HANDLER_SAX_PROPERTY,
1380                                   this.getLexicalHandler());
1381            }
1382            catch (SAXException ex1) {
1383                try {
1384                    parser.setProperty(LEXICAL_HANDLER_ALT_PROPERTY,
1385                                       this.getLexicalHandler());
1386                } catch (SAXException ex2) {
1387                    // Forget it!
1388
}
1389            }
1390        }
1391        if (this.getDeclHandler() != null) {
1392            try {
1393                parser.setProperty(DECL_HANDLER_SAX_PROPERTY,
1394                                   this.getDeclHandler());
1395            }
1396            catch (SAXException ex1) {
1397                try {
1398                    parser.setProperty(DECL_HANDLER_ALT_PROPERTY,
1399                                       this.getDeclHandler());
1400                } catch (SAXException ex2) {
1401                    // Forget it!
1402
}
1403            }
1404        }
1405
1406        // Absorb errors as much as possible, per Laurent
1407
parser.setErrorHandler(new DefaultHandler());
1408
1409        return parser;
1410    }
1411
1412    /**
1413     * Returns a JDOMLocator object referencing the node currently
1414     * being processed by this outputter. The returned object is a
1415     * snapshot of the location information and can thus safely be
1416     * memorized for later use.
1417     * <p>
1418     * This method allows direct access to the location information
1419     * maintained by SAXOutputter without requiring to implement
1420     * <code>XMLFilter</code>. (In SAX, locators are only available
1421     * though the <code>ContentHandler</code> interface).</p>
1422     * <p>
1423     * Note that location information is only available while
1424     * SAXOutputter is outputting nodes. Hence this method should
1425     * only be used by objects taking part in the output processing
1426     * such as <code>ErrorHandler</code>s.
1427     *
1428     * @return a JDOMLocator object referencing the node currently
1429     * being processed or <code>null</code> if no output
1430     * operation is being performed.
1431     */

1432    public JDOMLocator getLocator() {
1433        return (locator != null)? new JDOMLocator(locator): null;
1434    }
1435}
1436
Popular Tags