001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.juneau.marshall.html;
018
019import static org.apache.juneau.commons.utils.StringUtils.*;
020
021import java.io.*;
022
023import org.apache.juneau.marshall.*;
024import org.apache.juneau.marshall.xml.*;
025
026/**
027 * Specialized writer for serializing HTML.
028 *
029 * <h5 class='section'>See Also:</h5><ul>
030 *    <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/HtmlSupport">HTML</a>
031
032 * </ul>
033 */
034@SuppressWarnings({
035   "java:S119", // 'SELF' (CRTP self-type) is intentional and clearer than a single-letter name.
036   "resource" // Writer resource managed by calling code
037})
038public abstract class HtmlWriter<SELF extends HtmlWriter<SELF>> extends XmlWriter<SELF> {
039
040   /**
041    * Copy constructor.
042    *
043    * @param w Writer being copied.
044    */
045   protected HtmlWriter(HtmlWriter<?> w) {
046      super(w);
047   }
048
049   /**
050    * Constructor.
051    *
052    * @param out The writer being wrapped.
053    * @param useWhitespace If <jk>true</jk>, tabs will be used in output.
054    * @param maxIndent The maximum indentation level.
055    * @param trimStrings If <jk>true</jk>, strings should be trimmed before they're serialized.
056    * @param quoteChar The quote character to use (i.e. <js>'\''</js> or <js>'"'</js>)
057    * @param uriResolver The URI resolver for resolving URIs to absolute or root-relative form.
058    */
059   protected HtmlWriter(Writer out, boolean useWhitespace, int maxIndent, boolean trimStrings, char quoteChar, UriResolver uriResolver) {
060      super(out, useWhitespace, maxIndent, trimStrings, quoteChar, uriResolver, false, null);
061   }
062
063   @Override /* Overridden from SerializerWriter */
064   public SELF cr(int depth) {
065      if (depth > 0)
066         super.cr(depth);
067      return self();
068   }
069
070   @Override /* Overridden from SerializerWriter */
071   public SELF cre(int depth) {
072      if (depth > 0)
073         super.cre(depth);
074      return self();
075   }
076
077   @Override /* Overridden from XmlSerializerWriter */
078   @SuppressWarnings({
079      "java:S3776" // Cognitive complexity acceptable for text serialization with whitespace handling
080   })
081   public SELF text(Object o, boolean preserveWhitespace) {
082
083      if (o == null) {
084         append("<null/>");
085         return self();
086      }
087      var s = o.toString();
088      if (s.isEmpty()) {
089         append("<sp/>");
090         return self();
091      }
092
093      for (var i = 0; i < s.length(); i++) {
094         var test = s.charAt(i);
095         if (test == '&')
096            append("&amp;");
097         else if (test == '<')
098            append("&lt;");
099         else if (test == '>')
100            append("&gt;");
101         else if (test == '\n')
102            append(preserveWhitespace ? "\n" : "<br/>");
103         else if (test == '\f')  // XML 1.0 doesn't support form feeds or backslashes, so we have to invent something.
104            append(preserveWhitespace ? "\f" : "<ff/>");
105         else if (test == '\b')
106            append(preserveWhitespace ? "\b" : "<bs/>");
107         else if (test == '\t')
108            append(preserveWhitespace ? "\t" : "<sp>&#x2003;</sp>");
109         else if ((i == 0 || i == s.length() - 1) && Character.isWhitespace(test)) {
110            if (preserveWhitespace)
111               w(test);
112            else if (test == ' ')
113               append("<sp> </sp>");
114            else
115               append("<sp>&#x").append(toHex4(test)).append(";</sp>");
116         } else if (Character.isISOControl(test))
117            append("&#" + (int)test + ";");
118         else
119            w(test);
120      }
121
122      return self();
123   }
124}