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.util; 020 021import java.util.HashMap; 022import java.util.Map; 023 024/** 025 * Utility class for escaping and unescaping HTML/XML entities. 026 * This class provides methods that were previously supplied by Apache Commons Text. 027 * 028 * @since 2.1.0 029 */ 030public class HtmlEntityUtils { 031 032 private static final Map<String, String> HTML4_ENTITIES = new HashMap<>(); 033 private static final Map<String, String> XML_ENTITIES = new HashMap<>(); 034 035 static { 036 // XML entities (subset also used in HTML) 037 XML_ENTITIES.put("quot", "\""); 038 XML_ENTITIES.put("amp", "&"); 039 XML_ENTITIES.put("lt", "<"); 040 XML_ENTITIES.put("gt", ">"); 041 XML_ENTITIES.put("apos", "'"); 042 043 // Initialize HTML4 entities with common XML entities (but NOT apos) 044 HTML4_ENTITIES.put("quot", "\""); 045 HTML4_ENTITIES.put("amp", "&"); 046 HTML4_ENTITIES.put("lt", "<"); 047 HTML4_ENTITIES.put("gt", ">"); 048 049 // Add common HTML4 entities 050 HTML4_ENTITIES.put("nbsp", "\u00A0"); 051 HTML4_ENTITIES.put("iexcl", "\u00A1"); 052 HTML4_ENTITIES.put("cent", "\u00A2"); 053 HTML4_ENTITIES.put("pound", "\u00A3"); 054 HTML4_ENTITIES.put("curren", "\u00A4"); 055 HTML4_ENTITIES.put("yen", "\u00A5"); 056 HTML4_ENTITIES.put("brvbar", "\u00A6"); 057 HTML4_ENTITIES.put("sect", "\u00A7"); 058 HTML4_ENTITIES.put("uml", "\u00A8"); 059 HTML4_ENTITIES.put("copy", "\u00A9"); 060 HTML4_ENTITIES.put("ordf", "\u00AA"); 061 HTML4_ENTITIES.put("laquo", "\u00AB"); 062 HTML4_ENTITIES.put("not", "\u00AC"); 063 HTML4_ENTITIES.put("shy", "\u00AD"); 064 HTML4_ENTITIES.put("reg", "\u00AE"); 065 HTML4_ENTITIES.put("macr", "\u00AF"); 066 HTML4_ENTITIES.put("deg", "\u00B0"); 067 HTML4_ENTITIES.put("plusmn", "\u00B1"); 068 HTML4_ENTITIES.put("sup2", "\u00B2"); 069 HTML4_ENTITIES.put("sup3", "\u00B3"); 070 HTML4_ENTITIES.put("acute", "\u00B4"); 071 HTML4_ENTITIES.put("micro", "\u00B5"); 072 HTML4_ENTITIES.put("para", "\u00B6"); 073 HTML4_ENTITIES.put("middot", "\u00B7"); 074 HTML4_ENTITIES.put("cedil", "\u00B8"); 075 HTML4_ENTITIES.put("sup1", "\u00B9"); 076 HTML4_ENTITIES.put("ordm", "\u00BA"); 077 HTML4_ENTITIES.put("raquo", "\u00BB"); 078 HTML4_ENTITIES.put("frac14", "\u00BC"); 079 HTML4_ENTITIES.put("frac12", "\u00BD"); 080 HTML4_ENTITIES.put("frac34", "\u00BE"); 081 HTML4_ENTITIES.put("iquest", "\u00BF"); 082 HTML4_ENTITIES.put("Agrave", "\u00C0"); 083 HTML4_ENTITIES.put("Aacute", "\u00C1"); 084 HTML4_ENTITIES.put("Acirc", "\u00C2"); 085 HTML4_ENTITIES.put("Atilde", "\u00C3"); 086 HTML4_ENTITIES.put("Auml", "\u00C4"); 087 HTML4_ENTITIES.put("Aring", "\u00C5"); 088 HTML4_ENTITIES.put("AElig", "\u00C6"); 089 HTML4_ENTITIES.put("Ccedil", "\u00C7"); 090 HTML4_ENTITIES.put("Egrave", "\u00C8"); 091 HTML4_ENTITIES.put("Eacute", "\u00C9"); 092 HTML4_ENTITIES.put("Ecirc", "\u00CA"); 093 HTML4_ENTITIES.put("Euml", "\u00CB"); 094 HTML4_ENTITIES.put("Igrave", "\u00CC"); 095 HTML4_ENTITIES.put("Iacute", "\u00CD"); 096 HTML4_ENTITIES.put("Icirc", "\u00CE"); 097 HTML4_ENTITIES.put("Iuml", "\u00CF"); 098 HTML4_ENTITIES.put("ETH", "\u00D0"); 099 HTML4_ENTITIES.put("Ntilde", "\u00D1"); 100 HTML4_ENTITIES.put("Ograve", "\u00D2"); 101 HTML4_ENTITIES.put("Oacute", "\u00D3"); 102 HTML4_ENTITIES.put("Ocirc", "\u00D4"); 103 HTML4_ENTITIES.put("Otilde", "\u00D5"); 104 HTML4_ENTITIES.put("Ouml", "\u00D6"); 105 HTML4_ENTITIES.put("times", "\u00D7"); 106 HTML4_ENTITIES.put("Oslash", "\u00D8"); 107 HTML4_ENTITIES.put("Ugrave", "\u00D9"); 108 HTML4_ENTITIES.put("Uacute", "\u00DA"); 109 HTML4_ENTITIES.put("Ucirc", "\u00DB"); 110 HTML4_ENTITIES.put("Uuml", "\u00DC"); 111 HTML4_ENTITIES.put("Yacute", "\u00DD"); 112 HTML4_ENTITIES.put("THORN", "\u00DE"); 113 HTML4_ENTITIES.put("szlig", "\u00DF"); 114 HTML4_ENTITIES.put("agrave", "\u00E0"); 115 HTML4_ENTITIES.put("aacute", "\u00E1"); 116 HTML4_ENTITIES.put("acirc", "\u00E2"); 117 HTML4_ENTITIES.put("atilde", "\u00E3"); 118 HTML4_ENTITIES.put("auml", "\u00E4"); 119 HTML4_ENTITIES.put("aring", "\u00E5"); 120 HTML4_ENTITIES.put("aelig", "\u00E6"); 121 HTML4_ENTITIES.put("ccedil", "\u00E7"); 122 HTML4_ENTITIES.put("egrave", "\u00E8"); 123 HTML4_ENTITIES.put("eacute", "\u00E9"); 124 HTML4_ENTITIES.put("ecirc", "\u00EA"); 125 HTML4_ENTITIES.put("euml", "\u00EB"); 126 HTML4_ENTITIES.put("igrave", "\u00EC"); 127 HTML4_ENTITIES.put("iacute", "\u00ED"); 128 HTML4_ENTITIES.put("icirc", "\u00EE"); 129 HTML4_ENTITIES.put("iuml", "\u00EF"); 130 HTML4_ENTITIES.put("eth", "\u00F0"); 131 HTML4_ENTITIES.put("ntilde", "\u00F1"); 132 HTML4_ENTITIES.put("ograve", "\u00F2"); 133 HTML4_ENTITIES.put("oacute", "\u00F3"); 134 HTML4_ENTITIES.put("ocirc", "\u00F4"); 135 HTML4_ENTITIES.put("otilde", "\u00F5"); 136 HTML4_ENTITIES.put("ouml", "\u00F6"); 137 HTML4_ENTITIES.put("divide", "\u00F7"); 138 HTML4_ENTITIES.put("oslash", "\u00F8"); 139 HTML4_ENTITIES.put("ugrave", "\u00F9"); 140 HTML4_ENTITIES.put("uacute", "\u00FA"); 141 HTML4_ENTITIES.put("ucirc", "\u00FB"); 142 HTML4_ENTITIES.put("uuml", "\u00FC"); 143 HTML4_ENTITIES.put("yacute", "\u00FD"); 144 HTML4_ENTITIES.put("thorn", "\u00FE"); 145 HTML4_ENTITIES.put("yuml", "\u00FF"); 146 // Additional common entities 147 HTML4_ENTITIES.put("OElig", "\u0152"); 148 HTML4_ENTITIES.put("oelig", "\u0153"); 149 HTML4_ENTITIES.put("Scaron", "\u0160"); 150 HTML4_ENTITIES.put("scaron", "\u0161"); 151 HTML4_ENTITIES.put("Yuml", "\u0178"); 152 HTML4_ENTITIES.put("fnof", "\u0192"); 153 HTML4_ENTITIES.put("circ", "\u02C6"); 154 HTML4_ENTITIES.put("tilde", "\u02DC"); 155 HTML4_ENTITIES.put("Alpha", "\u0391"); 156 HTML4_ENTITIES.put("Beta", "\u0392"); 157 HTML4_ENTITIES.put("Gamma", "\u0393"); 158 HTML4_ENTITIES.put("Delta", "\u0394"); 159 HTML4_ENTITIES.put("Epsilon", "\u0395"); 160 HTML4_ENTITIES.put("Zeta", "\u0396"); 161 HTML4_ENTITIES.put("Eta", "\u0397"); 162 HTML4_ENTITIES.put("Theta", "\u0398"); 163 HTML4_ENTITIES.put("Iota", "\u0399"); 164 HTML4_ENTITIES.put("Kappa", "\u039A"); 165 HTML4_ENTITIES.put("Lambda", "\u039B"); 166 HTML4_ENTITIES.put("Mu", "\u039C"); 167 HTML4_ENTITIES.put("Nu", "\u039D"); 168 HTML4_ENTITIES.put("Xi", "\u039E"); 169 HTML4_ENTITIES.put("Omicron", "\u039F"); 170 HTML4_ENTITIES.put("Pi", "\u03A0"); 171 HTML4_ENTITIES.put("Rho", "\u03A1"); 172 HTML4_ENTITIES.put("Sigma", "\u03A3"); 173 HTML4_ENTITIES.put("Tau", "\u03A4"); 174 HTML4_ENTITIES.put("Upsilon", "\u03A5"); 175 HTML4_ENTITIES.put("Phi", "\u03A6"); 176 HTML4_ENTITIES.put("Chi", "\u03A7"); 177 HTML4_ENTITIES.put("Psi", "\u03A8"); 178 HTML4_ENTITIES.put("Omega", "\u03A9"); 179 HTML4_ENTITIES.put("alpha", "\u03B1"); 180 HTML4_ENTITIES.put("beta", "\u03B2"); 181 HTML4_ENTITIES.put("gamma", "\u03B3"); 182 HTML4_ENTITIES.put("delta", "\u03B4"); 183 HTML4_ENTITIES.put("epsilon", "\u03B5"); 184 HTML4_ENTITIES.put("zeta", "\u03B6"); 185 HTML4_ENTITIES.put("eta", "\u03B7"); 186 HTML4_ENTITIES.put("theta", "\u03B8"); 187 HTML4_ENTITIES.put("iota", "\u03B9"); 188 HTML4_ENTITIES.put("kappa", "\u03BA"); 189 HTML4_ENTITIES.put("lambda", "\u03BB"); 190 HTML4_ENTITIES.put("mu", "\u03BC"); 191 HTML4_ENTITIES.put("nu", "\u03BD"); 192 HTML4_ENTITIES.put("xi", "\u03BE"); 193 HTML4_ENTITIES.put("omicron", "\u03BF"); 194 HTML4_ENTITIES.put("pi", "\u03C0"); 195 HTML4_ENTITIES.put("rho", "\u03C1"); 196 HTML4_ENTITIES.put("sigmaf", "\u03C2"); 197 HTML4_ENTITIES.put("sigma", "\u03C3"); 198 HTML4_ENTITIES.put("tau", "\u03C4"); 199 HTML4_ENTITIES.put("upsilon", "\u03C5"); 200 HTML4_ENTITIES.put("phi", "\u03C6"); 201 HTML4_ENTITIES.put("chi", "\u03C7"); 202 HTML4_ENTITIES.put("psi", "\u03C8"); 203 HTML4_ENTITIES.put("omega", "\u03C9"); 204 HTML4_ENTITIES.put("thetasym", "\u03D1"); 205 HTML4_ENTITIES.put("upsih", "\u03D2"); 206 HTML4_ENTITIES.put("piv", "\u03D6"); 207 HTML4_ENTITIES.put("ensp", "\u2002"); 208 HTML4_ENTITIES.put("emsp", "\u2003"); 209 HTML4_ENTITIES.put("thinsp", "\u2009"); 210 HTML4_ENTITIES.put("zwnj", "\u200C"); 211 HTML4_ENTITIES.put("zwj", "\u200D"); 212 HTML4_ENTITIES.put("lrm", "\u200E"); 213 HTML4_ENTITIES.put("rlm", "\u200F"); 214 HTML4_ENTITIES.put("ndash", "\u2013"); 215 HTML4_ENTITIES.put("mdash", "\u2014"); 216 HTML4_ENTITIES.put("lsquo", "\u2018"); 217 HTML4_ENTITIES.put("rsquo", "\u2019"); 218 HTML4_ENTITIES.put("sbquo", "\u201A"); 219 HTML4_ENTITIES.put("ldquo", "\u201C"); 220 HTML4_ENTITIES.put("rdquo", "\u201D"); 221 HTML4_ENTITIES.put("bdquo", "\u201E"); 222 HTML4_ENTITIES.put("dagger", "\u2020"); 223 HTML4_ENTITIES.put("Dagger", "\u2021"); 224 HTML4_ENTITIES.put("bull", "\u2022"); 225 HTML4_ENTITIES.put("hellip", "\u2026"); 226 HTML4_ENTITIES.put("permil", "\u2030"); 227 HTML4_ENTITIES.put("prime", "\u2032"); 228 HTML4_ENTITIES.put("Prime", "\u2033"); 229 HTML4_ENTITIES.put("lsaquo", "\u2039"); 230 HTML4_ENTITIES.put("rsaquo", "\u203A"); 231 HTML4_ENTITIES.put("oline", "\u203E"); 232 HTML4_ENTITIES.put("frasl", "\u2044"); 233 HTML4_ENTITIES.put("euro", "\u20AC"); 234 HTML4_ENTITIES.put("image", "\u2111"); 235 HTML4_ENTITIES.put("weierp", "\u2118"); 236 HTML4_ENTITIES.put("real", "\u211C"); 237 HTML4_ENTITIES.put("trade", "\u2122"); 238 HTML4_ENTITIES.put("alefsym", "\u2135"); 239 HTML4_ENTITIES.put("larr", "\u2190"); 240 HTML4_ENTITIES.put("uarr", "\u2191"); 241 HTML4_ENTITIES.put("rarr", "\u2192"); 242 HTML4_ENTITIES.put("darr", "\u2193"); 243 HTML4_ENTITIES.put("harr", "\u2194"); 244 HTML4_ENTITIES.put("crarr", "\u21B5"); 245 HTML4_ENTITIES.put("lArr", "\u21D0"); 246 HTML4_ENTITIES.put("uArr", "\u21D1"); 247 HTML4_ENTITIES.put("rArr", "\u21D2"); 248 HTML4_ENTITIES.put("dArr", "\u21D3"); 249 HTML4_ENTITIES.put("hArr", "\u21D4"); 250 HTML4_ENTITIES.put("forall", "\u2200"); 251 HTML4_ENTITIES.put("part", "\u2202"); 252 HTML4_ENTITIES.put("exist", "\u2203"); 253 HTML4_ENTITIES.put("empty", "\u2205"); 254 HTML4_ENTITIES.put("nabla", "\u2207"); 255 HTML4_ENTITIES.put("isin", "\u2208"); 256 HTML4_ENTITIES.put("notin", "\u2209"); 257 HTML4_ENTITIES.put("ni", "\u220B"); 258 HTML4_ENTITIES.put("prod", "\u220F"); 259 HTML4_ENTITIES.put("sum", "\u2211"); 260 HTML4_ENTITIES.put("minus", "\u2212"); 261 HTML4_ENTITIES.put("lowast", "\u2217"); 262 HTML4_ENTITIES.put("radic", "\u221A"); 263 HTML4_ENTITIES.put("prop", "\u221D"); 264 HTML4_ENTITIES.put("infin", "\u221E"); 265 HTML4_ENTITIES.put("ang", "\u2220"); 266 HTML4_ENTITIES.put("and", "\u2227"); 267 HTML4_ENTITIES.put("or", "\u2228"); 268 HTML4_ENTITIES.put("cap", "\u2229"); 269 HTML4_ENTITIES.put("cup", "\u222A"); 270 HTML4_ENTITIES.put("int", "\u222B"); 271 HTML4_ENTITIES.put("there4", "\u2234"); 272 HTML4_ENTITIES.put("sim", "\u223C"); 273 HTML4_ENTITIES.put("cong", "\u2245"); 274 HTML4_ENTITIES.put("asymp", "\u2248"); 275 HTML4_ENTITIES.put("ne", "\u2260"); 276 HTML4_ENTITIES.put("equiv", "\u2261"); 277 HTML4_ENTITIES.put("le", "\u2264"); 278 HTML4_ENTITIES.put("ge", "\u2265"); 279 HTML4_ENTITIES.put("sub", "\u2282"); 280 HTML4_ENTITIES.put("sup", "\u2283"); 281 HTML4_ENTITIES.put("nsub", "\u2284"); 282 HTML4_ENTITIES.put("sube", "\u2286"); 283 HTML4_ENTITIES.put("supe", "\u2287"); 284 HTML4_ENTITIES.put("oplus", "\u2295"); 285 HTML4_ENTITIES.put("otimes", "\u2297"); 286 HTML4_ENTITIES.put("perp", "\u22A5"); 287 HTML4_ENTITIES.put("sdot", "\u22C5"); 288 HTML4_ENTITIES.put("lceil", "\u2308"); 289 HTML4_ENTITIES.put("rceil", "\u2309"); 290 HTML4_ENTITIES.put("lfloor", "\u230A"); 291 HTML4_ENTITIES.put("rfloor", "\u230B"); 292 HTML4_ENTITIES.put("lang", "\u2329"); 293 HTML4_ENTITIES.put("rang", "\u232A"); 294 HTML4_ENTITIES.put("loz", "\u25CA"); 295 HTML4_ENTITIES.put("spades", "\u2660"); 296 HTML4_ENTITIES.put("clubs", "\u2663"); 297 HTML4_ENTITIES.put("hearts", "\u2665"); 298 HTML4_ENTITIES.put("diams", "\u2666"); 299 } 300 301 private HtmlEntityUtils() { 302 // utility class 303 } 304 305 /** 306 * Unescapes XML entities in a string. 307 * 308 * @param text the text to unescape 309 * @return the unescaped text 310 */ 311 public static String unescapeXml(String text) { 312 return unescapeEntities(text, XML_ENTITIES); 313 } 314 315 /** 316 * Unescapes HTML4 entities in a string. 317 * 318 * @param text the text to unescape 319 * @return the unescaped text 320 */ 321 public static String unescapeHtml4(String text) { 322 return unescapeEntities(text, HTML4_ENTITIES); 323 } 324 325 private static String unescapeEntities(String text, Map<String, String> entities) { 326 if (text == null || text.isEmpty()) { 327 return text; 328 } 329 330 StringBuilder result = new StringBuilder(text.length()); 331 int i = 0; 332 int len = text.length(); 333 334 while (i < len) { 335 char c = text.charAt(i); 336 if (c == '&') { 337 int end = text.indexOf(';', i + 1); 338 if (end != -1) { 339 String entityContent = text.substring(i + 1, end); 340 String replacement = null; 341 342 // Check for numeric entity ({ or «) 343 if (entityContent.length() > 0 && entityContent.charAt(0) == '#') { 344 try { 345 if (entityContent.length() > 1 && entityContent.charAt(1) == 'x') { 346 // Hexadecimal - need at least "x" plus one hex digit 347 if (entityContent.length() > 2) { 348 int codePoint = Integer.parseInt(entityContent.substring(2), 16); 349 replacement = new String(Character.toChars(codePoint)); 350 } 351 } else if (entityContent.length() > 1) { 352 // Decimal - need at least one decimal digit after '#' 353 int codePoint = Integer.parseInt(entityContent.substring(1)); 354 replacement = new String(Character.toChars(codePoint)); 355 } 356 } catch (NumberFormatException e) { 357 // Invalid numeric entity, leave as is 358 } catch (IllegalArgumentException e) { 359 // Invalid code point, leave as is 360 } 361 } else { 362 // Named entity 363 replacement = entities.get(entityContent); 364 } 365 366 if (replacement != null) { 367 result.append(replacement); 368 i = end + 1; 369 continue; 370 } 371 } 372 } 373 result.append(c); 374 i++; 375 } 376 377 return result.toString(); 378 } 379}