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="https://maven.apache.org/doxia/references/xdoc-format.html">Xdoc</a> Sink implementation.
037 * <br>
038 * It uses the Xdoc XSD <a href="https://maven.apache.org/xsd/xdoc-2.0.xsd">
039 * https://maven.apache.org/xsd/xdoc-2.0.xsd</a>.
040 *
041 * @author <a href="mailto:james@jamestaylor.org">James Taylor</a>
042 * @since 1.0
043 */
044public class XdocSink
045    extends XhtmlBaseSink
046    implements XdocMarkup
047{
048    // ----------------------------------------------------------------------
049    // Instance fields
050    // ----------------------------------------------------------------------
051
052    /** An indication on if we're inside a box (verbatim). */
053    private boolean boxedFlag;
054
055    private String encoding;
056
057    private String languageId;
058
059    // ----------------------------------------------------------------------
060    // Constructors
061    // ----------------------------------------------------------------------
062
063    /**
064     * Constructor, initialize the Writer.
065     *
066     * @param writer not null writer to write the result. <b>Should</b> be an UTF-8 Writer.
067     * You could use <code>newXmlWriter</code> methods from {@link org.codehaus.plexus.util.WriterFactory}.
068     */
069    protected XdocSink( Writer writer )
070    {
071        super( writer );
072    }
073
074    /**
075     * Constructor, initialize the Writer and tells which encoding is used.
076     *
077     * @param writer not null writer to write the result.
078     * @param encoding the encoding used, that should be written to the generated HTML content
079     * if not <code>null</code>.
080     * @since 1.1
081     */
082    protected XdocSink( Writer writer, String encoding )
083    {
084        this( writer );
085        this.encoding = encoding;
086    }
087
088    /**
089     * Constructor, initialize the Writer and tells which encoding and languageId are used.
090     *
091     * @param writer not null writer to write the result.
092     * @param encoding the encoding used, that should be written to the generated HTML content
093     * if not <code>null</code>.
094     * @param languageId language identifier for the root element as defined by
095     * <a href="ftp://ftp.isi.edu/in-notes/bcp/bcp47.txt">IETF BCP 47</a>, Tags for the Identification of Languages;
096     * in addition, the empty string may be specified.
097     * @since 1.1
098     */
099    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}