import ceylon.collection { ArrayList, HashMap } import ceylon.language.meta.declaration { Module } import ceylon.test { TestResult, TestRunResult, TestDescription, TestListener, TestState } import ceylon.test.engine.internal { FileWriter } import ceylon.test.event { TestRunFinishedEvent } import java.text { NumberFormat } "A [[TestListener]] that generate simple HTML report about test execution." shared class HtmlReporter(String reportSubdir, String? reportsDir) satisfies TestListener { shared actual void testRunFinished(TestRunFinishedEvent event) { generate(event.runner.description, event.result); } void generate(TestDescription root, TestRunResult result) { value testedModules = findTestedModules(result); value parentPath = reportsDir else "reports/``reportSubdir``"; String path; if( testedModules.size == 1 ) { assert(exists testedModule = testedModules[0]); path = "``parentPath``/results-``testedModule.name``-``testedModule.version``.html"; } else { path = "``parentPath``/results.html"; } try (fw = FileWriter(path)) { fw.write("<!DOCTYPE html>"); fw.write("<html>"); generateHead(fw); fw.write("<body>"); generateBanner(fw, result); generateSummary(fw, result); generateResultsTable(fw, root, result); fw.write("</body>"); generateScript(fw); fw.write("</html>"); } } void generateHead(FileWriter fw) { fw.write("<head> <meta charset='UTF-8'> <title>Results</title> "); generateCss(fw); fw.write("</head>"); } void generateCss(FileWriter fw) { value resultsCss = `module`.resourceByPath("results.css"); assert(exists resultsCss); fw.write("<style type='text/css'>"); fw.write(resultsCss.textContent()); fw.write("</style>"); } void generateBanner(FileWriter fw, TestRunResult result) { if( result.isSuccess ) { fw.write("<div class='banner success'>Success</div>"); } else { fw.write("<div class='banner failed'>Failed</div>"); } } void generateSummary(FileWriter fw, TestRunResult result) { fw.write(" <table class='summary'> <tbody> <tr> <td class='value total'>``result.runCount``</td> <td class='value succeeded'>``result.successCount``</td> <td class='value failures'>``result.failureCount``</td> <td class='value errors'>``result.errorCount``</td> <td class='value skipped'>``result.skippedCount``</td> <td class='value aborted'>``result.abortedCount``</td> <td class='value time'>``escapeHtml(formatTime(result.elapsedTime))``<span class='label'>sec</span></td> </tr> <tr> <td class='label'>Total</td> <td class='label'>Succeeded</td> <td class='label'>Failures</td> <td class='label'>Errors</td> <td class='label'>Skipped</td> <td class='label'>Aborted</td> <td class='label'>Time</td> </tr> </tbody> </table> "); } void generateResultsTable(FileWriter fw, TestDescription root, TestRunResult result) { fw.write(" <table class='results'> <thead> <tr> <th>Tests</th> </tr> </thead> <tbody> "); void traverseTests(TestDescription d, Integer depth) { for( child in d.children ) { value r = result.results.find((TestResult r) => r.description == child); if( exists r ) { generateResultRow(fw, d, r, depth); if( r.description.children.empty ) { result.results .select((v) => v.description.children.empty && v.description.name == r.description.name && v.description.variant exists && v.description.variantIndex exists) .each((v) => generateResultRow(fw, r.description, v, depth+1)); } } traverseTests(child, depth+1); } } traverseTests(root, 0); fw.write(" </tbody> </table> "); } void generateResultRow(FileWriter fw, TestDescription parent, TestResult result, Integer depth) { value expandableFlag = result.exception exists; value expandableSnippet = " expandable' onclick='toggleStackTrace(event)' title='Show/Hide more details'>"; switch(result.state) case (TestState.success) { fw.write("<tr class='success'>"); fw.write("<td>"); fw.write("<i class='icon success'></i>"); } case(TestState.error | TestState.failure) { fw.write("<tr class='failure``expandableFlag then expandableSnippet else "'>"``"); fw.write("<td>"); fw.write("<i class='icon failure'></i>"); } case(TestState.skipped) { fw.write("<tr class='skipped``expandableFlag then expandableSnippet else "'>"``"); fw.write("<td>"); fw.write("<i class='icon skipped'></i>"); } case(TestState.aborted) { fw.write("<tr class='aborted``expandableFlag then expandableSnippet else "'>"``"); fw.write("<td>"); fw.write("<i class='icon aborted'></i>"); } String name; if( exists variant = result.description.variant, exists variantIndex = result.description.variantIndex) { name = "#``variantIndex`` ``variant``"; } else if( result.description.name.startsWith(parent.name) ) { name = result.description.name.replaceFirst(parent.name+".", ""); } else { name = result.description.name; } if( depth > 0 ) { for(i in 1..depth ) { fw.write("<span class='indent'></span>"); } } fw.write("<span class='name``result.description.children.empty then "" else " suite"``' title='``escapeHtml(result.description.name)``'>``escapeHtml(name)``</span>"); fw.write("<span class='duration'>``escapeHtml(formatTime(result.elapsedTime))``s</span>"); if( exists e = result.exception ) { fw.write("<div class='stack-trace' style='display: none;'>"); fw.write("<span class='stack-trace-arrow'><span class='stack-trace-arrow-inner'></span></span>"); fw.write("<pre>"); printStackTrace(e, (s) { fw.write(escapeHtml(s)); }); fw.write("</pre>"); fw.write("</div>"); } fw.write("</td>"); fw.write("</tr>"); fw.write(operatingSystem.newline); } void generateScript(FileWriter fw) { fw.write( """ <script type='text/javascript'> function toggleStackTrace(e) { var st = e.currentTarget.getElementsByClassName('stack-trace')[0]; if (st.style.display == 'none') { st.style.display = ''; } else { st.style.display = 'none'; } } </script> """); } List<Module> findTestedModules(TestRunResult result) { value testedModules = ArrayList<Module>(); for(r in result.results) { if( exists m = r.description.functionDeclaration?.containingModule ) { if( !m in testedModules ) { testedModules.add(m); } } } return testedModules; } native String formatTime(Integer timeInMilliseconds); native("js") String formatTime(Integer timeInMilliseconds) { dynamic { dynamic t = timeInMilliseconds/1000.0; return t.toFixed(3); } } native("jvm") String formatTime(Integer timeInMilliseconds) { NumberFormat timeFormat = NumberFormat.numberInstance; timeFormat.groupingUsed = true; timeFormat.minimumFractionDigits = 3; timeFormat.maximumFractionDigits = 3; timeFormat.minimumIntegerDigits = 1; return timeFormat.format(timeInMilliseconds/1000.0); } } Map<Character, String> htmlEntitiesMap = createHtmlEntitiesMap(); Map<Character, String> createHtmlEntitiesMap() { value htmlEntitiesMap = HashMap<Character, String>(); htmlEntitiesMap['\"'] = """; // " - double-quote htmlEntitiesMap['&'] = "&"; // & - ampersand htmlEntitiesMap['<'] = "<"; // < - less-than htmlEntitiesMap['>'] = ">"; // > - greater-than htmlEntitiesMap['\{#00A0}'] = " "; // non-breaking space htmlEntitiesMap['\{#00A1}'] = "¡"; // inverted exclamation mark htmlEntitiesMap['\{#00A2}'] = "¢"; // cent sign htmlEntitiesMap['\{#00A3}'] = "£"; // pound sign htmlEntitiesMap['\{#00A4}'] = "¤"; // currency sign htmlEntitiesMap['\{#00A5}'] = "¥"; // yen sign = yuan sign htmlEntitiesMap['\{#00A6}'] = "¦"; // broken bar = broken vertical bar htmlEntitiesMap['\{#00A7}'] = "§"; // section sign htmlEntitiesMap['\{#00A8}'] = "¨"; // diaeresis = spacing diaeresis htmlEntitiesMap['\{#00A9}'] = "©"; // © - copyright sign htmlEntitiesMap['\{#00AA}'] = "ª"; // feminine ordinal indicator htmlEntitiesMap['\{#00AB}'] = "«"; // left-pointing double angle quotation mark = left pointing guillemet htmlEntitiesMap['\{#00AC}'] = "¬"; // not sign htmlEntitiesMap['\{#00AD}'] = "­"; // soft hyphen = discretionary hyphen htmlEntitiesMap['\{#00AE}'] = "®"; // ® - registered trademark sign htmlEntitiesMap['\{#00AF}'] = "¯"; // macron = spacing macron = overline = APL overbar htmlEntitiesMap['\{#00B0}'] = "°"; // degree sign htmlEntitiesMap['\{#00B1}'] = "±"; // plus-minus sign = plus-or-minus sign htmlEntitiesMap['\{#00B2}'] = "²"; // superscript two = superscript digit two = squared htmlEntitiesMap['\{#00B3}'] = "³"; // superscript three = superscript digit three = cubed htmlEntitiesMap['\{#00B4}'] = "´"; // acute accent = spacing acute htmlEntitiesMap['\{#00B5}'] = "µ"; // micro sign htmlEntitiesMap['\{#00B6}'] = "¶"; // pilcrow sign = paragraph sign htmlEntitiesMap['\{#00B7}'] = "·"; // middle dot = Georgian comma = Greek middle dot htmlEntitiesMap['\{#00B8}'] = "¸"; // cedilla = spacing cedilla htmlEntitiesMap['\{#00B9}'] = "¹"; // superscript one = superscript digit one htmlEntitiesMap['\{#00BA}'] = "º"; // masculine ordinal indicator htmlEntitiesMap['\{#00BB}'] = "»"; // right-pointing double angle quotation mark = right pointing guillemet htmlEntitiesMap['\{#00BC}'] = "¼"; // vulgar fraction one quarter = fraction one quarter htmlEntitiesMap['\{#00BD}'] = "½"; // vulgar fraction one half = fraction one half htmlEntitiesMap['\{#00BE}'] = "¾"; // vulgar fraction three quarters = fraction three quarters htmlEntitiesMap['\{#00BF}'] = "¿"; // inverted question mark = turned question mark htmlEntitiesMap['\{#00C0}'] = "À"; // À - uppercase A, grave accent htmlEntitiesMap['\{#00C1}'] = "Á"; // Á - uppercase A, acute accent htmlEntitiesMap['\{#00C2}'] = "Â"; //  - uppercase A, circumflex accent htmlEntitiesMap['\{#00C3}'] = "Ã"; // à - uppercase A, tilde htmlEntitiesMap['\{#00C4}'] = "Ä"; // Ä - uppercase A, umlaut htmlEntitiesMap['\{#00C5}'] = "Å"; // Å - uppercase A, ring htmlEntitiesMap['\{#00C6}'] = "Æ"; // Æ - uppercase AE htmlEntitiesMap['\{#00C7}'] = "Ç"; // Ç - uppercase C, cedilla htmlEntitiesMap['\{#00C8}'] = "È"; // È - uppercase E, grave accent htmlEntitiesMap['\{#00C9}'] = "É"; // É - uppercase E, acute accent htmlEntitiesMap['\{#00CA}'] = "Ê"; // Ê - uppercase E, circumflex accent htmlEntitiesMap['\{#00CB}'] = "Ë"; // Ë - uppercase E, umlaut htmlEntitiesMap['\{#00CC}'] = "Ì"; // Ì - uppercase I, grave accent htmlEntitiesMap['\{#00CD}'] = "Í"; // Í - uppercase I, acute accent htmlEntitiesMap['\{#00CE}'] = "Î"; // Î - uppercase I, circumflex accent htmlEntitiesMap['\{#00CF}'] = "Ï"; // Ï - uppercase I, umlaut htmlEntitiesMap['\{#00D0}'] = "Ð"; // Ð - uppercase Eth, Icelandic htmlEntitiesMap['\{#00D1}'] = "Ñ"; // Ñ - uppercase N, tilde htmlEntitiesMap['\{#00D2}'] = "Ò"; // Ò - uppercase O, grave accent htmlEntitiesMap['\{#00D3}'] = "Ó"; // Ó - uppercase O, acute accent htmlEntitiesMap['\{#00D4}'] = "Ô"; // Ô - uppercase O, circumflex accent htmlEntitiesMap['\{#00D5}'] = "Õ"; // Õ - uppercase O, tilde htmlEntitiesMap['\{#00D6}'] = "Ö"; // Ö - uppercase O, umlaut htmlEntitiesMap['\{#00D7}'] = "×"; // multiplication sign htmlEntitiesMap['\{#00D8}'] = "Ø"; // Ø - uppercase O, slash htmlEntitiesMap['\{#00D9}'] = "Ù"; // Ù - uppercase U, grave accent htmlEntitiesMap['\{#00DA}'] = "Ú"; // Ú - uppercase U, acute accent htmlEntitiesMap['\{#00DB}'] = "Û"; // Û - uppercase U, circumflex accent htmlEntitiesMap['\{#00DC}'] = "Ü"; // Ü - uppercase U, umlaut htmlEntitiesMap['\{#00DD}'] = "Ý"; // Ý - uppercase Y, acute accent htmlEntitiesMap['\{#00DE}'] = "Þ"; // Þ - uppercase THORN, Icelandic htmlEntitiesMap['\{#00DF}'] = "ß"; // ß - lowercase sharps, German htmlEntitiesMap['\{#00E0}'] = "à"; // à - lowercase a, grave accent htmlEntitiesMap['\{#00E1}'] = "á"; // á - lowercase a, acute accent htmlEntitiesMap['\{#00E2}'] = "â"; // â - lowercase a, circumflex accent htmlEntitiesMap['\{#00E3}'] = "ã"; // ã - lowercase a, tilde htmlEntitiesMap['\{#00E4}'] = "ä"; // ä - lowercase a, umlaut htmlEntitiesMap['\{#00E5}'] = "å"; // å - lowercase a, ring htmlEntitiesMap['\{#00E6}'] = "æ"; // æ - lowercase ae htmlEntitiesMap['\{#00E7}'] = "ç"; // ç - lowercase c, cedilla htmlEntitiesMap['\{#00E8}'] = "è"; // è - lowercase e, grave accent htmlEntitiesMap['\{#00E9}'] = "é"; // é - lowercase e, acute accent htmlEntitiesMap['\{#00EA}'] = "ê"; // ê - lowercase e, circumflex accent htmlEntitiesMap['\{#00EB}'] = "ë"; // ë - lowercase e, umlaut htmlEntitiesMap['\{#00EC}'] = "ì"; // ì - lowercase i, grave accent htmlEntitiesMap['\{#00ED}'] = "í"; // í - lowercase i, acute accent htmlEntitiesMap['\{#00EE}'] = "î"; // î - lowercase i, circumflex accent htmlEntitiesMap['\{#00EF}'] = "ï"; // ï - lowercase i, umlaut htmlEntitiesMap['\{#00F0}'] = "ð"; // ð - lowercase eth, Icelandic htmlEntitiesMap['\{#00F1}'] = "ñ"; // ñ - lowercase n, tilde htmlEntitiesMap['\{#00F2}'] = "ò"; // ò - lowercase o, grave accent htmlEntitiesMap['\{#00F3}'] = "ó"; // ó - lowercase o, acute accent htmlEntitiesMap['\{#00F4}'] = "ô"; // ô - lowercase o, circumflex accent htmlEntitiesMap['\{#00F5}'] = "õ"; // õ - lowercase o, tilde htmlEntitiesMap['\{#00F6}'] = "ö"; // ö - lowercase o, umlaut htmlEntitiesMap['\{#00F7}'] = "÷"; // division sign htmlEntitiesMap['\{#00F8}'] = "ø"; // ø - lowercase o, slash htmlEntitiesMap['\{#00F9}'] = "ù"; // ù - lowercase u, grave accent htmlEntitiesMap['\{#00FA}'] = "ú"; // ú - lowercase u, acute accent htmlEntitiesMap['\{#00FB}'] = "û"; // û - lowercase u, circumflex accent htmlEntitiesMap['\{#00FC}'] = "ü"; // ü - lowercase u, umlaut htmlEntitiesMap['\{#00FD}'] = "ý"; // ý - lowercase y, acute accent htmlEntitiesMap['\{#00FE}'] = "þ"; // þ - lowercase thorn, Icelandic htmlEntitiesMap['\{#00FF}'] = "ÿ"; // ÿ - lowercase y, umlaut htmlEntitiesMap['\{#0192}'] = "ƒ"; // latin small f with hook = function= florin, U+0192 ISOtech --> htmlEntitiesMap['\{#0391}'] = "Α"; // greek capital letter alpha, U+0391 --> htmlEntitiesMap['\{#0392}'] = "Β"; // greek capital letter beta, U+0392 --> htmlEntitiesMap['\{#0393}'] = "Γ"; // greek capital letter gamma,U+0393 ISOgrk3 --> htmlEntitiesMap['\{#0394}'] = "Δ"; // greek capital letter delta,U+0394 ISOgrk3 --> htmlEntitiesMap['\{#0395}'] = "Ε"; // greek capital letter epsilon, U+0395 --> htmlEntitiesMap['\{#0396}'] = "Ζ"; // greek capital letter zeta, U+0396 --> htmlEntitiesMap['\{#0397}'] = "Η"; // greek capital letter eta, U+0397 --> htmlEntitiesMap['\{#0398}'] = "Θ"; // greek capital letter theta,U+0398 ISOgrk3 --> htmlEntitiesMap['\{#0399}'] = "Ι"; // greek capital letter iota, U+0399 --> htmlEntitiesMap['\{#039A}'] = "Κ"; // greek capital letter kappa, U+039A --> htmlEntitiesMap['\{#039B}'] = "Λ"; // greek capital letter lambda,U+039B ISOgrk3 --> htmlEntitiesMap['\{#039C}'] = "Μ"; // greek capital letter mu, U+039C --> htmlEntitiesMap['\{#039D}'] = "Ν"; // greek capital letter nu, U+039D --> htmlEntitiesMap['\{#039E}'] = "Ξ"; // greek capital letter xi, U+039E ISOgrk3 --> htmlEntitiesMap['\{#039F}'] = "Ο"; // greek capital letter omicron, U+039F --> htmlEntitiesMap['\{#03A0}'] = "Π"; // greek capital letter pi, U+03A0 ISOgrk3 --> htmlEntitiesMap['\{#03A1}'] = "Ρ"; // greek capital letter rho, U+03A1 --> htmlEntitiesMap['\{#03A3}'] = "Σ"; // greek capital letter sigma,U+03A3 ISOgrk3 --> htmlEntitiesMap['\{#03A4}'] = "Τ"; // greek capital letter tau, U+03A4 --> htmlEntitiesMap['\{#03A5}'] = "Υ"; // greek capital letter upsilon,U+03A5 ISOgrk3 --> htmlEntitiesMap['\{#03A6}'] = "Φ"; // greek capital letter phi,U+03A6 ISOgrk3 --> htmlEntitiesMap['\{#03A7}'] = "Χ"; // greek capital letter chi, U+03A7 --> htmlEntitiesMap['\{#03A8}'] = "Ψ"; // greek capital letter psi,U+03A8 ISOgrk3 --> htmlEntitiesMap['\{#03A9}'] = "Ω"; // greek capital letter omega,U+03A9 ISOgrk3 --> htmlEntitiesMap['\{#03B1}'] = "α"; // greek small letter alpha,U+03B1 ISOgrk3 --> htmlEntitiesMap['\{#03B2}'] = "β"; // greek small letter beta, U+03B2 ISOgrk3 --> htmlEntitiesMap['\{#03B3}'] = "γ"; // greek small letter gamma,U+03B3 ISOgrk3 --> htmlEntitiesMap['\{#03B4}'] = "δ"; // greek small letter delta,U+03B4 ISOgrk3 --> htmlEntitiesMap['\{#03B5}'] = "ε"; // greek small letter epsilon,U+03B5 ISOgrk3 --> htmlEntitiesMap['\{#03B6}'] = "ζ"; // greek small letter zeta, U+03B6 ISOgrk3 --> htmlEntitiesMap['\{#03B7}'] = "η"; // greek small letter eta, U+03B7 ISOgrk3 --> htmlEntitiesMap['\{#03B8}'] = "θ"; // greek small letter theta,U+03B8 ISOgrk3 --> htmlEntitiesMap['\{#03B9}'] = "ι"; // greek small letter iota, U+03B9 ISOgrk3 --> htmlEntitiesMap['\{#03BA}'] = "κ"; // greek small letter kappa,U+03BA ISOgrk3 --> htmlEntitiesMap['\{#03BB}'] = "λ"; // greek small letter lambda,U+03BB ISOgrk3 --> htmlEntitiesMap['\{#03BC}'] = "μ"; // greek small letter mu, U+03BC ISOgrk3 --> htmlEntitiesMap['\{#03BD}'] = "ν"; // greek small letter nu, U+03BD ISOgrk3 --> htmlEntitiesMap['\{#03BE}'] = "ξ"; // greek small letter xi, U+03BE ISOgrk3 --> htmlEntitiesMap['\{#03BF}'] = "ο"; // greek small letter omicron, U+03BF NEW --> htmlEntitiesMap['\{#03C0}'] = "π"; // greek small letter pi, U+03C0 ISOgrk3 --> htmlEntitiesMap['\{#03C1}'] = "ρ"; // greek small letter rho, U+03C1 ISOgrk3 --> htmlEntitiesMap['\{#03C2}'] = "ς"; // greek small letter final sigma,U+03C2 ISOgrk3 --> htmlEntitiesMap['\{#03C3}'] = "σ"; // greek small letter sigma,U+03C3 ISOgrk3 --> htmlEntitiesMap['\{#03C4}'] = "τ"; // greek small letter tau, U+03C4 ISOgrk3 --> htmlEntitiesMap['\{#03C5}'] = "υ"; // greek small letter upsilon,U+03C5 ISOgrk3 --> htmlEntitiesMap['\{#03C6}'] = "φ"; // greek small letter phi, U+03C6 ISOgrk3 --> htmlEntitiesMap['\{#03C7}'] = "χ"; // greek small letter chi, U+03C7 ISOgrk3 --> htmlEntitiesMap['\{#03C8}'] = "ψ"; // greek small letter psi, U+03C8 ISOgrk3 --> htmlEntitiesMap['\{#03C9}'] = "ω"; // greek small letter omega,U+03C9 ISOgrk3 --> htmlEntitiesMap['\{#03D1}'] = "ϑ"; // greek small letter theta symbol,U+03D1 NEW --> htmlEntitiesMap['\{#03D2}'] = "ϒ"; // greek upsilon with hook symbol,U+03D2 NEW --> htmlEntitiesMap['\{#03D6}'] = "ϖ"; // greek pi symbol, U+03D6 ISOgrk3 --> htmlEntitiesMap['\{#2022}'] = "•"; // bullet = black small circle,U+2022 ISOpub --> htmlEntitiesMap['\{#2026}'] = "…"; // horizontal ellipsis = three dot leader,U+2026 ISOpub --> htmlEntitiesMap['\{#2032}'] = "′"; // prime = minutes = feet, U+2032 ISOtech --> htmlEntitiesMap['\{#2033}'] = "″"; // double prime = seconds = inches,U+2033 ISOtech --> htmlEntitiesMap['\{#203E}'] = "‾"; // overline = spacing overscore,U+203E NEW --> htmlEntitiesMap['\{#2044}'] = "⁄"; // fraction slash, U+2044 NEW --> htmlEntitiesMap['\{#2118}'] = "℘"; // script capital P = power set= Weierstrass p, U+2118 ISOamso --> htmlEntitiesMap['\{#2111}'] = "ℑ"; // blackletter capital I = imaginary part,U+2111 ISOamso --> htmlEntitiesMap['\{#211C}'] = "ℜ"; // blackletter capital R = real part symbol,U+211C ISOamso --> htmlEntitiesMap['\{#2122}'] = "™"; // trade mark sign, U+2122 ISOnum --> htmlEntitiesMap['\{#2135}'] = "ℵ"; // alef symbol = first transfinite cardinal,U+2135 NEW --> htmlEntitiesMap['\{#2190}'] = "←"; // leftwards arrow, U+2190 ISOnum --> htmlEntitiesMap['\{#2191}'] = "↑"; // upwards arrow, U+2191 ISOnum--> htmlEntitiesMap['\{#2192}'] = "→"; // rightwards arrow, U+2192 ISOnum --> htmlEntitiesMap['\{#2193}'] = "↓"; // downwards arrow, U+2193 ISOnum --> htmlEntitiesMap['\{#2194}'] = "↔"; // left right arrow, U+2194 ISOamsa --> htmlEntitiesMap['\{#21B5}'] = "↵"; // downwards arrow with corner leftwards= carriage return, U+21B5 NEW --> htmlEntitiesMap['\{#21D0}'] = "⇐"; // leftwards double arrow, U+21D0 ISOtech --> htmlEntitiesMap['\{#21D1}'] = "⇑"; // upwards double arrow, U+21D1 ISOamsa --> htmlEntitiesMap['\{#21D2}'] = "⇒"; // rightwards double arrow,U+21D2 ISOtech --> htmlEntitiesMap['\{#21D3}'] = "⇓"; // downwards double arrow, U+21D3 ISOamsa --> htmlEntitiesMap['\{#21D4}'] = "⇔"; // left right double arrow,U+21D4 ISOamsa --> htmlEntitiesMap['\{#2200}'] = "∀"; // for all, U+2200 ISOtech --> htmlEntitiesMap['\{#2202}'] = "∂"; // partial differential, U+2202 ISOtech --> htmlEntitiesMap['\{#2203}'] = "∃"; // there exists, U+2203 ISOtech --> htmlEntitiesMap['\{#2205}'] = "∅"; // empty set = null set = diameter,U+2205 ISOamso --> htmlEntitiesMap['\{#2207}'] = "∇"; // nabla = backward difference,U+2207 ISOtech --> htmlEntitiesMap['\{#2208}'] = "∈"; // element of, U+2208 ISOtech --> htmlEntitiesMap['\{#2209}'] = "∉"; // not an element of, U+2209 ISOtech --> htmlEntitiesMap['\{#220B}'] = "∋"; // contains as member, U+220B ISOtech --> htmlEntitiesMap['\{#220F}'] = "∏"; // n-ary product = product sign,U+220F ISOamsb --> htmlEntitiesMap['\{#2211}'] = "∑"; // n-ary summation, U+2211 ISOamsb --> htmlEntitiesMap['\{#2212}'] = "−"; // minus sign, U+2212 ISOtech --> htmlEntitiesMap['\{#2217}'] = "∗"; // asterisk operator, U+2217 ISOtech --> htmlEntitiesMap['\{#221A}'] = "√"; // square root = radical sign,U+221A ISOtech --> htmlEntitiesMap['\{#221D}'] = "∝"; // proportional to, U+221D ISOtech --> htmlEntitiesMap['\{#221E}'] = "∞"; // infinity, U+221E ISOtech --> htmlEntitiesMap['\{#2220}'] = "∠"; // angle, U+2220 ISOamso --> htmlEntitiesMap['\{#2227}'] = "∧"; // logical and = wedge, U+2227 ISOtech --> htmlEntitiesMap['\{#2228}'] = "∨"; // logical or = vee, U+2228 ISOtech --> htmlEntitiesMap['\{#2229}'] = "∩"; // intersection = cap, U+2229 ISOtech --> htmlEntitiesMap['\{#222A}'] = "∪"; // union = cup, U+222A ISOtech --> htmlEntitiesMap['\{#222B}'] = "∫"; // integral, U+222B ISOtech --> htmlEntitiesMap['\{#2234}'] = "∴"; // therefore, U+2234 ISOtech --> htmlEntitiesMap['\{#223C}'] = "∼"; // tilde operator = varies with = similar to,U+223C ISOtech --> htmlEntitiesMap['\{#2245}'] = "≅"; // approximately equal to, U+2245 ISOtech --> htmlEntitiesMap['\{#2248}'] = "≈"; // almost equal to = asymptotic to,U+2248 ISOamsr --> htmlEntitiesMap['\{#2260}'] = "≠"; // not equal to, U+2260 ISOtech --> htmlEntitiesMap['\{#2261}'] = "≡"; // identical to, U+2261 ISOtech --> htmlEntitiesMap['\{#2264}'] = "≤"; // less-than or equal to, U+2264 ISOtech --> htmlEntitiesMap['\{#2265}'] = "≥"; // greater-than or equal to,U+2265 ISOtech --> htmlEntitiesMap['\{#2282}'] = "⊂"; // subset of, U+2282 ISOtech --> htmlEntitiesMap['\{#2283}'] = "⊃"; // superset of, U+2283 ISOtech --> htmlEntitiesMap['\{#2286}'] = "⊆"; // subset of or equal to, U+2286 ISOtech --> htmlEntitiesMap['\{#2287}'] = "⊇"; // superset of or equal to,U+2287 ISOtech --> htmlEntitiesMap['\{#2295}'] = "⊕"; // circled plus = direct sum,U+2295 ISOamsb --> htmlEntitiesMap['\{#2297}'] = "⊗"; // circled times = vector product,U+2297 ISOamsb --> htmlEntitiesMap['\{#22A5}'] = "⊥"; // up tack = orthogonal to = perpendicular,U+22A5 ISOtech --> htmlEntitiesMap['\{#22C5}'] = "⋅"; // dot operator, U+22C5 ISOamsb --> htmlEntitiesMap['\{#2308}'] = "⌈"; // left ceiling = apl upstile,U+2308 ISOamsc --> htmlEntitiesMap['\{#2309}'] = "⌉"; // right ceiling, U+2309 ISOamsc --> htmlEntitiesMap['\{#230A}'] = "⌊"; // left floor = apl downstile,U+230A ISOamsc --> htmlEntitiesMap['\{#230B}'] = "⌋"; // right floor, U+230B ISOamsc --> htmlEntitiesMap['\{#2329}'] = "⟨"; // left-pointing angle bracket = bra,U+2329 ISOtech --> htmlEntitiesMap['\{#232A}'] = "⟩"; // right-pointing angle bracket = ket,U+232A ISOtech --> htmlEntitiesMap['\{#25CA}'] = "◊"; // lozenge, U+25CA ISOpub --> htmlEntitiesMap['\{#2660}'] = "♠"; // black spade suit, U+2660 ISOpub --> htmlEntitiesMap['\{#2663}'] = "♣"; // black club suit = shamrock,U+2663 ISOpub --> htmlEntitiesMap['\{#2665}'] = "♥"; // black heart suit = valentine,U+2665 ISOpub --> htmlEntitiesMap['\{#2666}'] = "♦"; // black diamond suit, U+2666 ISOpub --> htmlEntitiesMap['\{#0152}'] = "Œ"; // -- latin capital ligature OE,U+0152 ISOlat2 --> htmlEntitiesMap['\{#0153}'] = "œ"; // -- latin small ligature oe, U+0153 ISOlat2 --> htmlEntitiesMap['\{#0160}'] = "Š"; // -- latin capital letter S with caron,U+0160 ISOlat2 --> htmlEntitiesMap['\{#0161}'] = "š"; // -- latin small letter s with caron,U+0161 ISOlat2 --> htmlEntitiesMap['\{#0178}'] = "Ÿ"; // -- latin capital letter Y with diaeresis,U+0178 ISOlat2 --> htmlEntitiesMap['\{#02C6}'] = "ˆ"; // -- modifier letter circumflex accent,U+02C6 ISOpub --> htmlEntitiesMap['\{#02DC}'] = "˜"; // small tilde, U+02DC ISOdia --> htmlEntitiesMap['\{#2002}'] = " "; // en space, U+2002 ISOpub --> htmlEntitiesMap['\{#2003}'] = " "; // em space, U+2003 ISOpub --> htmlEntitiesMap['\{#2009}'] = " "; // thin space, U+2009 ISOpub --> htmlEntitiesMap['\{#200C}'] = "‌"; // zero width non-joiner,U+200C NEW RFC 2070 --> htmlEntitiesMap['\{#200D}'] = "‍"; // zero width joiner, U+200D NEW RFC 2070 --> htmlEntitiesMap['\{#200E}'] = "‎"; // left-to-right mark, U+200E NEW RFC 2070 --> htmlEntitiesMap['\{#200F}'] = "‏"; // right-to-left mark, U+200F NEW RFC 2070 --> htmlEntitiesMap['\{#2013}'] = "–"; // en dash, U+2013 ISOpub --> htmlEntitiesMap['\{#2014}'] = "—"; // em dash, U+2014 ISOpub --> htmlEntitiesMap['\{#2018}'] = "‘"; // left single quotation mark,U+2018 ISOnum --> htmlEntitiesMap['\{#2019}'] = "’"; // right single quotation mark,U+2019 ISOnum --> htmlEntitiesMap['\{#201A}'] = "‚"; // single low-9 quotation mark, U+201A NEW --> htmlEntitiesMap['\{#201C}'] = "“"; // left double quotation mark,U+201C ISOnum --> htmlEntitiesMap['\{#201D}'] = "”"; // right double quotation mark,U+201D ISOnum --> htmlEntitiesMap['\{#201E}'] = "„"; // double low-9 quotation mark, U+201E NEW --> htmlEntitiesMap['\{#2020}'] = "†"; // dagger, U+2020 ISOpub --> htmlEntitiesMap['\{#2021}'] = "‡"; // double dagger, U+2021 ISOpub --> htmlEntitiesMap['\{#2030}'] = "‰"; // per mille sign, U+2030 ISOtech --> htmlEntitiesMap['\{#2039}'] = "‹"; // single left-pointing angle quotation mark,U+2039 ISO proposed --> htmlEntitiesMap['\{#203A}'] = "›"; // single right-pointing angle quotation mark,U+203A ISO proposed --> htmlEntitiesMap['\{#20AC}'] = "€"; // -- euro sign, U+20AC NEW --> return htmlEntitiesMap; } String escapeHtml(String s) { value sb = StringBuilder(); for (c in s) { if( exists v = htmlEntitiesMap[c] ) { sb.append(v); } else { sb.appendCharacter(c); } } return sb.string; }