001package org.apache.maven.doxia.module.xdoc;
002
003/*
004 * Licensed to the Apache Software Foundation (ASF) under one
005 * or more contributor license agreements.  See the NOTICE file
006 * distributed with this work for additional information
007 * regarding copyright ownership.  The ASF licenses this file
008 * to you under the Apache License, Version 2.0 (the
009 * "License"); you may not use this file except in compliance
010 * with the License.  You may obtain a copy of the License at
011 *
012 *   http://www.apache.org/licenses/LICENSE-2.0
013 *
014 * Unless required by applicable law or agreed to in writing,
015 * software distributed under the License is distributed on an
016 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017 * KIND, either express or implied.  See the License for the
018 * specific language governing permissions and limitations
019 * under the License.
020 */
021
022import java.io.Writer;
023
024import javax.swing.text.MutableAttributeSet;
025import javax.swing.text.html.HTML.Attribute;
026
027import org.apache.maven.doxia.sink.SinkEventAttributes;
028import org.apache.maven.doxia.sink.impl.SinkEventAttributeSet;
029import org.apache.maven.doxia.sink.impl.SinkUtils;
030import org.apache.maven.doxia.sink.impl.XhtmlBaseSink;
031import org.apache.maven.doxia.util.HtmlTools;
032
033import org.codehaus.plexus.util.StringUtils;
034
035/**
036 * <a href="http://maven.apache.org/doxia/references/xdoc-format.html">Xdoc</a> Sink implementation.
037 * <br/>
038 * It uses the Xdoc XSD <a href="http://maven.apache.org/xsd/xdoc-2.0.xsd">
039 * http://maven.apache.org/xsd/xdoc-2.0.xsd</a>.
040 *
041 * @author <a href="mailto:james@jamestaylor.org">James Taylor</a>
042 * @version $Id$
043 * @since 1.0
044 */
045public class XdocSink
046    extends XhtmlBaseSink
047    implements XdocMarkup
048{
049    // ----------------------------------------------------------------------
050    // Instance fields
051    // ----------------------------------------------------------------------
052
053    /** An indication on if we're inside a box (verbatim). */
054    private boolean boxedFlag;
055
056    private String encoding;
057
058    private String languageId;
059
060    // ----------------------------------------------------------------------
061    // Constructors
062    // ----------------------------------------------------------------------
063
064    /**
065     * Constructor, initialize the Writer.
066     *
067     * @param writer not null writer to write the result. <b>Should</b> be an UTF-8 Writer.
068     * You could use <code>newXmlWriter</code> methods from {@link org.codehaus.plexus.util.WriterFactory}.
069     */
070    protected XdocSink( Writer writer )
071    {
072        super( writer );
073    }
074
075    /**
076     * Constructor, initialize the Writer and tells which encoding is used.
077     *
078     * @param writer not null writer to write the result.
079     * @param encoding the encoding used, that should be written to the generated HTML content
080     * if not <code>null</code>.
081     * @since 1.1
082     */
083    protected XdocSink( Writer writer, String encoding )
084    {
085        this( writer );
086        this.encoding = encoding;
087    }
088
089    /**
090     * Constructor, initialize the Writer and tells which encoding and languageId are used.
091     *
092     * @param writer not null writer to write the result.
093     * @param encoding the encoding used, that should be written to the generated HTML content
094     * if not <code>null</code>.
095     * @param languageId language identifier for the root element as defined by
096     * <a href="ftp://ftp.isi.edu/in-notes/bcp/bcp47.txt">IETF BCP 47</a>, Tags for the Identification of Languages;
097     * in addition, the empty string may be specified.
098     * @since 1.1
099     */
100    protected XdocSink( Writer writer, String encoding, String languageId )
101    {
102        this( writer, encoding );
103
104        this.languageId = languageId;
105    }
106
107    // ----------------------------------------------------------------------
108    // Public protected methods
109    // ----------------------------------------------------------------------
110
111    /** {@inheritDoc} */
112    protected void init()
113    {
114        super.init();
115
116        boxedFlag = false;
117    }
118
119    /**
120     * {@inheritDoc}
121     * @see #head(org.apache.maven.doxia.sink.SinkEventAttributes)
122     */
123    public void head()
124    {
125        head( null );
126    }
127
128    /**
129     * {@inheritDoc}
130     * @see XdocMarkup#DOCUMENT_TAG
131     * @see XdocMarkup#PROPERTIES_TAG
132     */
133    public void head( SinkEventAttributes attributes )
134    {
135        init();
136
137        setHeadFlag( true );
138
139        write( "<?xml version=\"1.0\"" );
140        if ( encoding != null )
141        {
142            write( " encoding=\"" + encoding + "\"" );
143        }
144        write( "?>" );
145
146        MutableAttributeSet atts = new SinkEventAttributeSet();
147        atts.addAttribute( "xmlns", XDOC_NAMESPACE );
148        atts.addAttribute( "xmlns:xsi", XML_NAMESPACE );
149        atts.addAttribute( "xsi:schemaLocation", XDOC_NAMESPACE + " " + XDOC_SYSTEM_ID );
150
151        if ( languageId != null )
152        {
153            atts.addAttribute( Attribute.LANG.toString(), languageId );
154            atts.addAttribute( "xml:lang", languageId );
155        }
156
157        if ( attributes != null )
158        {
159            atts.addAttributes( attributes );
160        }
161
162        writeStartTag( DOCUMENT_TAG, atts );
163
164        writeStartTag( PROPERTIES_TAG );
165    }
166
167    /**
168     * {@inheritDoc}
169     * @see XdocMarkup#DOCUMENT_TAG
170     * @see XdocMarkup#PROPERTIES_TAG
171     */
172    public void head_()
173    {
174        setHeadFlag( false );
175
176        writeEndTag( PROPERTIES_TAG );
177    }
178
179    /**
180     * {@inheritDoc}
181     * @see javax.swing.text.html.HTML.Tag#TITLE
182     */
183    public void title()
184    {
185        writeStartTag( TITLE );
186    }
187
188    /**
189     * {@inheritDoc}
190     * @see javax.swing.text.html.HTML.Tag#TITLE
191     */
192    public void title_()
193    {
194        content( getTextBuffer().toString() );
195
196        writeEndTag( TITLE );
197
198        resetTextBuffer();
199    }
200
201    /**
202     * {@inheritDoc}
203     * @see XdocMarkup#AUTHOR_TAG
204     */
205    public void author_()
206    {
207        if ( getTextBuffer().length() > 0 )
208        {
209            writeStartTag( AUTHOR_TAG );
210            String text = HtmlTools.escapeHTML( getTextBuffer().toString() );
211            // hack: un-escape numerical entities that have been escaped above
212            // note that numerical entities should really be written as one unicode character in the first place
213            text = StringUtils.replace( text, "&amp;#", "&#" );
214            write( text );
215            writeEndTag( AUTHOR_TAG );
216            resetTextBuffer();
217        }
218    }
219
220    /**
221     * {@inheritDoc}
222     * @see XdocMarkup#DATE_TAG
223     */
224    public void date_()
225    {
226        if ( getTextBuffer().length() > 0 )
227        {
228            writeStartTag( DATE_TAG );
229            content( getTextBuffer().toString() );
230            writeEndTag( DATE_TAG );
231            resetTextBuffer();
232        }
233    }
234
235    /**
236     * {@inheritDoc}
237     * @see #body(org.apache.maven.doxia.sink.SinkEventAttributes)
238     */
239    public void body()
240    {
241       body( null );
242    }
243
244    /**
245     * {@inheritDoc}
246     * @see javax.swing.text.html.HTML.Tag#BODY
247     */
248    public void body( SinkEventAttributes attributes )
249    {
250        writeStartTag( BODY, attributes );
251    }
252
253    /**
254     * {@inheritDoc}
255     * @see javax.swing.text.html.HTML.Tag#BODY
256     * @see XdocMarkup#DOCUMENT_TAG
257     */
258    public void body_()
259    {
260        writeEndTag( BODY );
261
262        writeEndTag( DOCUMENT_TAG );
263
264        flush();
265
266        init();
267    }
268
269    // ----------------------------------------------------------------------
270    // Sections
271    // ----------------------------------------------------------------------
272
273    /**
274     * {@inheritDoc}
275     *
276     * Starts a section.
277     * @see XdocMarkup#SECTION_TAG
278     * @see XdocMarkup#SUBSECTION_TAG
279     */
280    protected void onSection( int depth, SinkEventAttributes attributes )
281    {
282        if ( depth == SECTION_LEVEL_1 )
283        {
284            write( String.valueOf( LESS_THAN ) + SECTION_TAG.toString()
285                    + SinkUtils.getAttributeString(
286                        SinkUtils.filterAttributes( attributes, SinkUtils.SINK_BASE_ATTRIBUTES ) )
287                    + String.valueOf( SPACE ) + Attribute.NAME + String.valueOf( EQUAL ) + String.valueOf( QUOTE ) );
288        }
289        else if ( depth == SECTION_LEVEL_2 )
290        {
291            write( String.valueOf( LESS_THAN ) + SUBSECTION_TAG.toString()
292                    + SinkUtils.getAttributeString(
293                        SinkUtils.filterAttributes( attributes, SinkUtils.SINK_BASE_ATTRIBUTES  ) )
294                    + String.valueOf( SPACE ) + Attribute.NAME + String.valueOf( EQUAL ) + String.valueOf( QUOTE ) );
295        }
296    }
297
298    /**
299     * {@inheritDoc}
300     *
301     * Ends a section.
302     * @see XdocMarkup#SECTION_TAG
303     * @see XdocMarkup#SUBSECTION_TAG
304     */
305    protected void onSection_( int depth )
306    {
307        if ( depth == SECTION_LEVEL_1 )
308        {
309            writeEndTag( SECTION_TAG );
310        }
311        else if ( depth == SECTION_LEVEL_2 )
312        {
313            writeEndTag( SUBSECTION_TAG );
314        }
315    }
316
317    /**
318     * {@inheritDoc}
319     *
320     * Starts a section title.
321     * @see javax.swing.text.html.HTML.Tag#H4
322     * @see javax.swing.text.html.HTML.Tag#H5
323     * @see javax.swing.text.html.HTML.Tag#H6
324     */
325    protected void onSectionTitle( int depth, SinkEventAttributes attributes )
326    {
327        MutableAttributeSet atts = SinkUtils.filterAttributes(
328                attributes, SinkUtils.SINK_SECTION_ATTRIBUTES  );
329
330        if ( depth == SECTION_LEVEL_3 )
331        {
332            writeStartTag( H4, atts );
333        }
334        else if ( depth == SECTION_LEVEL_4 )
335        {
336            writeStartTag( H5, atts );
337        }
338        else if ( depth == SECTION_LEVEL_5 )
339        {
340            writeStartTag( H6, atts );
341        }
342    }
343
344    /**
345     * {@inheritDoc}
346     *
347     * Ends a section title.
348     * @see javax.swing.text.html.HTML.Tag#H4
349     * @see javax.swing.text.html.HTML.Tag#H5
350     * @see javax.swing.text.html.HTML.Tag#H6
351     */
352    protected void onSectionTitle_( int depth )
353    {
354        if ( depth == SECTION_LEVEL_1 || depth == SECTION_LEVEL_2 )
355        {
356            write( String.valueOf( QUOTE ) + String.valueOf( GREATER_THAN ) );
357        }
358        else if ( depth == SECTION_LEVEL_3 )
359        {
360            writeEndTag( H4 );
361        }
362        else if ( depth == SECTION_LEVEL_4 )
363        {
364            writeEndTag( H5 );
365        }
366        else if ( depth == SECTION_LEVEL_5 )
367        {
368            writeEndTag( H6 );
369        }
370    }
371
372    // -----------------------------------------------------------------------
373    //
374    // -----------------------------------------------------------------------
375
376    /**
377     * {@inheritDoc}
378     * @see XdocMarkup#SOURCE_TAG
379     * @see javax.swing.text.html.HTML.Tag#PRE
380     */
381    public void verbatim( SinkEventAttributes attributes )
382    {
383        setVerbatimFlag( true );
384
385        MutableAttributeSet atts = SinkUtils.filterAttributes(
386                attributes, SinkUtils.SINK_VERBATIM_ATTRIBUTES  );
387
388
389        if ( atts == null )
390        {
391            atts = new SinkEventAttributeSet();
392        }
393
394        boolean boxed = false;
395
396        if ( atts.isDefined( SinkEventAttributes.DECORATION ) )
397        {
398            boxed = "boxed".equals(
399                (String) atts.getAttribute( SinkEventAttributes.DECORATION ) );
400        }
401
402        boxedFlag = boxed;
403        atts.removeAttribute( SinkEventAttributes.DECORATION );
404
405        if ( boxed )
406        {
407            writeStartTag( SOURCE_TAG, atts );
408        }
409        else
410        {
411            atts.removeAttribute( Attribute.ALIGN.toString() );
412            writeStartTag( PRE, atts );
413        }
414    }
415
416    /**
417     * {@inheritDoc}
418     * @see XdocMarkup#SOURCE_TAG
419     * @see javax.swing.text.html.HTML.Tag#PRE
420     */
421    public void verbatim_()
422    {
423        if ( boxedFlag )
424        {
425            writeEndTag( SOURCE_TAG );
426        }
427        else
428        {
429            writeEndTag( PRE );
430        }
431
432        setVerbatimFlag( false );
433
434        boxedFlag = false;
435    }
436
437    /**
438     * The default align is <code>center</code>.
439     *
440     * {@inheritDoc}
441     * @see javax.swing.text.html.HTML.Tag#TABLE
442     */
443    public void tableRows( int[] justification, boolean grid )
444    {
445        // similar to super.tableRows( justification, grid ) but without class.
446
447        this.tableRows = true;
448
449        setCellJustif( justification );
450
451        if ( this.tableAttributes == null )
452        {
453            this.tableAttributes = new SinkEventAttributeSet( 0 );
454        }
455
456        MutableAttributeSet att = new SinkEventAttributeSet();
457
458        if ( !tableAttributes.isDefined( Attribute.BORDER.toString() ) )
459        {
460            att.addAttribute( Attribute.BORDER, ( grid ? "1" : "0" ) );
461        }
462
463        att.addAttributes( tableAttributes );
464
465        tableAttributes.removeAttributes( tableAttributes );
466
467        writeStartTag( TABLE, att );
468    }
469
470    /**
471     * The default valign is <code>top</code>.
472     *
473     * {@inheritDoc}
474     * @see javax.swing.text.html.HTML.Tag#TR
475     */
476    public void tableRow()
477    {
478        MutableAttributeSet att = new SinkEventAttributeSet();
479        att.addAttribute( Attribute.VALIGN, "top" );
480
481        writeStartTag( TR, att );
482
483        setCellCount( 0 );
484    }
485
486    public void close()
487    {
488        super.close();
489
490        init();
491    }
492
493    /**
494     * Adds a link with an optional target.
495     *
496     * @param name the link name.
497     * @param target the link target, may be null.
498     */
499    public void link( String name, String target )
500    {
501        if ( isHeadFlag() )
502        {
503            return;
504        }
505
506        MutableAttributeSet att = new SinkEventAttributeSet();
507
508        att.addAttribute( Attribute.HREF, HtmlTools.escapeHTML( name ) );
509
510        if ( target != null )
511        {
512            att.addAttribute( Attribute.TARGET, target );
513        }
514
515        writeStartTag( A, att );
516    }
517
518    // ----------------------------------------------------------------------
519    //
520    // ----------------------------------------------------------------------
521
522    /**
523     * Write text to output, preserving white space.
524     *
525     * @param text The text to write.
526     * @deprecated use write(String)
527     */
528    protected void markup( String text )
529    {
530        write( text );
531    }
532}