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.XhtmlBaseSink;
028import org.apache.maven.doxia.sink.SinkEventAttributeSet;
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 * @version $Id: XhtmlSink.html 905940 2014-04-12 16:27:29Z hboutemy $
042 * @since 1.0
043 */
044public class XhtmlSink
045    extends XhtmlBaseSink
046    implements XhtmlMarkup
047{
048    // ----------------------------------------------------------------------
049    // Instance fields
050    // ----------------------------------------------------------------------
051
052    private String encoding;
053
054    private String languageId;
055
056    /** An indication on if we're inside a head title. */
057    private boolean headTitleFlag;
058
059    // ----------------------------------------------------------------------
060    // Constructors
061    // ----------------------------------------------------------------------
062
063    /**
064     * Constructor, initialize the Writer.
065     *
066     * @param writer not null writer to write the result.
067     */
068    protected XhtmlSink( Writer writer )
069    {
070        super( writer );
071    }
072
073    /**
074     * Constructor, initialize the Writer and tells which encoding is used.
075     *
076     * @param writer not null writer to write the result.
077     * @param encoding the encoding used, that should be written to the generated HTML content
078     * if not <code>null</code>.
079     */
080    protected XhtmlSink( Writer writer, String encoding )
081    {
082        super( writer );
083
084        this.encoding = encoding;
085    }
086
087    /**
088     * Constructor, initialize the Writer and tells which encoding and languageId are used.
089     *
090     * @param writer not null writer to write the result.
091     * @param encoding the encoding used, that should be written to the generated HTML content
092     * if not <code>null</code>.
093     * @param languageId language identifier for the root element as defined by
094     * <a href="ftp://ftp.isi.edu/in-notes/bcp/bcp47.txt">IETF BCP 47</a>, Tags for the Identification of Languages;
095     * in addition, the empty string may be specified.
096     */
097    protected XhtmlSink( Writer writer, String encoding, String languageId )
098    {
099        this( writer, encoding );
100
101        this.languageId = languageId;
102    }
103
104    /** {@inheritDoc} */
105    public void head()
106    {
107        init();
108
109        setHeadFlag( true );
110
111        write( "<!DOCTYPE html PUBLIC \"" + XHTML_TRANSITIONAL_PUBLIC_ID + "\" \"" + XHTML_TRANSITIONAL_SYSTEM_ID
112            + "\">" );
113
114        MutableAttributeSet atts = new SinkEventAttributeSet();
115        atts.addAttribute( "xmlns", XHTML_NAMESPACE );
116
117        if ( languageId != null )
118        {
119            atts.addAttribute( Attribute.LANG.toString(), languageId );
120            atts.addAttribute( "xml:lang", languageId );
121        }
122
123        writeStartTag( HTML, atts );
124
125        writeStartTag( HEAD );
126    }
127
128    /** {@inheritDoc} */
129    public void head_()
130    {
131        if ( !isHeadTitleFlag() )
132        {
133            // The content of element type "head" must match
134            // "((script|style|meta|link|object|isindex)*,
135            //  ((title,(script|style|meta|link|object|isindex)*,
136            //  (base,(script|style|meta|link|object|isindex)*)?)|(base,(script|style|meta|link|object|isindex)*,
137            //  (title,(script|style|meta|link|object|isindex)*))))"
138            writeStartTag( TITLE );
139            writeEndTag( TITLE );
140        }
141
142        setHeadFlag( false );
143        setHeadTitleFlag( false );
144
145        if ( encoding != null )
146        {
147            write( "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=" + encoding + "\"/>" );
148        }
149
150        writeEndTag( HEAD );
151    }
152
153    /**
154     * {@inheritDoc}
155     * @see javax.swing.text.html.HTML.Tag#TITLE
156     */
157    public void title()
158    {
159        setHeadTitleFlag( true );
160
161        writeStartTag( TITLE );
162    }
163
164    /**
165     * {@inheritDoc}
166     * @see javax.swing.text.html.HTML.Tag#TITLE
167     */
168    public void title_()
169    {
170        content( getTextBuffer().toString() );
171
172        writeEndTag( TITLE );
173
174        resetTextBuffer();
175
176    }
177
178    /**
179     * {@inheritDoc}
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     * @see javax.swing.text.html.HTML.Tag#META
203     */
204    public void date_()
205    {
206        if ( getTextBuffer().length() > 0 )
207        {
208            MutableAttributeSet att = new SinkEventAttributeSet();
209            att.addAttribute( Attribute.NAME, "date" );
210            att.addAttribute( Attribute.CONTENT, getTextBuffer().toString() );
211
212            writeSimpleTag( META, att );
213
214            resetTextBuffer();
215        }
216    }
217
218    /**
219     * {@inheritDoc}
220     * @see javax.swing.text.html.HTML.Tag#BODY
221     */
222    public void body()
223    {
224        writeStartTag( BODY );
225    }
226
227    /**
228     * {@inheritDoc}
229     * @see javax.swing.text.html.HTML.Tag#BODY
230     * @see javax.swing.text.html.HTML.Tag#HTML
231     */
232    public void body_()
233    {
234        writeEndTag( BODY );
235
236        writeEndTag( HTML );
237
238        flush();
239
240        init();
241    }
242
243    // ----------------------------------------------------------------------
244    // Public protected methods
245    // ----------------------------------------------------------------------
246
247    /**
248     * <p>Setter for the field <code>headTitleFlag</code>.</p>
249     *
250     * @param headTitleFlag an header title flag.
251     * @since 1.1
252     */
253    protected void setHeadTitleFlag( boolean headTitleFlag )
254    {
255        this.headTitleFlag = headTitleFlag;
256    }
257
258    /**
259     * <p>isHeadTitleFlag.</p>
260     *
261     * @return the current headTitleFlag.
262     * @since 1.1
263     */
264    protected boolean isHeadTitleFlag()
265    {
266        return this.headTitleFlag ;
267    }
268}