001package org.apache.maven.doxia.module.xhtml;
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.impl.SinkEventAttributeSet;
028import org.apache.maven.doxia.sink.impl.XhtmlBaseSink;
029import org.apache.maven.doxia.util.HtmlTools;
030
031import org.codehaus.plexus.util.StringUtils;
032
033/**
034 * <a href="http://www.w3.org/TR/xhtml1/">Xhtml 1.0 Transitional</a> sink implementation.
035 * <br>
036 * It uses the DTD/xhtml1-transitional <a href="http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
037 * http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd</a>.
038 *
039 * @author Jason van Zyl
040 * @author ltheussl
041 * @since 1.0
042 */
043public class XhtmlSink
044    extends XhtmlBaseSink
045    implements XhtmlMarkup
046{
047    // ----------------------------------------------------------------------
048    // Instance fields
049    // ----------------------------------------------------------------------
050
051    private String encoding;
052
053    private String languageId;
054
055    /** An indication on if we're inside a head title. */
056    private boolean headTitleFlag;
057
058    // ----------------------------------------------------------------------
059    // Constructors
060    // ----------------------------------------------------------------------
061
062    /**
063     * Constructor, initialize the Writer.
064     *
065     * @param writer not null writer to write the result.
066     */
067    protected XhtmlSink( Writer writer )
068    {
069        super( writer );
070    }
071
072    /**
073     * Constructor, initialize the Writer and tells which encoding is used.
074     *
075     * @param writer not null writer to write the result.
076     * @param encoding the encoding used, that should be written to the generated HTML content
077     * if not <code>null</code>.
078     */
079    protected XhtmlSink( Writer writer, String encoding )
080    {
081        super( writer );
082
083        this.encoding = encoding;
084    }
085
086    /**
087     * Constructor, initialize the Writer and tells which encoding and languageId are used.
088     *
089     * @param writer not null writer to write the result.
090     * @param encoding the encoding used, that should be written to the generated HTML content
091     * if not <code>null</code>.
092     * @param languageId language identifier for the root element as defined by
093     * <a href="ftp://ftp.isi.edu/in-notes/bcp/bcp47.txt">IETF BCP 47</a>, Tags for the Identification of Languages;
094     * in addition, the empty string may be specified.
095     */
096    protected XhtmlSink( Writer writer, String encoding, String languageId )
097    {
098        this( writer, encoding );
099
100        this.languageId = languageId;
101    }
102
103    /**
104     * {@inheritDoc}
105     */
106    public void head()
107    {
108        init();
109
110        setHeadFlag( true );
111
112        write( "<!DOCTYPE html PUBLIC \"" + XHTML_TRANSITIONAL_PUBLIC_ID + "\" \"" + XHTML_TRANSITIONAL_SYSTEM_ID
113            + "\">" );
114
115        MutableAttributeSet atts = new SinkEventAttributeSet();
116        atts.addAttribute( "xmlns", XHTML_NAMESPACE );
117
118        if ( languageId != null )
119        {
120            atts.addAttribute( Attribute.LANG.toString(), languageId );
121            atts.addAttribute( "xml:lang", languageId );
122        }
123
124        writeStartTag( HTML, atts );
125
126        writeStartTag( HEAD );
127    }
128
129    /**
130     * {@inheritDoc}
131     */
132    public void head_()
133    {
134        if ( !isHeadTitleFlag() )
135        {
136            // The content of element type "head" must match
137            // "((script|style|meta|link|object|isindex)*,
138            //  ((title,(script|style|meta|link|object|isindex)*,
139            //  (base,(script|style|meta|link|object|isindex)*)?)|(base,(script|style|meta|link|object|isindex)*,
140            //  (title,(script|style|meta|link|object|isindex)*))))"
141            writeStartTag( TITLE );
142            writeEndTag( TITLE );
143        }
144
145        setHeadFlag( false );
146        setHeadTitleFlag( false );
147
148        if ( encoding != null )
149        {
150            write( "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=" + encoding + "\"/>" );
151        }
152
153        writeEndTag( HEAD );
154    }
155
156    /**
157     * {@inheritDoc}
158     *
159     * @see javax.swing.text.html.HTML.Tag#TITLE
160     */
161    public void title()
162    {
163        setHeadTitleFlag( true );
164
165        writeStartTag( TITLE );
166    }
167
168    /**
169     * {@inheritDoc}
170     *
171     * @see javax.swing.text.html.HTML.Tag#TITLE
172     */
173    public void title_()
174    {
175        content( getTextBuffer().toString() );
176
177        writeEndTag( TITLE );
178
179        resetTextBuffer();
180
181    }
182
183    /**
184     * {@inheritDoc}
185     *
186     * @see javax.swing.text.html.HTML.Tag#META
187     */
188    public void author_()
189    {
190        if ( getTextBuffer().length() > 0 )
191        {
192            MutableAttributeSet att = new SinkEventAttributeSet();
193            att.addAttribute( Attribute.NAME, "author" );
194            String text = HtmlTools.escapeHTML( getTextBuffer().toString() );
195            // hack: un-escape numerical entities that have been escaped above
196            // note that numerical entities should really be added as one unicode character in the first place
197            text = StringUtils.replace( text, "&amp;#", "&#" );
198            att.addAttribute( Attribute.CONTENT, text );
199
200            writeSimpleTag( META, att );
201
202            resetTextBuffer();
203        }
204    }
205
206    /**
207     * {@inheritDoc}
208     *
209     * @see javax.swing.text.html.HTML.Tag#META
210     */
211    public void date_()
212    {
213        if ( getTextBuffer().length() > 0 )
214        {
215            MutableAttributeSet att = new SinkEventAttributeSet();
216            att.addAttribute( Attribute.NAME, "date" );
217            att.addAttribute( Attribute.CONTENT, getTextBuffer().toString() );
218
219            writeSimpleTag( META, att );
220
221            resetTextBuffer();
222        }
223    }
224
225    /**
226     * {@inheritDoc}
227     *
228     * @see javax.swing.text.html.HTML.Tag#BODY
229     */
230    public void body()
231    {
232        writeStartTag( BODY );
233    }
234
235    /**
236     * {@inheritDoc}
237     *
238     * @see javax.swing.text.html.HTML.Tag#BODY
239     * @see javax.swing.text.html.HTML.Tag#HTML
240     */
241    public void body_()
242    {
243        writeEndTag( BODY );
244
245        writeEndTag( HTML );
246
247        flush();
248
249        init();
250    }
251
252    // ----------------------------------------------------------------------
253    // Public protected methods
254    // ----------------------------------------------------------------------
255
256    /**
257     * <p>Setter for the field <code>headTitleFlag</code>.</p>
258     *
259     * @param headTitleFlag an header title flag.
260     * @since 1.1
261     */
262    protected void setHeadTitleFlag( boolean headTitleFlag )
263    {
264        this.headTitleFlag = headTitleFlag;
265    }
266
267    /**
268     * <p>isHeadTitleFlag.</p>
269     *
270     * @return the current headTitleFlag.
271     * @since 1.1
272     */
273    protected boolean isHeadTitleFlag()
274    {
275        return this.headTitleFlag ;
276    }
277}