001/*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *   http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing,
013 * software distributed under the License is distributed on an
014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 * KIND, either express or implied.  See the License for the
016 * specific language governing permissions and limitations
017 * under the License.
018 */
019package org.apache.maven.doxia.module.xhtml5;
020
021import javax.swing.text.MutableAttributeSet;
022
023import java.io.Writer;
024
025import org.apache.maven.doxia.sink.SinkEventAttributeSet;
026import org.apache.maven.doxia.sink.SinkEventAttributes;
027import org.apache.maven.doxia.sink.impl.Xhtml5BaseSink;
028import org.apache.maven.doxia.util.DoxiaStringUtils;
029import org.apache.maven.doxia.util.HtmlTools;
030
031/**
032 * <a href="https://www.w3.org/TR/html52/">XHTML 5.2</a> sink implementation.
033 */
034public class Xhtml5Sink extends Xhtml5BaseSink implements Xhtml5Markup {
035    // ----------------------------------------------------------------------
036    // Instance fields
037    // ----------------------------------------------------------------------
038
039    private String encoding;
040
041    private String languageId;
042
043    /** An indication on if we're inside a head title. */
044    private boolean headTitleFlag;
045
046    // ----------------------------------------------------------------------
047    // Constructors
048    // ----------------------------------------------------------------------
049
050    /**
051     * Constructor, initialize the Writer.
052     *
053     * @param writer not null writer to write the result.
054     */
055    protected Xhtml5Sink(Writer writer) {
056        super(writer);
057    }
058
059    /**
060     * Constructor, initialize the Writer and tells which encoding is used.
061     *
062     * @param writer not null writer to write the result.
063     * @param encoding the encoding used, that should be written to the generated HTML content
064     * if not <code>null</code>.
065     */
066    protected Xhtml5Sink(Writer writer, String encoding) {
067        super(writer);
068
069        this.encoding = encoding;
070    }
071
072    /**
073     * Constructor, initialize the Writer and tells which encoding and languageId are 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     * @param languageId language identifier for the root element as defined by
079     * <a href="ftp://ftp.isi.edu/in-notes/bcp/bcp47.txt">IETF BCP 47</a>, Tags for the Identification of Languages;
080     * in addition, the empty string may be specified.
081     */
082    protected Xhtml5Sink(Writer writer, String encoding, String languageId) {
083        this(writer, encoding);
084
085        this.languageId = languageId;
086    }
087
088    @Override
089    public void head(SinkEventAttributes attributes) {
090        init();
091
092        setHeadFlag(true);
093
094        write("<!DOCTYPE html>");
095
096        MutableAttributeSet atts = new SinkEventAttributeSet();
097        if (attributes != null) {
098            atts.addAttributes(attributes);
099        }
100        atts.addAttribute("xmlns", XHTML5_NAMESPACE);
101
102        if (languageId != null) {
103            atts.addAttribute(SinkEventAttributes.LANG, languageId);
104            atts.addAttribute("xml:lang", languageId);
105        }
106
107        writeStartTag(HTML, atts);
108
109        writeStartTag(HEAD);
110    }
111
112    /**
113     * {@inheritDoc}
114     */
115    public void head_() {
116        if (!isHeadTitleFlag()) {
117            // The content of element type "head" must match
118            // "((script|style|meta|link|object|isindex)*,
119            //  ((title,(script|style|meta|link|object|isindex)*,
120            //  (base,(script|style|meta|link|object|isindex)*)?)|(base,(script|style|meta|link|object|isindex)*,
121            //  (title,(script|style|meta|link|object|isindex)*))))"
122            writeStartTag(TITLE);
123            writeEndTag(TITLE);
124        }
125
126        setHeadFlag(false);
127        setHeadTitleFlag(false);
128
129        if (encoding != null) {
130            write("<meta charset=\"" + encoding + "\"/>");
131        }
132
133        writeEndTag(HEAD);
134    }
135
136    /**
137     * {@inheritDoc}
138     *
139     * @see javax.swing.text.html.HTML.Tag#TITLE
140     */
141    public void title(SinkEventAttributes attributes) {
142        setHeadTitleFlag(true);
143
144        writeStartTag(TITLE, attributes);
145    }
146
147    /**
148     * {@inheritDoc}
149     *
150     * @see javax.swing.text.html.HTML.Tag#TITLE
151     */
152    public void title_() {
153        content(getTextBuffer().toString());
154
155        writeEndTag(TITLE);
156
157        resetTextBuffer();
158    }
159
160    /**
161     * {@inheritDoc}
162     *
163     * @see javax.swing.text.html.HTML.Tag#META
164     */
165    public void author_() {
166        if (getTextBuffer().length() > 0) {
167            MutableAttributeSet att = new SinkEventAttributeSet();
168            att.addAttribute(SinkEventAttributes.NAME, "author");
169            String text = HtmlTools.escapeHTML(getTextBuffer().toString());
170            // hack: un-escape numerical entities that have been escaped above
171            // note that numerical entities should really be added as one unicode character in the first place
172            text = DoxiaStringUtils.replace(text, "&amp;#", "&#");
173            att.addAttribute(SinkEventAttributes.CONTENT, text);
174
175            writeSimpleTag(META, att);
176
177            resetTextBuffer();
178        }
179    }
180
181    /**
182     * {@inheritDoc}
183     *
184     * @see javax.swing.text.html.HTML.Tag#META
185     */
186    public void date_() {
187        if (getTextBuffer().length() > 0) {
188            MutableAttributeSet att = new SinkEventAttributeSet();
189            att.addAttribute(SinkEventAttributes.NAME, "date");
190            att.addAttribute(SinkEventAttributes.CONTENT, getTextBuffer().toString());
191
192            writeSimpleTag(META, att);
193
194            resetTextBuffer();
195        }
196    }
197
198    /**
199     * {@inheritDoc}
200     *
201     * @see javax.swing.text.html.HTML.Tag#BODY
202     */
203    @Override
204    public void body(SinkEventAttributes attributes) {
205        writeStartTag(BODY, attributes);
206    }
207
208    /**
209     * {@inheritDoc}
210     *
211     * @see javax.swing.text.html.HTML.Tag#BODY
212     * @see javax.swing.text.html.HTML.Tag#HTML
213     */
214    public void body_() {
215        writeEndTag(BODY);
216
217        writeEndTag(HTML);
218
219        flush();
220
221        init();
222    }
223
224    // ----------------------------------------------------------------------
225    // Public protected methods
226    // ----------------------------------------------------------------------
227
228    /**
229     * <p>Setter for the field <code>headTitleFlag</code>.</p>
230     *
231     * @param headTitleFlag an header title flag.
232     * @since 1.1
233     */
234    protected void setHeadTitleFlag(boolean headTitleFlag) {
235        this.headTitleFlag = headTitleFlag;
236    }
237
238    /**
239     * <p>isHeadTitleFlag.</p>
240     *
241     * @return the current headTitleFlag.
242     * @since 1.1
243     */
244    protected boolean isHeadTitleFlag() {
245        return this.headTitleFlag;
246    }
247}