1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License. You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing,
13 * software distributed under the License is distributed on an
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 * KIND, either express or implied. See the License for the
16 * specific language governing permissions and limitations
17 * under the License.
18 */
19 package org.apache.maven.doxia.module.xhtml5;
20
21 import javax.swing.text.MutableAttributeSet;
22 import javax.swing.text.html.HTML.Attribute;
23
24 import java.io.Writer;
25
26 import org.apache.commons.lang3.StringUtils;
27 import org.apache.maven.doxia.sink.SinkEventAttributes;
28 import org.apache.maven.doxia.sink.impl.SinkEventAttributeSet;
29 import org.apache.maven.doxia.sink.impl.Xhtml5BaseSink;
30 import org.apache.maven.doxia.util.HtmlTools;
31
32 /**
33 * <a href="https://www.w3.org/TR/html52/">XHTML 5.2</a> sink implementation.
34 */
35 public class Xhtml5Sink extends Xhtml5BaseSink implements Xhtml5Markup {
36 // ----------------------------------------------------------------------
37 // Instance fields
38 // ----------------------------------------------------------------------
39
40 private String encoding;
41
42 private String languageId;
43
44 /** An indication on if we're inside a head title. */
45 private boolean headTitleFlag;
46
47 // ----------------------------------------------------------------------
48 // Constructors
49 // ----------------------------------------------------------------------
50
51 /**
52 * Constructor, initialize the Writer.
53 *
54 * @param writer not null writer to write the result.
55 */
56 protected Xhtml5Sink(Writer writer) {
57 super(writer);
58 }
59
60 /**
61 * Constructor, initialize the Writer and tells which encoding is used.
62 *
63 * @param writer not null writer to write the result.
64 * @param encoding the encoding used, that should be written to the generated HTML content
65 * if not <code>null</code>.
66 */
67 protected Xhtml5Sink(Writer writer, String encoding) {
68 super(writer);
69
70 this.encoding = encoding;
71 }
72
73 /**
74 * Constructor, initialize the Writer and tells which encoding and languageId are used.
75 *
76 * @param writer not null writer to write the result.
77 * @param encoding the encoding used, that should be written to the generated HTML content
78 * if not <code>null</code>.
79 * @param languageId language identifier for the root element as defined by
80 * <a href="ftp://ftp.isi.edu/in-notes/bcp/bcp47.txt">IETF BCP 47</a>, Tags for the Identification of Languages;
81 * in addition, the empty string may be specified.
82 */
83 protected Xhtml5Sink(Writer writer, String encoding, String languageId) {
84 this(writer, encoding);
85
86 this.languageId = languageId;
87 }
88
89 @Override
90 public void head(SinkEventAttributes attributes) {
91 init();
92
93 setHeadFlag(true);
94
95 write("<!DOCTYPE html>");
96
97 MutableAttributeSet atts = new SinkEventAttributeSet();
98 if (attributes != null) {
99 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, "&#", "&#");
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 }