001package org.apache.maven.doxia.module.xhtml5;
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.markup.HtmlMarkup;
028import org.apache.maven.doxia.sink.SinkEventAttributes;
029import org.apache.maven.doxia.sink.impl.SinkEventAttributeSet;
030import org.apache.maven.doxia.sink.impl.SinkUtils;
031import org.apache.maven.doxia.sink.impl.Xhtml5BaseSink;
032import org.apache.maven.doxia.util.HtmlTools;
033import org.codehaus.plexus.util.StringUtils;
034
035/**
036 * <a href="https://www.w3.org/TR/html52/">XHTML 5.2</a> sink implementation.
037 */
038public class Xhtml5Sink
039    extends Xhtml5BaseSink
040    implements Xhtml5Markup
041{
042    // ----------------------------------------------------------------------
043    // Instance fields
044    // ----------------------------------------------------------------------
045
046    private String encoding;
047
048    private String languageId;
049
050    /** An indication on if we're inside a head title. */
051    private boolean headTitleFlag;
052
053    // ----------------------------------------------------------------------
054    // Constructors
055    // ----------------------------------------------------------------------
056
057    /**
058     * Constructor, initialize the Writer.
059     *
060     * @param writer not null writer to write the result.
061     */
062    protected Xhtml5Sink( Writer writer )
063    {
064        super( writer );
065    }
066
067    /**
068     * Constructor, initialize the Writer and tells which encoding is used.
069     *
070     * @param writer not null writer to write the result.
071     * @param encoding the encoding used, that should be written to the generated HTML content
072     * if not <code>null</code>.
073     */
074    protected Xhtml5Sink( Writer writer, String encoding )
075    {
076        super( writer );
077
078        this.encoding = encoding;
079    }
080
081    /**
082     * Constructor, initialize the Writer and tells which encoding and languageId are used.
083     *
084     * @param writer not null writer to write the result.
085     * @param encoding the encoding used, that should be written to the generated HTML content
086     * if not <code>null</code>.
087     * @param languageId language identifier for the root element as defined by
088     * <a href="ftp://ftp.isi.edu/in-notes/bcp/bcp47.txt">IETF BCP 47</a>, Tags for the Identification of Languages;
089     * in addition, the empty string may be specified.
090     */
091    protected Xhtml5Sink( Writer writer, String encoding, String languageId )
092    {
093        this( writer, encoding );
094
095        this.languageId = languageId;
096    }
097
098    /**
099     * {@inheritDoc}
100     */
101    public void head()
102    {
103        init();
104
105        setHeadFlag( true );
106
107        write( "<!DOCTYPE html>" );
108
109        MutableAttributeSet atts = new SinkEventAttributeSet();
110        atts.addAttribute( "xmlns", XHTML5_NAMESPACE );
111
112        if ( languageId != null )
113        {
114            atts.addAttribute( Attribute.LANG.toString(), languageId );
115            atts.addAttribute( "xml:lang", languageId );
116        }
117
118        writeStartTag( HTML, atts );
119
120        writeStartTag( HEAD );
121    }
122
123    /**
124     * {@inheritDoc}
125     */
126    public void head_()
127    {
128        if ( !isHeadTitleFlag() )
129        {
130            // The content of element type "head" must match
131            // "((script|style|meta|link|object|isindex)*,
132            //  ((title,(script|style|meta|link|object|isindex)*,
133            //  (base,(script|style|meta|link|object|isindex)*)?)|(base,(script|style|meta|link|object|isindex)*,
134            //  (title,(script|style|meta|link|object|isindex)*))))"
135            writeStartTag( TITLE );
136            writeEndTag( TITLE );
137        }
138
139        setHeadFlag( false );
140        setHeadTitleFlag( false );
141
142        if ( encoding != null )
143        {
144            write( "<meta charset=\"" + encoding + "\"/>" );
145        }
146
147        writeEndTag( HEAD );
148    }
149
150    /**
151     * {@inheritDoc}
152     *
153     * @see javax.swing.text.html.HTML.Tag#TITLE
154     */
155    public void title()
156    {
157        setHeadTitleFlag( true );
158
159        writeStartTag( TITLE );
160    }
161
162    /**
163     * {@inheritDoc}
164     *
165     * @see javax.swing.text.html.HTML.Tag#TITLE
166     */
167    public void title_()
168    {
169        content( getTextBuffer().toString() );
170
171        writeEndTag( TITLE );
172
173        resetTextBuffer();
174
175    }
176
177    /**
178     * {@inheritDoc}
179     *
180     * @see javax.swing.text.html.HTML.Tag#META
181     */
182    public void author_()
183    {
184        if ( getTextBuffer().length() > 0 )
185        {
186            MutableAttributeSet att = new SinkEventAttributeSet();
187            att.addAttribute( Attribute.NAME, "author" );
188            String text = HtmlTools.escapeHTML( getTextBuffer().toString() );
189            // hack: un-escape numerical entities that have been escaped above
190            // note that numerical entities should really be added as one unicode character in the first place
191            text = StringUtils.replace( text, "&amp;#", "&#" );
192            att.addAttribute( Attribute.CONTENT, text );
193
194            writeSimpleTag( META, att );
195
196            resetTextBuffer();
197        }
198    }
199
200    /**
201     * {@inheritDoc}
202     *
203     * @see javax.swing.text.html.HTML.Tag#META
204     */
205    public void date_()
206    {
207        if ( getTextBuffer().length() > 0 )
208        {
209            MutableAttributeSet att = new SinkEventAttributeSet();
210            att.addAttribute( Attribute.NAME, "date" );
211            att.addAttribute( Attribute.CONTENT, getTextBuffer().toString() );
212
213            writeSimpleTag( META, att );
214
215            resetTextBuffer();
216        }
217    }
218
219    /**
220     * {@inheritDoc}
221     *
222     * @see javax.swing.text.html.HTML.Tag#BODY
223     */
224    public void body()
225    {
226        writeStartTag( BODY );
227    }
228
229    /**
230     * {@inheritDoc}
231     *
232     * @see javax.swing.text.html.HTML.Tag#BODY
233     * @see javax.swing.text.html.HTML.Tag#HTML
234     */
235    public void body_()
236    {
237        writeEndTag( BODY );
238
239        writeEndTag( HTML );
240
241        flush();
242
243        init();
244    }
245
246    /**
247     * {@inheritDoc}
248     *
249     * Starts a section..
250     * @see javax.swing.text.html.HTML.Tag#DIV
251     */
252    protected void onSection( int depth, SinkEventAttributes attributes )
253    {
254        if ( depth >= SECTION_LEVEL_1 && depth <= SECTION_LEVEL_5 )
255        {
256            MutableAttributeSet att = new SinkEventAttributeSet();
257            att.addAttributes( SinkUtils.filterAttributes(
258                    attributes, SinkUtils.SINK_BASE_ATTRIBUTES  ) );
259
260            writeStartTag( HtmlMarkup.SECTION, att );
261        }
262    }
263
264    /**
265     * {@inheritDoc}
266     *
267     * Ends a section.
268     * @see javax.swing.text.html.HTML.Tag#DIV
269     */
270    protected void onSection_( int depth )
271    {
272        if ( depth >= SECTION_LEVEL_1 && depth <= SECTION_LEVEL_5 )
273        {
274            writeEndTag( HtmlMarkup.SECTION );
275        }
276    }
277
278    /**
279     * {@inheritDoc}
280     *
281     * Starts a section title.
282     * @see javax.swing.text.html.HTML.Tag#H2
283     * @see javax.swing.text.html.HTML.Tag#H3
284     * @see javax.swing.text.html.HTML.Tag#H4
285     * @see javax.swing.text.html.HTML.Tag#H5
286     * @see javax.swing.text.html.HTML.Tag#H6
287     */
288    protected void onSectionTitle( int depth, SinkEventAttributes attributes )
289    {
290        MutableAttributeSet atts = SinkUtils.filterAttributes(
291                attributes, SinkUtils.SINK_SECTION_ATTRIBUTES  );
292
293        if ( depth == SECTION_LEVEL_1 )
294        {
295            writeStartTag( HtmlMarkup.H2, atts );
296        }
297        else if ( depth == SECTION_LEVEL_2 )
298        {
299            writeStartTag( HtmlMarkup.H3, atts );
300        }
301        else if ( depth == SECTION_LEVEL_3 )
302        {
303            writeStartTag( HtmlMarkup.H4, atts );
304        }
305        else if ( depth == SECTION_LEVEL_4 )
306        {
307            writeStartTag( HtmlMarkup.H5, atts );
308        }
309        else if ( depth == SECTION_LEVEL_5 )
310        {
311            writeStartTag( HtmlMarkup.H6, atts );
312        }
313    }
314
315    /**
316     * {@inheritDoc}
317     *
318     * Ends a section title.
319     * @see javax.swing.text.html.HTML.Tag#H2
320     * @see javax.swing.text.html.HTML.Tag#H3
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 )
326    {
327        if ( depth == SECTION_LEVEL_1 )
328        {
329            writeEndTag( HtmlMarkup.H2 );
330        }
331        else if ( depth == SECTION_LEVEL_2 )
332        {
333            writeEndTag( HtmlMarkup.H3 );
334        }
335        else if ( depth == SECTION_LEVEL_3 )
336        {
337            writeEndTag( HtmlMarkup.H4 );
338        }
339        else if ( depth == SECTION_LEVEL_4 )
340        {
341            writeEndTag( HtmlMarkup.H5 );
342        }
343        else if ( depth == SECTION_LEVEL_5 )
344        {
345            writeEndTag( HtmlMarkup.H6 );
346        }
347    }
348
349    // ----------------------------------------------------------------------
350    // Public protected methods
351    // ----------------------------------------------------------------------
352
353    /**
354     * <p>Setter for the field <code>headTitleFlag</code>.</p>
355     *
356     * @param headTitleFlag an header title flag.
357     * @since 1.1
358     */
359    protected void setHeadTitleFlag( boolean headTitleFlag )
360    {
361        this.headTitleFlag = headTitleFlag;
362    }
363
364    /**
365     * <p>isHeadTitleFlag.</p>
366     *
367     * @return the current headTitleFlag.
368     * @since 1.1
369     */
370    protected boolean isHeadTitleFlag()
371    {
372        return this.headTitleFlag ;
373    }
374}