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, "&#", "&#"); 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}