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