View Javadoc
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, "&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 }