org.mule.util.XMLEntityCodec Maven / Gradle / Ivy
/*
* $Id: XMLEntityCodec.java 19191 2010-08-25 21:05:23Z tcarlson $
* --------------------------------------------------------------------------------------
* Copyright (c) MuleSoft, Inc. All rights reserved. http://www.mulesoft.com
*
* The software in this package is published under the terms of the CPAL v1.0
* license, a copy of which has been included with this distribution in the
* LICENSE.txt file.
*/
package org.mule.util;
import java.io.IOException;
import java.io.Writer;
import java.util.HashMap;
import java.util.Map;
/**
* This encoder contains methods that convert characters to Character entities as
* defined by http://www.w3.org/TR/REC-html40/sgml/entities.html. More precisely it
* combines the functionality of {@link org.apache.commons.lang.StringEscapeUtils#escapeXml(String)} and
* {@link org.apache.commons.lang.StringEscapeUtils#escapeHtml(String)} into a single pass.
*/
// @ThreadSafe
public final class XMLEntityCodec
{
private static final Entities MuleEntities = new Entities();
static
{
MuleEntities.addEntities(Entities.APOS_ARRAY);
MuleEntities.addEntities(Entities.BASIC_ARRAY);
MuleEntities.addEntities(Entities.ISO8859_1_ARRAY);
MuleEntities.addEntities(Entities.HTML40_ARRAY);
}
protected XMLEntityCodec()
{
// no-op
}
public static String encodeString(String str)
{
if (StringUtils.isEmpty(str))
{
return str;
}
return MuleEntities.escape(str);
}
public static String decodeString(String str)
{
if (StringUtils.isEmpty(str))
{
return str;
}
return MuleEntities.unescape(str);
}
/**
*
* Returns the name of the entity identified by the specified value.
*
*
* @param value the value to locate
* @return entity name associated with the specified value
*/
public static String entityName(int value)
{
return MuleEntities.map.name(value);
}
/**
*
* Returns the value of the entity identified by the specified name.
*
*
* @param name the name to locate
* @return entity value associated with the specified name
*/
public static int entityValue(String name)
{
return MuleEntities.map.value(name);
}
//
// everything from here on is copied from commons-lang 2.2 + svn since it is not
// extensible and referencing the package-private class can lead to classloader
// problems :-(
//
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Provides HTML and XML entity utilities.
*
* @see ISO Entities
* @see HTML 3.2 Character Entities for ISO Latin-1
* @see HTML 4.0 Character entity references
* @see HTML 4.01 Character References
* @see HTML 4.01 Code positions
*
* @author Alexander Day Chaffee
* @author Gary Gregory
* @since 2.0
* @version $Id: XMLEntityCodec.java 19191 2010-08-25 21:05:23Z tcarlson $
*/
private static class Entities
{
private static final String[][] BASIC_ARRAY =
{
{"quot", "34"}, // " - double-quote
{"amp", "38"}, // & - ampersand
{"lt", "60"}, // < - less-than
{"gt", "62"}, // > - greater-than
};
private static final String[][] APOS_ARRAY =
{
{"apos", "39"}, // XML apostrophe
};
// package scoped for testing
static final String[][] ISO8859_1_ARRAY =
{
{"nbsp", "160"}, // non-breaking space
{"iexcl", "161"}, //inverted exclamation mark
{"cent", "162"}, //cent sign
{"pound", "163"}, //pound sign
{"curren", "164"}, //currency sign
{"yen", "165"}, //yen sign = yuan sign
{"brvbar", "166"}, //broken bar = broken vertical bar
{"sect", "167"}, //section sign
{"uml", "168"}, //diaeresis = spacing diaeresis
{"copy", "169"}, // ? - copyright sign
{"ordf", "170"}, //feminine ordinal indicator
{"laquo", "171"}, //left-pointing double angle quotation mark = left pointing guillemet
{"not", "172"}, //not sign
{"shy", "173"}, //soft hyphen = discretionary hyphen
{"reg", "174"}, // ? - registered trademark sign
{"macr", "175"}, //macron = spacing macron = overline = APL overbar
{"deg", "176"}, //degree sign
{"plusmn", "177"}, //plus-minus sign = plus-or-minus sign
{"sup2", "178"}, //superscript two = superscript digit two = squared
{"sup3", "179"}, //superscript three = superscript digit three = cubed
{"acute", "180"}, //acute accent = spacing acute
{"micro", "181"}, //micro sign
{"para", "182"}, //pilcrow sign = paragraph sign
{"middot", "183"}, //middle dot = Georgian comma = Greek middle dot
{"cedil", "184"}, //cedilla = spacing cedilla
{"sup1", "185"}, //superscript one = superscript digit one
{"ordm", "186"}, //masculine ordinal indicator
{"raquo", "187"}, //right-pointing double angle quotation mark = right pointing guillemet
{"frac14", "188"}, //vulgar fraction one quarter = fraction one quarter
{"frac12", "189"}, //vulgar fraction one half = fraction one half
{"frac34", "190"}, //vulgar fraction three quarters = fraction three quarters
{"iquest", "191"}, //inverted question mark = turned question mark
{"Agrave", "192"}, // ? - uppercase A, grave accent
{"Aacute", "193"}, // ? - uppercase A, acute accent
{"Acirc", "194"}, // ? - uppercase A, circumflex accent
{"Atilde", "195"}, // ? - uppercase A, tilde
{"Auml", "196"}, // ? - uppercase A, umlaut
{"Aring", "197"}, // ? - uppercase A, ring
{"AElig", "198"}, // ? - uppercase AE
{"Ccedil", "199"}, // ? - uppercase C, cedilla
{"Egrave", "200"}, // ? - uppercase E, grave accent
{"Eacute", "201"}, // ? - uppercase E, acute accent
{"Ecirc", "202"}, // ? - uppercase E, circumflex accent
{"Euml", "203"}, // ? - uppercase E, umlaut
{"Igrave", "204"}, // ? - uppercase I, grave accent
{"Iacute", "205"}, // ? - uppercase I, acute accent
{"Icirc", "206"}, // ? - uppercase I, circumflex accent
{"Iuml", "207"}, // ? - uppercase I, umlaut
{"ETH", "208"}, // ? - uppercase Eth, Icelandic
{"Ntilde", "209"}, // ? - uppercase N, tilde
{"Ograve", "210"}, // ? - uppercase O, grave accent
{"Oacute", "211"}, // ? - uppercase O, acute accent
{"Ocirc", "212"}, // ? - uppercase O, circumflex accent
{"Otilde", "213"}, // ? - uppercase O, tilde
{"Ouml", "214"}, // ? - uppercase O, umlaut
{"times", "215"}, //multiplication sign
{"Oslash", "216"}, // ? - uppercase O, slash
{"Ugrave", "217"}, // ? - uppercase U, grave accent
{"Uacute", "218"}, // ? - uppercase U, acute accent
{"Ucirc", "219"}, // ? - uppercase U, circumflex accent
{"Uuml", "220"}, // ? - uppercase U, umlaut
{"Yacute", "221"}, // ? - uppercase Y, acute accent
{"THORN", "222"}, // ? - uppercase THORN, Icelandic
{"szlig", "223"}, // ? - lowercase sharps, German
{"agrave", "224"}, // ? - lowercase a, grave accent
{"aacute", "225"}, // ? - lowercase a, acute accent
{"acirc", "226"}, // ? - lowercase a, circumflex accent
{"atilde", "227"}, // ? - lowercase a, tilde
{"auml", "228"}, // ? - lowercase a, umlaut
{"aring", "229"}, // ? - lowercase a, ring
{"aelig", "230"}, // ? - lowercase ae
{"ccedil", "231"}, // ? - lowercase c, cedilla
{"egrave", "232"}, // ? - lowercase e, grave accent
{"eacute", "233"}, // ? - lowercase e, acute accent
{"ecirc", "234"}, // ? - lowercase e, circumflex accent
{"euml", "235"}, // ? - lowercase e, umlaut
{"igrave", "236"}, // ? - lowercase i, grave accent
{"iacute", "237"}, // ? - lowercase i, acute accent
{"icirc", "238"}, // ? - lowercase i, circumflex accent
{"iuml", "239"}, // ? - lowercase i, umlaut
{"eth", "240"}, // ? - lowercase eth, Icelandic
{"ntilde", "241"}, // ? - lowercase n, tilde
{"ograve", "242"}, // ? - lowercase o, grave accent
{"oacute", "243"}, // ? - lowercase o, acute accent
{"ocirc", "244"}, // ? - lowercase o, circumflex accent
{"otilde", "245"}, // ? - lowercase o, tilde
{"ouml", "246"}, // ? - lowercase o, umlaut
{"divide", "247"}, // division sign
{"oslash", "248"}, // ? - lowercase o, slash
{"ugrave", "249"}, // ? - lowercase u, grave accent
{"uacute", "250"}, // ? - lowercase u, acute accent
{"ucirc", "251"}, // ? - lowercase u, circumflex accent
{"uuml", "252"}, // ? - lowercase u, umlaut
{"yacute", "253"}, // ? - lowercase y, acute accent
{"thorn", "254"}, // ? - lowercase thorn, Icelandic
{"yuml", "255"}, // ? - lowercase y, umlaut
};
// http://www.w3.org/TR/REC-html40/sgml/entities.html
// package scoped for testing
static final String[][] HTML40_ARRAY =
{
//
{"fnof", "402"}, //latin small f with hook = function= florin, U+0192 ISOtech -->
//
{"Alpha", "913"}, //greek capital letter alpha, U+0391 -->
{"Beta", "914"}, //greek capital letter beta, U+0392 -->
{"Gamma", "915"}, //greek capital letter gamma,U+0393 ISOgrk3 -->
{"Delta", "916"}, //greek capital letter delta,U+0394 ISOgrk3 -->
{"Epsilon", "917"}, //greek capital letter epsilon, U+0395 -->
{"Zeta", "918"}, //greek capital letter zeta, U+0396 -->
{"Eta", "919"}, //greek capital letter eta, U+0397 -->
{"Theta", "920"}, //greek capital letter theta,U+0398 ISOgrk3 -->
{"Iota", "921"}, //greek capital letter iota, U+0399 -->
{"Kappa", "922"}, //greek capital letter kappa, U+039A -->
{"Lambda", "923"}, //greek capital letter lambda,U+039B ISOgrk3 -->
{"Mu", "924"}, //greek capital letter mu, U+039C -->
{"Nu", "925"}, //greek capital letter nu, U+039D -->
{"Xi", "926"}, //greek capital letter xi, U+039E ISOgrk3 -->
{"Omicron", "927"}, //greek capital letter omicron, U+039F -->
{"Pi", "928"}, //greek capital letter pi, U+03A0 ISOgrk3 -->
{"Rho", "929"}, //greek capital letter rho, U+03A1 -->
//
{"Sigma", "931"}, //greek capital letter sigma,U+03A3 ISOgrk3 -->
{"Tau", "932"}, //greek capital letter tau, U+03A4 -->
{"Upsilon", "933"}, //greek capital letter upsilon,U+03A5 ISOgrk3 -->
{"Phi", "934"}, //greek capital letter phi,U+03A6 ISOgrk3 -->
{"Chi", "935"}, //greek capital letter chi, U+03A7 -->
{"Psi", "936"}, //greek capital letter psi,U+03A8 ISOgrk3 -->
{"Omega", "937"}, //greek capital letter omega,U+03A9 ISOgrk3 -->
{"alpha", "945"}, //greek small letter alpha,U+03B1 ISOgrk3 -->
{"beta", "946"}, //greek small letter beta, U+03B2 ISOgrk3 -->
{"gamma", "947"}, //greek small letter gamma,U+03B3 ISOgrk3 -->
{"delta", "948"}, //greek small letter delta,U+03B4 ISOgrk3 -->
{"epsilon", "949"}, //greek small letter epsilon,U+03B5 ISOgrk3 -->
{"zeta", "950"}, //greek small letter zeta, U+03B6 ISOgrk3 -->
{"eta", "951"}, //greek small letter eta, U+03B7 ISOgrk3 -->
{"theta", "952"}, //greek small letter theta,U+03B8 ISOgrk3 -->
{"iota", "953"}, //greek small letter iota, U+03B9 ISOgrk3 -->
{"kappa", "954"}, //greek small letter kappa,U+03BA ISOgrk3 -->
{"lambda", "955"}, //greek small letter lambda,U+03BB ISOgrk3 -->
{"mu", "956"}, //greek small letter mu, U+03BC ISOgrk3 -->
{"nu", "957"}, //greek small letter nu, U+03BD ISOgrk3 -->
{"xi", "958"}, //greek small letter xi, U+03BE ISOgrk3 -->
{"omicron", "959"}, //greek small letter omicron, U+03BF NEW -->
{"pi", "960"}, //greek small letter pi, U+03C0 ISOgrk3 -->
{"rho", "961"}, //greek small letter rho, U+03C1 ISOgrk3 -->
{"sigmaf", "962"}, //greek small letter final sigma,U+03C2 ISOgrk3 -->
{"sigma", "963"}, //greek small letter sigma,U+03C3 ISOgrk3 -->
{"tau", "964"}, //greek small letter tau, U+03C4 ISOgrk3 -->
{"upsilon", "965"}, //greek small letter upsilon,U+03C5 ISOgrk3 -->
{"phi", "966"}, //greek small letter phi, U+03C6 ISOgrk3 -->
{"chi", "967"}, //greek small letter chi, U+03C7 ISOgrk3 -->
{"psi", "968"}, //greek small letter psi, U+03C8 ISOgrk3 -->
{"omega", "969"}, //greek small letter omega,U+03C9 ISOgrk3 -->
{"thetasym", "977"}, //greek small letter theta symbol,U+03D1 NEW -->
{"upsih", "978"}, //greek upsilon with hook symbol,U+03D2 NEW -->
{"piv", "982"}, //greek pi symbol, U+03D6 ISOgrk3 -->
//
{"bull", "8226"}, //bullet = black small circle,U+2022 ISOpub -->
//
{"hellip", "8230"}, //horizontal ellipsis = three dot leader,U+2026 ISOpub -->
{"prime", "8242"}, //prime = minutes = feet, U+2032 ISOtech -->
{"Prime", "8243"}, //double prime = seconds = inches,U+2033 ISOtech -->
{"oline", "8254"}, //overline = spacing overscore,U+203E NEW -->
{"frasl", "8260"}, //fraction slash, U+2044 NEW -->
//
{"weierp", "8472"}, //script capital P = power set= Weierstrass p, U+2118 ISOamso -->
{"image", "8465"}, //blackletter capital I = imaginary part,U+2111 ISOamso -->
{"real", "8476"}, //blackletter capital R = real part symbol,U+211C ISOamso -->
{"trade", "8482"}, //trade mark sign, U+2122 ISOnum -->
{"alefsym", "8501"}, //alef symbol = first transfinite cardinal,U+2135 NEW -->
//
//
{"larr", "8592"}, //leftwards arrow, U+2190 ISOnum -->
{"uarr", "8593"}, //upwards arrow, U+2191 ISOnum-->
{"rarr", "8594"}, //rightwards arrow, U+2192 ISOnum -->
{"darr", "8595"}, //downwards arrow, U+2193 ISOnum -->
{"harr", "8596"}, //left right arrow, U+2194 ISOamsa -->
{"crarr", "8629"}, //downwards arrow with corner leftwards= carriage return, U+21B5 NEW -->
{"lArr", "8656"}, //leftwards double arrow, U+21D0 ISOtech -->
//
{"uArr", "8657"}, //upwards double arrow, U+21D1 ISOamsa -->
{"rArr", "8658"}, //rightwards double arrow,U+21D2 ISOtech -->
//
{"dArr", "8659"}, //downwards double arrow, U+21D3 ISOamsa -->
{"hArr", "8660"}, //left right double arrow,U+21D4 ISOamsa -->
//
{"forall", "8704"}, //for all, U+2200 ISOtech -->
{"part", "8706"}, //partial differential, U+2202 ISOtech -->
{"exist", "8707"}, //there exists, U+2203 ISOtech -->
{"empty", "8709"}, //empty set = null set = diameter,U+2205 ISOamso -->
{"nabla", "8711"}, //nabla = backward difference,U+2207 ISOtech -->
{"isin", "8712"}, //element of, U+2208 ISOtech -->
{"notin", "8713"}, //not an element of, U+2209 ISOtech -->
{"ni", "8715"}, //contains as member, U+220B ISOtech -->
//
{"prod", "8719"}, //n-ary product = product sign,U+220F ISOamsb -->
//
{"sum", "8721"}, //n-ary summation, U+2211 ISOamsb -->
//
{"minus", "8722"}, //minus sign, U+2212 ISOtech -->
{"lowast", "8727"}, //asterisk operator, U+2217 ISOtech -->
{"radic", "8730"}, //square root = radical sign,U+221A ISOtech -->
{"prop", "8733"}, //proportional to, U+221D ISOtech -->
{"infin", "8734"}, //infinity, U+221E ISOtech -->
{"ang", "8736"}, //angle, U+2220 ISOamso -->
{"and", "8743"}, //logical and = wedge, U+2227 ISOtech -->
{"or", "8744"}, //logical or = vee, U+2228 ISOtech -->
{"cap", "8745"}, //intersection = cap, U+2229 ISOtech -->
{"cup", "8746"}, //union = cup, U+222A ISOtech -->
{"int", "8747"}, //integral, U+222B ISOtech -->
{"there4", "8756"}, //therefore, U+2234 ISOtech -->
{"sim", "8764"}, //tilde operator = varies with = similar to,U+223C ISOtech -->
//
{"cong", "8773"}, //approximately equal to, U+2245 ISOtech -->
{"asymp", "8776"}, //almost equal to = asymptotic to,U+2248 ISOamsr -->
{"ne", "8800"}, //not equal to, U+2260 ISOtech -->
{"equiv", "8801"}, //identical to, U+2261 ISOtech -->
{"le", "8804"}, //less-than or equal to, U+2264 ISOtech -->
{"ge", "8805"}, //greater-than or equal to,U+2265 ISOtech -->
{"sub", "8834"}, //subset of, U+2282 ISOtech -->
{"sup", "8835"}, //superset of, U+2283 ISOtech -->
//
{"sube", "8838"}, //subset of or equal to, U+2286 ISOtech -->
{"supe", "8839"}, //superset of or equal to,U+2287 ISOtech -->
{"oplus", "8853"}, //circled plus = direct sum,U+2295 ISOamsb -->
{"otimes", "8855"}, //circled times = vector product,U+2297 ISOamsb -->
{"perp", "8869"}, //up tack = orthogonal to = perpendicular,U+22A5 ISOtech -->
{"sdot", "8901"}, //dot operator, U+22C5 ISOamsb -->
//
//
{"lceil", "8968"}, //left ceiling = apl upstile,U+2308 ISOamsc -->
{"rceil", "8969"}, //right ceiling, U+2309 ISOamsc -->
{"lfloor", "8970"}, //left floor = apl downstile,U+230A ISOamsc -->
{"rfloor", "8971"}, //right floor, U+230B ISOamsc -->
{"lang", "9001"}, //left-pointing angle bracket = bra,U+2329 ISOtech -->
//
{"rang", "9002"}, //right-pointing angle bracket = ket,U+232A ISOtech -->
//
//
{"loz", "9674"}, //lozenge, U+25CA ISOpub -->
//
{"spades", "9824"}, //black spade suit, U+2660 ISOpub -->
//
{"clubs", "9827"}, //black club suit = shamrock,U+2663 ISOpub -->
{"hearts", "9829"}, //black heart suit = valentine,U+2665 ISOpub -->
{"diams", "9830"}, //black diamond suit, U+2666 ISOpub -->
//
{"OElig", "338"}, // -- latin capital ligature OE,U+0152 ISOlat2 -->
{"oelig", "339"}, // -- latin small ligature oe, U+0153 ISOlat2 -->
//
{"Scaron", "352"}, // -- latin capital letter S with caron,U+0160 ISOlat2 -->
{"scaron", "353"}, // -- latin small letter s with caron,U+0161 ISOlat2 -->
{"Yuml", "376"}, // -- latin capital letter Y with diaeresis,U+0178 ISOlat2 -->
//
{"circ", "710"}, // -- modifier letter circumflex accent,U+02C6 ISOpub -->
{"tilde", "732"}, //small tilde, U+02DC ISOdia -->
//
{"ensp", "8194"}, //en space, U+2002 ISOpub -->
{"emsp", "8195"}, //em space, U+2003 ISOpub -->
{"thinsp", "8201"}, //thin space, U+2009 ISOpub -->
{"zwnj", "8204"}, //zero width non-joiner,U+200C NEW RFC 2070 -->
{"zwj", "8205"}, //zero width joiner, U+200D NEW RFC 2070 -->
{"lrm", "8206"}, //left-to-right mark, U+200E NEW RFC 2070 -->
{"rlm", "8207"}, //right-to-left mark, U+200F NEW RFC 2070 -->
{"ndash", "8211"}, //en dash, U+2013 ISOpub -->
{"mdash", "8212"}, //em dash, U+2014 ISOpub -->
{"lsquo", "8216"}, //left single quotation mark,U+2018 ISOnum -->
{"rsquo", "8217"}, //right single quotation mark,U+2019 ISOnum -->
{"sbquo", "8218"}, //single low-9 quotation mark, U+201A NEW -->
{"ldquo", "8220"}, //left double quotation mark,U+201C ISOnum -->
{"rdquo", "8221"}, //right double quotation mark,U+201D ISOnum -->
{"bdquo", "8222"}, //double low-9 quotation mark, U+201E NEW -->
{"dagger", "8224"}, //dagger, U+2020 ISOpub -->
{"Dagger", "8225"}, //double dagger, U+2021 ISOpub -->
{"permil", "8240"}, //per mille sign, U+2030 ISOtech -->
{"lsaquo", "8249"}, //single left-pointing angle quotation mark,U+2039 ISO proposed -->
//
{"rsaquo", "8250"}, //single right-pointing angle quotation mark,U+203A ISO proposed -->
//
{"euro", "8364"}, // -- euro sign, U+20AC NEW -->
};
// package scoped for testing
private EntityMap map = new Entities.LookupEntityMap();
/**
*
* Adds entities to this entity.
*
*
* @param entityArray array of entities to be added
*/
public void addEntities(String[][] entityArray)
{
for (int i = 0; i < entityArray.length; ++i)
{
addEntity(entityArray[i][0], Integer.parseInt(entityArray[i][1]));
}
}
/**
*
* Add an entity to this entity.
*
*
* @param name name of the entity
* @param value vale of the entity
*/
public void addEntity(String name, int value)
{
map.add(name, value);
}
/**
*
* Returns the name of the entity identified by the specified value.
*
*
* @param value the value to locate
* @return entity name associated with the specified value
*/
public String entityName(int value)
{
return map.name(value);
}
/**
*
* Returns the value of the entity identified by the specified name.
*
*
* @param name the name to locate
* @return entity value associated with the specified name
*/
public int entityValue(String name)
{
return map.value(name);
}
/**
*
* Escapes the characters in a String
.
*
*
* For example, if you have called addEntity("foo", 0xA1),
* escape("\u00A1") will return "&foo;"
*
*
* @param str The String
to escape.
* @return A new escaped String
.
*/
public String escape(String str)
{
// TODO: rewrite to use a Writer
StringBuffer buf = new StringBuffer(str.length() * 2);
for (int i = 0; i < str.length(); ++i)
{
char ch = str.charAt(i);
String entityName = this.entityName(ch);
if (entityName == null)
{
if (ch > 0x7F)
{
buf.append('&');
buf.append('#');
buf.append((int) ch);
buf.append(';');
}
else
{
buf.append(ch);
}
}
else
{
buf.append('&');
buf.append(entityName);
buf.append(';');
}
}
return buf.toString();
}
/**
*
* Escapes the characters in the String
passed and writes the
* result to the Writer
passed.
*
*
* @param writer The Writer
to write the results of the
* escaping to. Assumed to be a non-null value.
* @param str The String
to escape. Assumed to be a non-null
* value.
* @throws IOException when Writer
passed throws the exception
* from calls to the {@link Writer#write(int)} methods.
* @see #escape(String)
* @see Writer
*/
public void escape(Writer writer, String str) throws IOException
{
int len = str.length();
for (int i = 0; i < len; i++)
{
char c = str.charAt(i);
String entityName = this.entityName(c);
if (entityName == null)
{
if (c > 0x7F)
{
writer.write("");
writer.write(Integer.toString(c, 10));
writer.write(';');
}
else
{
writer.write(c);
}
}
else
{
writer.write('&');
writer.write(entityName);
writer.write(';');
}
}
}
/**
*
* Unescapes the entities in a String
.
*
*
* For example, if you have called addEntity("foo", 0xA1),
* unescape("&foo;") will return "\u00A1"
*
*
* @param str The String
to escape.
* @return A new escaped String
or str itself if no unescaping
* was necessary.
*/
public String unescape(String str)
{
int firstAmp = str.indexOf('&');
if (firstAmp < 0)
{
return str;
}
StringBuffer buf = new StringBuffer(str.length());
buf.append(str.substring(0, firstAmp));
for (int i = firstAmp; i < str.length(); ++i)
{
char ch = str.charAt(i);
if (ch == '&')
{
int semi = str.indexOf(';', i + 1);
if (semi == -1)
{
buf.append(ch);
continue;
}
int amph = str.indexOf('&', i + 1);
if (amph != -1 && amph < semi)
{
// Then the text looks like &...&...;
buf.append(ch);
continue;
}
String entityName = str.substring(i + 1, semi);
int entityValue;
if (entityName.length() == 0)
{
entityValue = -1;
}
else if (entityName.charAt(0) == '#')
{
if (entityName.length() == 1)
{
entityValue = -1;
}
else
{
char charAt1 = entityName.charAt(1);
try
{
if (charAt1 == 'x' || charAt1 == 'X')
{
entityValue = Integer.valueOf(entityName.substring(2), 16).intValue();
}
else
{
entityValue = Integer.parseInt(entityName.substring(1));
}
if (entityValue > 0xFFFF)
{
entityValue = -1;
}
}
catch (NumberFormatException ex)
{
entityValue = -1;
}
}
}
else
{
entityValue = this.entityValue(entityName);
}
if (entityValue == -1)
{
buf.append('&');
buf.append(entityName);
buf.append(';');
}
else
{
buf.append((char) (entityValue));
}
i = semi;
}
else
{
buf.append(ch);
}
}
return buf.toString();
}
/**
*
* Unescapes the escaped entities in the String
passed and
* writes the result to the Writer
passed.
*
*
* @param writer The Writer
to write the results to; assumed
* to be non-null.
* @param string The String
to write the results to; assumed
* to be non-null.
* @throws IOException when Writer
passed throws the exception
* from calls to the {@link Writer#write(int)} methods.
* @see #escape(String)
* @see Writer
*/
public void unescape(Writer writer, String string) throws IOException
{
int firstAmp = string.indexOf('&');
if (firstAmp < 0)
{
writer.write(string);
return;
}
writer.write(string, 0, firstAmp);
int len = string.length();
for (int i = firstAmp; i < len; i++)
{
char c = string.charAt(i);
if (c == '&')
{
int nextIdx = i + 1;
int semiColonIdx = string.indexOf(';', nextIdx);
if (semiColonIdx == -1)
{
writer.write(c);
continue;
}
int amphersandIdx = string.indexOf('&', i + 1);
if (amphersandIdx != -1 && amphersandIdx < semiColonIdx)
{
// Then the text looks like &...&...;
writer.write(c);
continue;
}
String entityContent = string.substring(nextIdx, semiColonIdx);
int entityValue = -1;
int entityContentLen = entityContent.length();
if (entityContentLen > 0)
{
if (entityContent.charAt(0) == '#')
{ // escaped value content is an integer (decimal or
// hexidecimal)
if (entityContentLen > 1)
{
char isHexChar = entityContent.charAt(1);
try
{
switch (isHexChar)
{
case 'X' :
case 'x' :
entityValue = Integer.parseInt(entityContent.substring(2), 16);
break;
default :
entityValue = Integer.parseInt(entityContent.substring(1), 10);
}
if (entityValue > 0xFFFF)
{
entityValue = -1;
}
}
catch (NumberFormatException e)
{
entityValue = -1;
}
}
}
else
{ // escaped value content is an entity name
entityValue = this.entityValue(entityContent);
}
}
if (entityValue == -1)
{
writer.write('&');
writer.write(entityContent);
writer.write(';');
}
else
{
writer.write(entityValue);
}
i = semiColonIdx; // move index up to the semi-colon
}
else
{
writer.write(c);
}
}
}
private static interface EntityMap
{
/**
*
* Add an entry to this entity map.
*
*
* @param name the entity name
* @param value the entity value
*/
void add(String name, int value);
/**
*
* Returns the name of the entity identified by the specified value.
*
*
* @param value the value to locate
* @return entity name associated with the specified value
*/
String name(int value);
/**
*
* Returns the value of the entity identified by the specified name.
*
*
* @param name the name to locate
* @return entity value associated with the specified name
*/
int value(String name);
}
private static class PrimitiveEntityMap implements EntityMap
{
private Map mapNameToValue = new HashMap();
private IntHashMap mapValueToName = new IntHashMap();
/**
* {@inheritDoc}
*/
public void add(String name, int value)
{
mapNameToValue.put(name, new Integer(value));
mapValueToName.put(value, name);
}
/**
* {@inheritDoc}
*/
public String name(int value)
{
return (String) mapValueToName.get(value);
}
/**
* {@inheritDoc}
*/
public int value(String name)
{
Object value = mapNameToValue.get(name);
if (value == null)
{
return -1;
}
return ((Integer) value).intValue();
}
}
private static class LookupEntityMap extends PrimitiveEntityMap
{
private static final int LOOKUP_TABLE_SIZE = 256;
private String[] lookupTable;
/**
* {@inheritDoc}
*/
public String name(int value)
{
if (value < LOOKUP_TABLE_SIZE)
{
return lookupTable()[value];
}
return super.name(value);
}
/**
*
* Returns the lookup table for this entity map. The lookup table is
* created if it has not been previously.
*
*
* @return the lookup table
*/
private String[] lookupTable()
{
if (lookupTable == null)
{
createLookupTable();
}
return lookupTable;
}
/**
*
* Creates an entity lookup table of LOOKUP_TABLE_SIZE elements,
* initialized with entity names.
*
*/
private void createLookupTable()
{
lookupTable = new String[LOOKUP_TABLE_SIZE];
for (int i = 0; i < LOOKUP_TABLE_SIZE; ++i)
{
lookupTable[i] = super.name(i);
}
}
}
/**
*
* A hash map that uses primitive ints for the key rather than objects.
*
*
* Note that this class is for internal optimization purposes only, and may
* not be supported in future releases of Jakarta Commons Lang. Utilities of
* this sort may be included in future releases of Jakarta Commons
* Collections.
*
*
* @author Justin Couch
* @author Alex Chaffee ([email protected])
* @author Stephen Colebourne
* @since 2.0
* @version $Revision: 19191 $
* @see java.util.HashMap
*/
private static class IntHashMap
{
/**
* The hash table data.
*/
private transient Entry table[];
/**
* The total number of entries in the hash table.
*/
private transient int count;
/**
* The table is rehashed when its size exceeds this threshold. (The value
* of this field is (int)(capacity * loadFactor).)
*
* @serial
*/
private int threshold;
/**
* The load factor for the hashtable.
*
* @serial
*/
private float loadFactor;
/**
*
* Innerclass that acts as a datastructure to create a new entry in the
* table.
*
*/
private static class Entry
{
int hash;
int key;
Object value;
Entry next;
/**
*
* Create a new entry with the given values.
*
*
* @param hash The code used to hash the object with
* @param key The key used to enter this in the table
* @param value The value for this key
* @param next A reference to the next entry in the table
*/
protected Entry(int hash, int key, Object value, Entry next)
{
this.hash = hash;
this.key = key;
this.value = value;
this.next = next;
}
}
/**
*
* Constructs a new, empty hashtable with a default capacity and load
* factor, which is 20
and 0.75
* respectively.
*
*/
public IntHashMap()
{
this(20, 0.75f);
}
/**
*
* Constructs a new, empty hashtable with the specified initial capacity
* and default load factor, which is 0.75
.
*
*
* @param initialCapacity the initial capacity of the hashtable.
* @throws IllegalArgumentException if the initial capacity is less than
* zero.
*/
public IntHashMap(int initialCapacity)
{
this(initialCapacity, 0.75f);
}
/**
*
* Constructs a new, empty hashtable with the specified initial capacity
* and the specified load factor.
*
*
* @param initialCapacity the initial capacity of the hashtable.
* @param loadFactor the load factor of the hashtable.
* @throws IllegalArgumentException if the initial capacity is less than
* zero, or if the load factor is nonpositive.
*/
public IntHashMap(int initialCapacity, float loadFactor)
{
super();
if (initialCapacity < 0)
{
throw new IllegalArgumentException("Illegal Capacity: " + initialCapacity);
}
if (loadFactor <= 0)
{
throw new IllegalArgumentException("Illegal Load: " + loadFactor);
}
if (initialCapacity == 0)
{
initialCapacity = 1;
}
this.loadFactor = loadFactor;
table = new Entry[initialCapacity];
threshold = (int) (initialCapacity * loadFactor);
}
/**
*
* Returns the number of keys in this hashtable.
*
*
* @return the number of keys in this hashtable.
*/
public int size()
{
return count;
}
/**
*
* Tests if this hashtable maps no keys to values.
*
*
* @return true
if this hashtable maps no keys to values;
* false
otherwise.
*/
public boolean isEmpty()
{
return count == 0;
}
/**
*
* Tests if some key maps into the specified value in this hashtable.
* This operation is more expensive than the containsKey
* method.
*
*
* Note that this method is identical in functionality to containsValue,
* (which is part of the Map interface in the collections framework).
*
*
* @param value a value to search for.
* @return true
if and only if some key maps to the
* value
argument in this hashtable as determined
* by the equals method; false
* otherwise.
* @throws NullPointerException if the value is null
.
* @see #containsKey(int)
* @see #containsValue(Object)
* @see java.util.Map
*/
public boolean contains(Object value)
{
if (value == null)
{
throw new IllegalArgumentException("parameter value may not be null");
}
Entry tab[] = table;
for (int i = tab.length; i-- > 0;)
{
for (Entry e = tab[i]; e != null; e = e.next)
{
if (e.value.equals(value))
{
return true;
}
}
}
return false;
}
/**
*
* Returns true
if this HashMap maps one or more keys to
* this value.
*
*
* Note that this method is identical in functionality to contains (which
* predates the Map interface).
*
*
* @param value value whose presence in this HashMap is to be tested.
* @return boolean true
if the value is contained
* @see java.util.Map
* @since JDK1.2
*/
public boolean containsValue(Object value)
{
return contains(value);
}
/**
*
* Tests if the specified object is a key in this hashtable.
*
*
* @param key possible key.
* @return true
if and only if the specified object is a
* key in this hashtable, as determined by the equals
* method; false
otherwise.
* @see #contains(Object)
*/
public boolean containsKey(int key)
{
Entry tab[] = table;
int hash = key;
int index = (hash & 0x7FFFFFFF) % tab.length;
for (Entry e = tab[index]; e != null; e = e.next)
{
if (e.hash == hash)
{
return true;
}
}
return false;
}
/**
*
* Returns the value to which the specified key is mapped in this map.
*
*
* @param key a key in the hashtable.
* @return the value to which the key is mapped in this hashtable;
* null
if the key is not mapped to any value in
* this hashtable.
* @see #put(int, Object)
*/
public Object get(int key)
{
Entry tab[] = table;
int hash = key;
int index = (hash & 0x7FFFFFFF) % tab.length;
for (Entry e = tab[index]; e != null; e = e.next)
{
if (e.hash == hash)
{
return e.value;
}
}
return null;
}
/**
*
* Increases the capacity of and internally reorganizes this hashtable,
* in order to accommodate and access its entries more efficiently.
*
*
* This method is called automatically when the number of keys in the
* hashtable exceeds this hashtable's capacity and load factor.
*
*/
protected void rehash()
{
int oldCapacity = table.length;
Entry oldMap[] = table;
int newCapacity = oldCapacity * 2 + 1;
Entry newMap[] = new Entry[newCapacity];
threshold = (int) (newCapacity * loadFactor);
table = newMap;
for (int i = oldCapacity; i-- > 0;)
{
for (Entry old = oldMap[i]; old != null;)
{
Entry e = old;
old = old.next;
int index = (e.hash & 0x7FFFFFFF) % newCapacity;
e.next = newMap[index];
newMap[index] = e;
}
}
}
/**
*
* Maps the specified key
to the specified
* value
in this hashtable. The key cannot be
* null
.
*
*
* The value can be retrieved by calling the get
method
* with a key that is equal to the original key.
*
*
* @param key the hashtable key.
* @param value the value.
* @return the previous value of the specified key in this hashtable, or
* null
if it did not have one.
* @throws NullPointerException if the key is null
.
* @see #get(int)
*/
public Object put(int key, Object value)
{
// Makes sure the key is not already in the hashtable.
Entry tab[] = table;
int hash = key;
int index = (hash & 0x7FFFFFFF) % tab.length;
for (Entry e = tab[index]; e != null; e = e.next)
{
if (e.hash == hash)
{
Object old = e.value;
e.value = value;
return old;
}
}
if (count >= threshold)
{
// Rehash the table if the threshold is exceeded
rehash();
tab = table;
index = (hash & 0x7FFFFFFF) % tab.length;
}
// Creates the new entry.
Entry e = new Entry(hash, key, value, tab[index]);
tab[index] = e;
count++;
return null;
}
/**
*
* Removes the key (and its corresponding value) from this hashtable.
*
*
* This method does nothing if the key is not present in the hashtable.
*
*
* @param key the key that needs to be removed.
* @return the value to which the key had been mapped in this hashtable,
* or null
if the key did not have a mapping.
*/
public Object remove(int key)
{
Entry tab[] = table;
int hash = key;
int index = (hash & 0x7FFFFFFF) % tab.length;
for (Entry e = tab[index], prev = null; e != null; prev = e, e = e.next)
{
if (e.hash == hash)
{
if (prev != null)
{
prev.next = e.next;
}
else
{
tab[index] = e.next;
}
count--;
Object oldValue = e.value;
e.value = null;
return oldValue;
}
}
return null;
}
/**
* Clears this hashtable so that it contains no keys.
*/
public synchronized void clear()
{
Entry tab[] = table;
for (int index = tab.length; --index >= 0;)
{
tab[index] = null;
}
count = 0;
}
}
}
}