View Javadoc
1   package org.apache.maven.doxia.module.xdoc;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *   http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import java.io.Writer;
23  
24  import javax.swing.text.MutableAttributeSet;
25  import javax.swing.text.html.HTML.Attribute;
26  
27  import org.apache.maven.doxia.sink.SinkEventAttributes;
28  import org.apache.maven.doxia.sink.impl.SinkEventAttributeSet;
29  import org.apache.maven.doxia.sink.impl.SinkUtils;
30  import org.apache.maven.doxia.sink.impl.XhtmlBaseSink;
31  import org.apache.maven.doxia.util.HtmlTools;
32  
33  import org.codehaus.plexus.util.StringUtils;
34  
35  /**
36   * <a href="https://maven.apache.org/doxia/references/xdoc-format.html">Xdoc</a> Sink implementation.
37   * <br>
38   * It uses the Xdoc XSD <a href="https://maven.apache.org/xsd/xdoc-2.0.xsd">
39   * https://maven.apache.org/xsd/xdoc-2.0.xsd</a>.
40   *
41   * @author <a href="mailto:james@jamestaylor.org">James Taylor</a>
42   * @since 1.0
43   */
44  public class XdocSink
45      extends XhtmlBaseSink
46      implements XdocMarkup
47  {
48      // ----------------------------------------------------------------------
49      // Instance fields
50      // ----------------------------------------------------------------------
51  
52      /** An indication on if we're inside a box (verbatim). */
53      private boolean boxedFlag;
54  
55      private String encoding;
56  
57      private String languageId;
58  
59      // ----------------------------------------------------------------------
60      // Constructors
61      // ----------------------------------------------------------------------
62  
63      /**
64       * Constructor, initialize the Writer.
65       *
66       * @param writer not null writer to write the result. <b>Should</b> be an UTF-8 Writer.
67       * You could use <code>newXmlWriter</code> methods from {@link org.codehaus.plexus.util.WriterFactory}.
68       */
69      protected XdocSink( Writer writer )
70      {
71          super( writer );
72      }
73  
74      /**
75       * Constructor, initialize the Writer and tells which encoding is used.
76       *
77       * @param writer not null writer to write the result.
78       * @param encoding the encoding used, that should be written to the generated HTML content
79       * if not <code>null</code>.
80       * @since 1.1
81       */
82      protected XdocSink( Writer writer, String encoding )
83      {
84          this( writer );
85          this.encoding = encoding;
86      }
87  
88      /**
89       * Constructor, initialize the Writer and tells which encoding and languageId are used.
90       *
91       * @param writer not null writer to write the result.
92       * @param encoding the encoding used, that should be written to the generated HTML content
93       * if not <code>null</code>.
94       * @param languageId language identifier for the root element as defined by
95       * <a href="ftp://ftp.isi.edu/in-notes/bcp/bcp47.txt">IETF BCP 47</a>, Tags for the Identification of Languages;
96       * in addition, the empty string may be specified.
97       * @since 1.1
98       */
99      protected XdocSink( Writer writer, String encoding, String languageId )
100     {
101         this( writer, encoding );
102 
103         this.languageId = languageId;
104     }
105 
106     // ----------------------------------------------------------------------
107     // Public protected methods
108     // ----------------------------------------------------------------------
109 
110     /**
111      * {@inheritDoc}
112      */
113     protected void init()
114     {
115         super.init();
116 
117         boxedFlag = false;
118     }
119 
120     /**
121      * {@inheritDoc}
122      *
123      * @see #head(org.apache.maven.doxia.sink.SinkEventAttributes)
124      */
125     public void head()
126     {
127         head( null );
128     }
129 
130     /**
131      * {@inheritDoc}
132      * @see XdocMarkup#DOCUMENT_TAG
133      * @see XdocMarkup#PROPERTIES_TAG
134      */
135     public void head( SinkEventAttributes attributes )
136     {
137         init();
138 
139         setHeadFlag( true );
140 
141         write( "<?xml version=\"1.0\"" );
142         if ( encoding != null )
143         {
144             write( " encoding=\"" + encoding + "\"" );
145         }
146         write( "?>" );
147 
148         MutableAttributeSet atts = new SinkEventAttributeSet();
149         atts.addAttribute( "xmlns", XDOC_NAMESPACE );
150         atts.addAttribute( "xmlns:xsi", XML_NAMESPACE );
151         atts.addAttribute( "xsi:schemaLocation", XDOC_NAMESPACE + " " + XDOC_SYSTEM_ID );
152 
153         if ( languageId != null )
154         {
155             atts.addAttribute( Attribute.LANG.toString(), languageId );
156             atts.addAttribute( "xml:lang", languageId );
157         }
158 
159         if ( attributes != null )
160         {
161             atts.addAttributes( attributes );
162         }
163 
164         writeStartTag( DOCUMENT_TAG, atts );
165 
166         writeStartTag( PROPERTIES_TAG );
167     }
168 
169     /**
170      * {@inheritDoc}
171      *
172      * @see XdocMarkup#DOCUMENT_TAG
173      * @see XdocMarkup#PROPERTIES_TAG
174      */
175     public void head_()
176     {
177         setHeadFlag( false );
178 
179         writeEndTag( PROPERTIES_TAG );
180     }
181 
182     /**
183      * {@inheritDoc}
184      *
185      * @see javax.swing.text.html.HTML.Tag#TITLE
186      */
187     public void title()
188     {
189         writeStartTag( TITLE );
190     }
191 
192     /**
193      * {@inheritDoc}
194      *
195      * @see javax.swing.text.html.HTML.Tag#TITLE
196      */
197     public void title_()
198     {
199         content( getTextBuffer().toString() );
200 
201         writeEndTag( TITLE );
202 
203         resetTextBuffer();
204     }
205 
206     /**
207      * {@inheritDoc}
208      *
209      * @see XdocMarkup#AUTHOR_TAG
210      */
211     public void author_()
212     {
213         if ( getTextBuffer().length() > 0 )
214         {
215             writeStartTag( AUTHOR_TAG );
216             String text = HtmlTools.escapeHTML( getTextBuffer().toString() );
217             // hack: un-escape numerical entities that have been escaped above
218             // note that numerical entities should really be written as one unicode character in the first place
219             text = StringUtils.replace( text, "&amp;#", "&#" );
220             write( text );
221             writeEndTag( AUTHOR_TAG );
222             resetTextBuffer();
223         }
224     }
225 
226     /**
227      * {@inheritDoc}
228      *
229      * @see XdocMarkup#DATE_TAG
230      */
231     public void date_()
232     {
233         if ( getTextBuffer().length() > 0 )
234         {
235             writeStartTag( DATE_TAG );
236             content( getTextBuffer().toString() );
237             writeEndTag( DATE_TAG );
238             resetTextBuffer();
239         }
240     }
241 
242     /**
243      * {@inheritDoc}
244      *
245      * @see #body(org.apache.maven.doxia.sink.SinkEventAttributes)
246      */
247     public void body()
248     {
249        body( null );
250     }
251 
252     /**
253      * {@inheritDoc}
254      * @see javax.swing.text.html.HTML.Tag#BODY
255      */
256     public void body( SinkEventAttributes attributes )
257     {
258         writeStartTag( BODY, attributes );
259     }
260 
261     /**
262      * {@inheritDoc}
263      *
264      * @see javax.swing.text.html.HTML.Tag#BODY
265      * @see XdocMarkup#DOCUMENT_TAG
266      */
267     public void body_()
268     {
269         writeEndTag( BODY );
270 
271         writeEndTag( DOCUMENT_TAG );
272 
273         flush();
274 
275         init();
276     }
277 
278     // ----------------------------------------------------------------------
279     // Sections
280     // ----------------------------------------------------------------------
281 
282     /**
283      * {@inheritDoc}
284      *
285      * Starts a section.
286      * @see XdocMarkup#SECTION_TAG
287      * @see XdocMarkup#SUBSECTION_TAG
288      */
289     protected void onSection( int depth, SinkEventAttributes attributes )
290     {
291         if ( depth == SECTION_LEVEL_1 )
292         {
293             write( LESS_THAN + SECTION_TAG.toString()
294                     + SinkUtils.getAttributeString(
295                         SinkUtils.filterAttributes( attributes, SinkUtils.SINK_BASE_ATTRIBUTES ) )
296                     + SPACE + Attribute.NAME + EQUAL + QUOTE );
297         }
298         else if ( depth == SECTION_LEVEL_2 )
299         {
300             write( LESS_THAN + SUBSECTION_TAG.toString()
301                     + SinkUtils.getAttributeString(
302                         SinkUtils.filterAttributes( attributes, SinkUtils.SINK_BASE_ATTRIBUTES  ) )
303                     + SPACE + Attribute.NAME + EQUAL + QUOTE );
304         }
305     }
306 
307     /**
308      * {@inheritDoc}
309      *
310      * Ends a section.
311      * @see XdocMarkup#SECTION_TAG
312      * @see XdocMarkup#SUBSECTION_TAG
313      */
314     protected void onSection_( int depth )
315     {
316         if ( depth == SECTION_LEVEL_1 )
317         {
318             writeEndTag( SECTION_TAG );
319         }
320         else if ( depth == SECTION_LEVEL_2 )
321         {
322             writeEndTag( SUBSECTION_TAG );
323         }
324     }
325 
326     /**
327      * {@inheritDoc}
328      *
329      * Starts a section title.
330      * @see javax.swing.text.html.HTML.Tag#H4
331      * @see javax.swing.text.html.HTML.Tag#H5
332      * @see javax.swing.text.html.HTML.Tag#H6
333      */
334     protected void onSectionTitle( int depth, SinkEventAttributes attributes )
335     {
336         MutableAttributeSet atts = SinkUtils.filterAttributes(
337                 attributes, SinkUtils.SINK_SECTION_ATTRIBUTES  );
338 
339         if ( depth == SECTION_LEVEL_3 )
340         {
341             writeStartTag( H4, atts );
342         }
343         else if ( depth == SECTION_LEVEL_4 )
344         {
345             writeStartTag( H5, atts );
346         }
347         else if ( depth == SECTION_LEVEL_5 )
348         {
349             writeStartTag( H6, atts );
350         }
351     }
352 
353     /**
354      * {@inheritDoc}
355      *
356      * Ends a section title.
357      * @see javax.swing.text.html.HTML.Tag#H4
358      * @see javax.swing.text.html.HTML.Tag#H5
359      * @see javax.swing.text.html.HTML.Tag#H6
360      */
361     protected void onSectionTitle_( int depth )
362     {
363         if ( depth == SECTION_LEVEL_1 || depth == SECTION_LEVEL_2 )
364         {
365             write( String.valueOf( QUOTE ) + GREATER_THAN );
366         }
367         else if ( depth == SECTION_LEVEL_3 )
368         {
369             writeEndTag( H4 );
370         }
371         else if ( depth == SECTION_LEVEL_4 )
372         {
373             writeEndTag( H5 );
374         }
375         else if ( depth == SECTION_LEVEL_5 )
376         {
377             writeEndTag( H6 );
378         }
379     }
380 
381     // -----------------------------------------------------------------------
382     //
383     // -----------------------------------------------------------------------
384 
385     /**
386      * {@inheritDoc}
387      *
388      * @see XdocMarkup#SOURCE_TAG
389      * @see javax.swing.text.html.HTML.Tag#PRE
390      * @param attributes a {@link org.apache.maven.doxia.sink.SinkEventAttributes} object.
391      */
392     public void verbatim( SinkEventAttributes attributes )
393     {
394         setVerbatimFlag( true );
395 
396         MutableAttributeSet atts = SinkUtils.filterAttributes(
397                 attributes, SinkUtils.SINK_VERBATIM_ATTRIBUTES  );
398 
399 
400         if ( atts == null )
401         {
402             atts = new SinkEventAttributeSet();
403         }
404 
405         boolean boxed = false;
406 
407         if ( atts.isDefined( SinkEventAttributes.DECORATION ) )
408         {
409             boxed = "boxed".equals( atts.getAttribute( SinkEventAttributes.DECORATION ) );
410         }
411 
412         boxedFlag = boxed;
413         atts.removeAttribute( SinkEventAttributes.DECORATION );
414 
415         if ( boxed )
416         {
417             writeStartTag( SOURCE_TAG, atts );
418         }
419         else
420         {
421             atts.removeAttribute( Attribute.ALIGN.toString() );
422             writeStartTag( PRE, atts );
423         }
424     }
425 
426     /**
427      * {@inheritDoc}
428      *
429      * @see XdocMarkup#SOURCE_TAG
430      * @see javax.swing.text.html.HTML.Tag#PRE
431      */
432     public void verbatim_()
433     {
434         if ( boxedFlag )
435         {
436             writeEndTag( SOURCE_TAG );
437         }
438         else
439         {
440             writeEndTag( PRE );
441         }
442 
443         setVerbatimFlag( false );
444 
445         boxedFlag = false;
446     }
447 
448     /**
449      * The default align is <code>center</code>.
450      *
451      * {@inheritDoc}
452      * @see javax.swing.text.html.HTML.Tag#TABLE
453      */
454     public void tableRows( int[] justification, boolean grid )
455     {
456         // similar to super.tableRows( justification, grid ) but without class.
457 
458         this.tableRows = true;
459 
460         setCellJustif( justification );
461 
462         if ( this.tableAttributes == null )
463         {
464             this.tableAttributes = new SinkEventAttributeSet( 0 );
465         }
466 
467         MutableAttributeSet att = new SinkEventAttributeSet();
468 
469         if ( !tableAttributes.isDefined( Attribute.BORDER.toString() ) )
470         {
471             att.addAttribute( Attribute.BORDER, ( grid ? "1" : "0" ) );
472         }
473 
474         att.addAttributes( tableAttributes );
475 
476         tableAttributes.removeAttributes( tableAttributes );
477 
478         writeStartTag( TABLE, att );
479     }
480 
481     /**
482      * The default valign is <code>top</code>.
483      *
484      * {@inheritDoc}
485      *
486      * @see javax.swing.text.html.HTML.Tag#TR
487      */
488     public void tableRow()
489     {
490         MutableAttributeSet att = new SinkEventAttributeSet();
491         att.addAttribute( Attribute.VALIGN, "top" );
492 
493         writeStartTag( TR, att );
494 
495         setCellCount( 0 );
496     }
497 
498     /**
499      * <p>close.</p>
500      */
501     public void close()
502     {
503         super.close();
504 
505         init();
506     }
507 
508     /**
509      * Adds a link with an optional target.
510      *
511      * @param name the link name.
512      * @param target the link target, may be null.
513      */
514     public void link( String name, String target )
515     {
516         if ( isHeadFlag() )
517         {
518             return;
519         }
520 
521         MutableAttributeSet att = new SinkEventAttributeSet();
522 
523         att.addAttribute( Attribute.HREF, HtmlTools.escapeHTML( name ) );
524 
525         if ( target != null )
526         {
527             att.addAttribute( Attribute.TARGET, target );
528         }
529 
530         writeStartTag( A, att );
531     }
532 
533     // ----------------------------------------------------------------------
534     //
535     // ----------------------------------------------------------------------
536 
537     /**
538      * Write text to output, preserving white space.
539      *
540      * @param text The text to write.
541      * @deprecated use write(String)
542      */
543     protected void markup( String text )
544     {
545         write( text );
546     }
547 }