001/* 002 * Copyright (c) 2003, 2022, Oracle and/or its affiliates. All rights reserved. 003 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 004 * 005 * This code is free software; you can redistribute it and/or modify it 006 * under the terms of the GNU General Public License version 2 only, as 007 * published by the Free Software Foundation. Oracle designates this 008 * particular file as subject to the "Classpath" exception as provided 009 * by Oracle in the LICENSE file that accompanied this code. 010 * 011 * This code is distributed in the hope that it will be useful, but WITHOUT 012 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 013 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 014 * version 2 for more details (a copy is included in the LICENSE file that 015 * accompanied this code). 016 * 017 * You should have received a copy of the GNU General Public License version 018 * 2 along with this work; if not, write to the Free Software Foundation, 019 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 020 * 021 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 022 * or visit www.oracle.com if you need additional information or have any 023 * questions. 024 */ 025 026package org.jdrupes.mdoclet.internal.doclets.formats.html.markup; 027 028import java.io.IOException; 029import java.io.Writer; 030 031import org.jdrupes.mdoclet.internal.doclets.toolkit.Content; 032import org.jdrupes.mdoclet.internal.doclets.toolkit.util.DocletConstants; 033 034/** 035 * A builder for HTML script elements. 036 */ 037public class Script { 038 private final StringBuilder sb; 039 040 /** 041 * Creates an empty script. 042 */ 043 public Script() { 044 sb = new StringBuilder(); 045 } 046 047 /** 048 * Creates a new script containing the specified code. 049 * 050 * @param code the code 051 */ 052 public Script(String code) { 053 this(); 054 append(code); 055 } 056 057 /** 058 * Appends the given code to the script. 059 * 060 * @param code the code 061 * @return this object 062 */ 063 public Script append(CharSequence code) { 064 sb.append(code); 065 return this; 066 } 067 068 /** 069 * Appends the given text as a string constant to the string. 070 * Characters within the string will be escaped as needed. 071 * 072 * @param text the text 073 * @return this object 074 */ 075 public Script appendStringLiteral(CharSequence text) { 076 sb.append(stringLiteral(text, '"')); 077 return this; 078 } 079 080 /** 081 * Appends the given text as a string constant to the string. 082 * Characters within the string will be escaped as needed. 083 * 084 * @param text the text 085 * @param quoteChar the quote character to use 086 * @return this object 087 */ 088 // The ability to specify the quote character is for backwards 089 // compatibility. Ideally, we should simplify the code so that 090 // the same quote character is always used. 091 public Script appendStringLiteral(CharSequence text, char quoteChar) { 092 sb.append(stringLiteral(text, quoteChar)); 093 return this; 094 } 095 096 /** 097 * Returns a "live" view of the script as a {@code Content} object. 098 * Any later modifications to the script will be reflected in the 099 * object that is returned. 100 * @return the script 101 */ 102 public Content asContent() { 103 ScriptContent scriptContent = new ScriptContent(sb); 104 var script = new HtmlTree(TagName.SCRIPT) { 105 @Override 106 public HtmlTree add(Content c) { 107 if (c != scriptContent) { 108 throw new IllegalArgumentException(); 109 } 110 return super.add(scriptContent); 111 } 112 }; 113 script.put(HtmlAttr.TYPE, "text/javascript"); 114 script.add(scriptContent); 115 return script; 116 } 117 118 /** 119 * Returns a JavaScript string literal containing a specified string, 120 * escaping the characters of that string as needed. 121 * 122 * @param s the string 123 * @return a string literal containing the string 124 */ 125 public static String stringLiteral(CharSequence s) { 126 return stringLiteral(s, '"'); 127 } 128 129 /** 130 * Returns a JavaScript string literal containing a specified string, 131 * escaping the characters of that string as needed. 132 * 133 * @param s the string 134 * @param quoteChar the quote character to use for the literal 135 * @return a string literal containing the string 136 */ 137 // The ability to specify the quote character is for backwards 138 // compatibility. Ideally, we should simplify the code so that 139 // the same quote character is always used. 140 public static String stringLiteral(CharSequence s, char quoteChar) { 141 if (quoteChar != '"' && quoteChar != '\'') { 142 throw new IllegalArgumentException(); 143 } 144 StringBuilder sb = new StringBuilder(); 145 sb.append(quoteChar); 146 for (int i = 0; i < s.length(); i++) { 147 char ch = s.charAt(i); 148 switch (ch) { 149 case '\b': 150 sb.append("\\b"); 151 break; 152 case '\t': 153 sb.append("\\t"); 154 break; 155 case '\n': 156 sb.append("\\n"); 157 break; 158 case '\f': 159 sb.append("\\f"); 160 break; 161 case '\r': 162 sb.append("\\r"); 163 break; 164 case '"': 165 sb.append("\\\""); 166 break; 167 case '\'': 168 sb.append("\\\'"); 169 break; 170 case '\\': 171 sb.append("\\\\"); 172 break; 173 default: 174 if (ch < 32 || ch >= 127) { 175 sb.append(String.format("\\u%04X", (int) ch)); 176 } else { 177 sb.append(ch); 178 } 179 break; 180 } 181 } 182 sb.append(quoteChar); 183 return sb.toString(); 184 } 185 186 private static class ScriptContent extends Content { 187 private final StringBuilder sb; 188 189 ScriptContent(StringBuilder sb) { 190 this.sb = sb; 191 } 192 193 @Override 194 public ScriptContent add(CharSequence code) { 195 sb.append(code); 196 return this; 197 } 198 199 @Override 200 public boolean write(Writer writer, String newline, boolean atNewline) 201 throws IOException { 202 String s = sb.toString(); 203 writer.write(s.replace("\n", newline)); 204 return s.endsWith("\n"); 205 } 206 207 @Override 208 public boolean isEmpty() { 209 return false; 210 } 211 } 212}