1 /* 2 * Copyright (c) MuleSoft, Inc. All rights reserved. http://www.mulesoft.com 3 * The software in this package is published under the terms of the CPAL v1.0 4 * license, a copy of which has been included with this distribution in the 5 * LICENSE.txt file. 6 */ 7 package org.mule.util; 8 9 import java.io.IOException; 10 import java.io.Writer; 11 import java.util.HashMap; 12 import java.util.Map; 13 14 /** 15 * This encoder contains methods that convert characters to Character entities as 16 * defined by http://www.w3.org/TR/REC-html40/sgml/entities.html. More precisely it 17 * combines the functionality of {@link org.apache.commons.lang.StringEscapeUtils#escapeXml(String)} and 18 * {@link org.apache.commons.lang.StringEscapeUtils#escapeHtml(String)} into a single pass. 19 */ 20 // @ThreadSafe 21 public final class XMLEntityCodec 22 { 23 private static final Entities MuleEntities = new Entities(); 24 25 static 26 { 27 MuleEntities.addEntities(Entities.APOS_ARRAY); 28 MuleEntities.addEntities(Entities.BASIC_ARRAY); 29 MuleEntities.addEntities(Entities.ISO8859_1_ARRAY); 30 MuleEntities.addEntities(Entities.HTML40_ARRAY); 31 } 32 33 protected XMLEntityCodec() 34 { 35 // no-op 36 } 37 38 public static String encodeString(String str) 39 { 40 if (StringUtils.isEmpty(str)) 41 { 42 return str; 43 } 44 45 return MuleEntities.escape(str); 46 } 47 48 public static String decodeString(String str) 49 { 50 if (StringUtils.isEmpty(str)) 51 { 52 return str; 53 } 54 55 return MuleEntities.unescape(str); 56 } 57 58 /** 59 * <p> 60 * Returns the name of the entity identified by the specified value. 61 * </p> 62 * 63 * @param value the value to locate 64 * @return entity name associated with the specified value 65 */ 66 public static String entityName(int value) 67 { 68 return MuleEntities.map.name(value); 69 } 70 71 /** 72 * <p> 73 * Returns the value of the entity identified by the specified name. 74 * </p> 75 * 76 * @param name the name to locate 77 * @return entity value associated with the specified name 78 */ 79 public static int entityValue(String name) 80 { 81 return MuleEntities.map.value(name); 82 } 83 84 85 // 86 // everything from here on is copied from commons-lang 2.2 + svn since it is not 87 // extensible and referencing the package-private class can lead to classloader 88 // problems :-( 89 // 90 91 /* 92 * Licensed to the Apache Software Foundation (ASF) under one or more 93 * contributor license agreements. See the NOTICE file distributed with 94 * this work for additional information regarding copyright ownership. 95 * The ASF licenses this file to You under the Apache License, Version 2.0 96 * (the "License"); you may not use this file except in compliance with 97 * the License. You may obtain a copy of the License at 98 * 99 * http://www.apache.org/licenses/LICENSE-2.0 100 * 101 * Unless required by applicable law or agreed to in writing, software 102 * distributed under the License is distributed on an "AS IS" BASIS, 103 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 104 * See the License for the specific language governing permissions and 105 * limitations under the License. 106 */ 107 108 /** 109 * <p>Provides HTML and XML entity utilities.</p> 110 * 111 * @see <a href="http://hotwired.lycos.com/webmonkey/reference/special_characters/">ISO Entities</a> 112 * @see <a href="http://www.w3.org/TR/REC-html32#latin1">HTML 3.2 Character Entities for ISO Latin-1</a> 113 * @see <a href="http://www.w3.org/TR/REC-html40/sgml/entities.html">HTML 4.0 Character entity references</a> 114 * @see <a href="http://www.w3.org/TR/html401/charset.html#h-5.3">HTML 4.01 Character References</a> 115 * @see <a href="http://www.w3.org/TR/html401/charset.html#code-position">HTML 4.01 Code positions</a> 116 * 117 * @author <a href="mailto:alex@purpletech.com">Alexander Day Chaffee</a> 118 * @author <a href="mailto:ggregory@seagullsw.com">Gary Gregory</a> 119 * @since 2.0 120 * @version $Id$ 121 */ 122 private static class Entities 123 { 124 125 private static final String[][] BASIC_ARRAY = 126 { 127 {"quot", "34"}, // " - double-quote 128 {"amp", "38"}, // & - ampersand 129 {"lt", "60"}, // < - less-than 130 {"gt", "62"}, // > - greater-than 131 }; 132 133 private static final String[][] APOS_ARRAY = 134 { 135 {"apos", "39"}, // XML apostrophe 136 }; 137 138 // package scoped for testing 139 static final String[][] ISO8859_1_ARRAY = 140 { 141 {"nbsp", "160"}, // non-breaking space 142 {"iexcl", "161"}, //inverted exclamation mark 143 {"cent", "162"}, //cent sign 144 {"pound", "163"}, //pound sign 145 {"curren", "164"}, //currency sign 146 {"yen", "165"}, //yen sign = yuan sign 147 {"brvbar", "166"}, //broken bar = broken vertical bar 148 {"sect", "167"}, //section sign 149 {"uml", "168"}, //diaeresis = spacing diaeresis 150 {"copy", "169"}, // � - copyright sign 151 {"ordf", "170"}, //feminine ordinal indicator 152 {"laquo", "171"}, //left-pointing double angle quotation mark = left pointing guillemet 153 {"not", "172"}, //not sign 154 {"shy", "173"}, //soft hyphen = discretionary hyphen 155 {"reg", "174"}, // � - registered trademark sign 156 {"macr", "175"}, //macron = spacing macron = overline = APL overbar 157 {"deg", "176"}, //degree sign 158 {"plusmn", "177"}, //plus-minus sign = plus-or-minus sign 159 {"sup2", "178"}, //superscript two = superscript digit two = squared 160 {"sup3", "179"}, //superscript three = superscript digit three = cubed 161 {"acute", "180"}, //acute accent = spacing acute 162 {"micro", "181"}, //micro sign 163 {"para", "182"}, //pilcrow sign = paragraph sign 164 {"middot", "183"}, //middle dot = Georgian comma = Greek middle dot 165 {"cedil", "184"}, //cedilla = spacing cedilla 166 {"sup1", "185"}, //superscript one = superscript digit one 167 {"ordm", "186"}, //masculine ordinal indicator 168 {"raquo", "187"}, //right-pointing double angle quotation mark = right pointing guillemet 169 {"frac14", "188"}, //vulgar fraction one quarter = fraction one quarter 170 {"frac12", "189"}, //vulgar fraction one half = fraction one half 171 {"frac34", "190"}, //vulgar fraction three quarters = fraction three quarters 172 {"iquest", "191"}, //inverted question mark = turned question mark 173 {"Agrave", "192"}, // � - uppercase A, grave accent 174 {"Aacute", "193"}, // � - uppercase A, acute accent 175 {"Acirc", "194"}, // � - uppercase A, circumflex accent 176 {"Atilde", "195"}, // � - uppercase A, tilde 177 {"Auml", "196"}, // � - uppercase A, umlaut 178 {"Aring", "197"}, // � - uppercase A, ring 179 {"AElig", "198"}, // � - uppercase AE 180 {"Ccedil", "199"}, // � - uppercase C, cedilla 181 {"Egrave", "200"}, // � - uppercase E, grave accent 182 {"Eacute", "201"}, // � - uppercase E, acute accent 183 {"Ecirc", "202"}, // � - uppercase E, circumflex accent 184 {"Euml", "203"}, // � - uppercase E, umlaut 185 {"Igrave", "204"}, // � - uppercase I, grave accent 186 {"Iacute", "205"}, // � - uppercase I, acute accent 187 {"Icirc", "206"}, // � - uppercase I, circumflex accent 188 {"Iuml", "207"}, // � - uppercase I, umlaut 189 {"ETH", "208"}, // � - uppercase Eth, Icelandic 190 {"Ntilde", "209"}, // � - uppercase N, tilde 191 {"Ograve", "210"}, // � - uppercase O, grave accent 192 {"Oacute", "211"}, // � - uppercase O, acute accent 193 {"Ocirc", "212"}, // � - uppercase O, circumflex accent 194 {"Otilde", "213"}, // � - uppercase O, tilde 195 {"Ouml", "214"}, // � - uppercase O, umlaut 196 {"times", "215"}, //multiplication sign 197 {"Oslash", "216"}, // � - uppercase O, slash 198 {"Ugrave", "217"}, // � - uppercase U, grave accent 199 {"Uacute", "218"}, // � - uppercase U, acute accent 200 {"Ucirc", "219"}, // � - uppercase U, circumflex accent 201 {"Uuml", "220"}, // � - uppercase U, umlaut 202 {"Yacute", "221"}, // � - uppercase Y, acute accent 203 {"THORN", "222"}, // � - uppercase THORN, Icelandic 204 {"szlig", "223"}, // � - lowercase sharps, German 205 {"agrave", "224"}, // � - lowercase a, grave accent 206 {"aacute", "225"}, // � - lowercase a, acute accent 207 {"acirc", "226"}, // � - lowercase a, circumflex accent 208 {"atilde", "227"}, // � - lowercase a, tilde 209 {"auml", "228"}, // � - lowercase a, umlaut 210 {"aring", "229"}, // � - lowercase a, ring 211 {"aelig", "230"}, // � - lowercase ae 212 {"ccedil", "231"}, // � - lowercase c, cedilla 213 {"egrave", "232"}, // � - lowercase e, grave accent 214 {"eacute", "233"}, // � - lowercase e, acute accent 215 {"ecirc", "234"}, // � - lowercase e, circumflex accent 216 {"euml", "235"}, // � - lowercase e, umlaut 217 {"igrave", "236"}, // � - lowercase i, grave accent 218 {"iacute", "237"}, // � - lowercase i, acute accent 219 {"icirc", "238"}, // � - lowercase i, circumflex accent 220 {"iuml", "239"}, // � - lowercase i, umlaut 221 {"eth", "240"}, // � - lowercase eth, Icelandic 222 {"ntilde", "241"}, // � - lowercase n, tilde 223 {"ograve", "242"}, // � - lowercase o, grave accent 224 {"oacute", "243"}, // � - lowercase o, acute accent 225 {"ocirc", "244"}, // � - lowercase o, circumflex accent 226 {"otilde", "245"}, // � - lowercase o, tilde 227 {"ouml", "246"}, // � - lowercase o, umlaut 228 {"divide", "247"}, // division sign 229 {"oslash", "248"}, // � - lowercase o, slash 230 {"ugrave", "249"}, // � - lowercase u, grave accent 231 {"uacute", "250"}, // � - lowercase u, acute accent 232 {"ucirc", "251"}, // � - lowercase u, circumflex accent 233 {"uuml", "252"}, // � - lowercase u, umlaut 234 {"yacute", "253"}, // � - lowercase y, acute accent 235 {"thorn", "254"}, // � - lowercase thorn, Icelandic 236 {"yuml", "255"}, // � - lowercase y, umlaut 237 }; 238 239 // http://www.w3.org/TR/REC-html40/sgml/entities.html 240 // package scoped for testing 241 static final String[][] HTML40_ARRAY = 242 { 243 // <!-- Latin Extended-B --> 244 {"fnof", "402"}, //latin small f with hook = function= florin, U+0192 ISOtech --> 245 // <!-- Greek --> 246 {"Alpha", "913"}, //greek capital letter alpha, U+0391 --> 247 {"Beta", "914"}, //greek capital letter beta, U+0392 --> 248 {"Gamma", "915"}, //greek capital letter gamma,U+0393 ISOgrk3 --> 249 {"Delta", "916"}, //greek capital letter delta,U+0394 ISOgrk3 --> 250 {"Epsilon", "917"}, //greek capital letter epsilon, U+0395 --> 251 {"Zeta", "918"}, //greek capital letter zeta, U+0396 --> 252 {"Eta", "919"}, //greek capital letter eta, U+0397 --> 253 {"Theta", "920"}, //greek capital letter theta,U+0398 ISOgrk3 --> 254 {"Iota", "921"}, //greek capital letter iota, U+0399 --> 255 {"Kappa", "922"}, //greek capital letter kappa, U+039A --> 256 {"Lambda", "923"}, //greek capital letter lambda,U+039B ISOgrk3 --> 257 {"Mu", "924"}, //greek capital letter mu, U+039C --> 258 {"Nu", "925"}, //greek capital letter nu, U+039D --> 259 {"Xi", "926"}, //greek capital letter xi, U+039E ISOgrk3 --> 260 {"Omicron", "927"}, //greek capital letter omicron, U+039F --> 261 {"Pi", "928"}, //greek capital letter pi, U+03A0 ISOgrk3 --> 262 {"Rho", "929"}, //greek capital letter rho, U+03A1 --> 263 // <!-- there is no Sigmaf, and no U+03A2 character either --> 264 {"Sigma", "931"}, //greek capital letter sigma,U+03A3 ISOgrk3 --> 265 {"Tau", "932"}, //greek capital letter tau, U+03A4 --> 266 {"Upsilon", "933"}, //greek capital letter upsilon,U+03A5 ISOgrk3 --> 267 {"Phi", "934"}, //greek capital letter phi,U+03A6 ISOgrk3 --> 268 {"Chi", "935"}, //greek capital letter chi, U+03A7 --> 269 {"Psi", "936"}, //greek capital letter psi,U+03A8 ISOgrk3 --> 270 {"Omega", "937"}, //greek capital letter omega,U+03A9 ISOgrk3 --> 271 {"alpha", "945"}, //greek small letter alpha,U+03B1 ISOgrk3 --> 272 {"beta", "946"}, //greek small letter beta, U+03B2 ISOgrk3 --> 273 {"gamma", "947"}, //greek small letter gamma,U+03B3 ISOgrk3 --> 274 {"delta", "948"}, //greek small letter delta,U+03B4 ISOgrk3 --> 275 {"epsilon", "949"}, //greek small letter epsilon,U+03B5 ISOgrk3 --> 276 {"zeta", "950"}, //greek small letter zeta, U+03B6 ISOgrk3 --> 277 {"eta", "951"}, //greek small letter eta, U+03B7 ISOgrk3 --> 278 {"theta", "952"}, //greek small letter theta,U+03B8 ISOgrk3 --> 279 {"iota", "953"}, //greek small letter iota, U+03B9 ISOgrk3 --> 280 {"kappa", "954"}, //greek small letter kappa,U+03BA ISOgrk3 --> 281 {"lambda", "955"}, //greek small letter lambda,U+03BB ISOgrk3 --> 282 {"mu", "956"}, //greek small letter mu, U+03BC ISOgrk3 --> 283 {"nu", "957"}, //greek small letter nu, U+03BD ISOgrk3 --> 284 {"xi", "958"}, //greek small letter xi, U+03BE ISOgrk3 --> 285 {"omicron", "959"}, //greek small letter omicron, U+03BF NEW --> 286 {"pi", "960"}, //greek small letter pi, U+03C0 ISOgrk3 --> 287 {"rho", "961"}, //greek small letter rho, U+03C1 ISOgrk3 --> 288 {"sigmaf", "962"}, //greek small letter final sigma,U+03C2 ISOgrk3 --> 289 {"sigma", "963"}, //greek small letter sigma,U+03C3 ISOgrk3 --> 290 {"tau", "964"}, //greek small letter tau, U+03C4 ISOgrk3 --> 291 {"upsilon", "965"}, //greek small letter upsilon,U+03C5 ISOgrk3 --> 292 {"phi", "966"}, //greek small letter phi, U+03C6 ISOgrk3 --> 293 {"chi", "967"}, //greek small letter chi, U+03C7 ISOgrk3 --> 294 {"psi", "968"}, //greek small letter psi, U+03C8 ISOgrk3 --> 295 {"omega", "969"}, //greek small letter omega,U+03C9 ISOgrk3 --> 296 {"thetasym", "977"}, //greek small letter theta symbol,U+03D1 NEW --> 297 {"upsih", "978"}, //greek upsilon with hook symbol,U+03D2 NEW --> 298 {"piv", "982"}, //greek pi symbol, U+03D6 ISOgrk3 --> 299 // <!-- General Punctuation --> 300 {"bull", "8226"}, //bullet = black small circle,U+2022 ISOpub --> 301 // <!-- bullet is NOT the same as bullet operator, U+2219 --> 302 {"hellip", "8230"}, //horizontal ellipsis = three dot leader,U+2026 ISOpub --> 303 {"prime", "8242"}, //prime = minutes = feet, U+2032 ISOtech --> 304 {"Prime", "8243"}, //double prime = seconds = inches,U+2033 ISOtech --> 305 {"oline", "8254"}, //overline = spacing overscore,U+203E NEW --> 306 {"frasl", "8260"}, //fraction slash, U+2044 NEW --> 307 // <!-- Letterlike Symbols --> 308 {"weierp", "8472"}, //script capital P = power set= Weierstrass p, U+2118 ISOamso --> 309 {"image", "8465"}, //blackletter capital I = imaginary part,U+2111 ISOamso --> 310 {"real", "8476"}, //blackletter capital R = real part symbol,U+211C ISOamso --> 311 {"trade", "8482"}, //trade mark sign, U+2122 ISOnum --> 312 {"alefsym", "8501"}, //alef symbol = first transfinite cardinal,U+2135 NEW --> 313 // <!-- alef symbol is NOT the same as hebrew letter alef,U+05D0 although the 314 // same glyph could be used to depict both characters --> 315 // <!-- Arrows --> 316 {"larr", "8592"}, //leftwards arrow, U+2190 ISOnum --> 317 {"uarr", "8593"}, //upwards arrow, U+2191 ISOnum--> 318 {"rarr", "8594"}, //rightwards arrow, U+2192 ISOnum --> 319 {"darr", "8595"}, //downwards arrow, U+2193 ISOnum --> 320 {"harr", "8596"}, //left right arrow, U+2194 ISOamsa --> 321 {"crarr", "8629"}, //downwards arrow with corner leftwards= carriage return, U+21B5 NEW --> 322 {"lArr", "8656"}, //leftwards double arrow, U+21D0 ISOtech --> 323 // <!-- ISO 10646 does not say that lArr is the same as the 'is implied by' 324 // arrow but also does not have any other character for that function. 325 // So ? lArr canbe used for 'is implied by' as ISOtech suggests --> 326 {"uArr", "8657"}, //upwards double arrow, U+21D1 ISOamsa --> 327 {"rArr", "8658"}, //rightwards double arrow,U+21D2 ISOtech --> 328 // <!-- ISO 10646 does not say this is the 'implies' character but does not 329 // have another character with this function so ?rArr can be used for 330 // 'implies' as ISOtech suggests --> 331 {"dArr", "8659"}, //downwards double arrow, U+21D3 ISOamsa --> 332 {"hArr", "8660"}, //left right double arrow,U+21D4 ISOamsa --> 333 // <!-- Mathematical Operators --> 334 {"forall", "8704"}, //for all, U+2200 ISOtech --> 335 {"part", "8706"}, //partial differential, U+2202 ISOtech --> 336 {"exist", "8707"}, //there exists, U+2203 ISOtech --> 337 {"empty", "8709"}, //empty set = null set = diameter,U+2205 ISOamso --> 338 {"nabla", "8711"}, //nabla = backward difference,U+2207 ISOtech --> 339 {"isin", "8712"}, //element of, U+2208 ISOtech --> 340 {"notin", "8713"}, //not an element of, U+2209 ISOtech --> 341 {"ni", "8715"}, //contains as member, U+220B ISOtech --> 342 // <!-- should there be a more memorable name than 'ni'? --> 343 {"prod", "8719"}, //n-ary product = product sign,U+220F ISOamsb --> 344 // <!-- prod is NOT the same character as U+03A0 'greek capital letter pi' 345 // though the same glyph might be used for both --> 346 {"sum", "8721"}, //n-ary summation, U+2211 ISOamsb --> 347 // <!-- sum is NOT the same character as U+03A3 'greek capital letter sigma' 348 // though the same glyph might be used for both --> 349 {"minus", "8722"}, //minus sign, U+2212 ISOtech --> 350 {"lowast", "8727"}, //asterisk operator, U+2217 ISOtech --> 351 {"radic", "8730"}, //square root = radical sign,U+221A ISOtech --> 352 {"prop", "8733"}, //proportional to, U+221D ISOtech --> 353 {"infin", "8734"}, //infinity, U+221E ISOtech --> 354 {"ang", "8736"}, //angle, U+2220 ISOamso --> 355 {"and", "8743"}, //logical and = wedge, U+2227 ISOtech --> 356 {"or", "8744"}, //logical or = vee, U+2228 ISOtech --> 357 {"cap", "8745"}, //intersection = cap, U+2229 ISOtech --> 358 {"cup", "8746"}, //union = cup, U+222A ISOtech --> 359 {"int", "8747"}, //integral, U+222B ISOtech --> 360 {"there4", "8756"}, //therefore, U+2234 ISOtech --> 361 {"sim", "8764"}, //tilde operator = varies with = similar to,U+223C ISOtech --> 362 // <!-- tilde operator is NOT the same character as the tilde, U+007E,although 363 // the same glyph might be used to represent both --> 364 {"cong", "8773"}, //approximately equal to, U+2245 ISOtech --> 365 {"asymp", "8776"}, //almost equal to = asymptotic to,U+2248 ISOamsr --> 366 {"ne", "8800"}, //not equal to, U+2260 ISOtech --> 367 {"equiv", "8801"}, //identical to, U+2261 ISOtech --> 368 {"le", "8804"}, //less-than or equal to, U+2264 ISOtech --> 369 {"ge", "8805"}, //greater-than or equal to,U+2265 ISOtech --> 370 {"sub", "8834"}, //subset of, U+2282 ISOtech --> 371 {"sup", "8835"}, //superset of, U+2283 ISOtech --> 372 // <!-- note that nsup, 'not a superset of, U+2283' is not covered by the 373 // Symbol font encoding and is not included. Should it be, for symmetry? 374 // It is in ISOamsn --> <!ENTITY nsub", "8836"}, 375 // not a subset of, U+2284 ISOamsn --> 376 {"sube", "8838"}, //subset of or equal to, U+2286 ISOtech --> 377 {"supe", "8839"}, //superset of or equal to,U+2287 ISOtech --> 378 {"oplus", "8853"}, //circled plus = direct sum,U+2295 ISOamsb --> 379 {"otimes", "8855"}, //circled times = vector product,U+2297 ISOamsb --> 380 {"perp", "8869"}, //up tack = orthogonal to = perpendicular,U+22A5 ISOtech --> 381 {"sdot", "8901"}, //dot operator, U+22C5 ISOamsb --> 382 // <!-- dot operator is NOT the same character as U+00B7 middle dot --> 383 // <!-- Miscellaneous Technical --> 384 {"lceil", "8968"}, //left ceiling = apl upstile,U+2308 ISOamsc --> 385 {"rceil", "8969"}, //right ceiling, U+2309 ISOamsc --> 386 {"lfloor", "8970"}, //left floor = apl downstile,U+230A ISOamsc --> 387 {"rfloor", "8971"}, //right floor, U+230B ISOamsc --> 388 {"lang", "9001"}, //left-pointing angle bracket = bra,U+2329 ISOtech --> 389 // <!-- lang is NOT the same character as U+003C 'less than' or U+2039 'single left-pointing angle quotation mark' --> 390 {"rang", "9002"}, //right-pointing angle bracket = ket,U+232A ISOtech --> 391 // <!-- rang is NOT the same character as U+003E 'greater than' or U+203A 392 // 'single right-pointing angle quotation mark' --> 393 // <!-- Geometric Shapes --> 394 {"loz", "9674"}, //lozenge, U+25CA ISOpub --> 395 // <!-- Miscellaneous Symbols --> 396 {"spades", "9824"}, //black spade suit, U+2660 ISOpub --> 397 // <!-- black here seems to mean filled as opposed to hollow --> 398 {"clubs", "9827"}, //black club suit = shamrock,U+2663 ISOpub --> 399 {"hearts", "9829"}, //black heart suit = valentine,U+2665 ISOpub --> 400 {"diams", "9830"}, //black diamond suit, U+2666 ISOpub --> 401 402 // <!-- Latin Extended-A --> 403 {"OElig", "338"}, // -- latin capital ligature OE,U+0152 ISOlat2 --> 404 {"oelig", "339"}, // -- latin small ligature oe, U+0153 ISOlat2 --> 405 // <!-- ligature is a misnomer, this is a separate character in some languages --> 406 {"Scaron", "352"}, // -- latin capital letter S with caron,U+0160 ISOlat2 --> 407 {"scaron", "353"}, // -- latin small letter s with caron,U+0161 ISOlat2 --> 408 {"Yuml", "376"}, // -- latin capital letter Y with diaeresis,U+0178 ISOlat2 --> 409 // <!-- Spacing Modifier Letters --> 410 {"circ", "710"}, // -- modifier letter circumflex accent,U+02C6 ISOpub --> 411 {"tilde", "732"}, //small tilde, U+02DC ISOdia --> 412 // <!-- General Punctuation --> 413 {"ensp", "8194"}, //en space, U+2002 ISOpub --> 414 {"emsp", "8195"}, //em space, U+2003 ISOpub --> 415 {"thinsp", "8201"}, //thin space, U+2009 ISOpub --> 416 {"zwnj", "8204"}, //zero width non-joiner,U+200C NEW RFC 2070 --> 417 {"zwj", "8205"}, //zero width joiner, U+200D NEW RFC 2070 --> 418 {"lrm", "8206"}, //left-to-right mark, U+200E NEW RFC 2070 --> 419 {"rlm", "8207"}, //right-to-left mark, U+200F NEW RFC 2070 --> 420 {"ndash", "8211"}, //en dash, U+2013 ISOpub --> 421 {"mdash", "8212"}, //em dash, U+2014 ISOpub --> 422 {"lsquo", "8216"}, //left single quotation mark,U+2018 ISOnum --> 423 {"rsquo", "8217"}, //right single quotation mark,U+2019 ISOnum --> 424 {"sbquo", "8218"}, //single low-9 quotation mark, U+201A NEW --> 425 {"ldquo", "8220"}, //left double quotation mark,U+201C ISOnum --> 426 {"rdquo", "8221"}, //right double quotation mark,U+201D ISOnum --> 427 {"bdquo", "8222"}, //double low-9 quotation mark, U+201E NEW --> 428 {"dagger", "8224"}, //dagger, U+2020 ISOpub --> 429 {"Dagger", "8225"}, //double dagger, U+2021 ISOpub --> 430 {"permil", "8240"}, //per mille sign, U+2030 ISOtech --> 431 {"lsaquo", "8249"}, //single left-pointing angle quotation mark,U+2039 ISO proposed --> 432 // <!-- lsaquo is proposed but not yet ISO standardized --> 433 {"rsaquo", "8250"}, //single right-pointing angle quotation mark,U+203A ISO proposed --> 434 // <!-- rsaquo is proposed but not yet ISO standardized --> 435 {"euro", "8364"}, // -- euro sign, U+20AC NEW --> 436 }; 437 438 // package scoped for testing 439 private EntityMap map = new Entities.LookupEntityMap(); 440 441 /** 442 * <p> 443 * Adds entities to this entity. 444 * </p> 445 * 446 * @param entityArray array of entities to be added 447 */ 448 public void addEntities(String[][] entityArray) 449 { 450 for (int i = 0; i < entityArray.length; ++i) 451 { 452 addEntity(entityArray[i][0], Integer.parseInt(entityArray[i][1])); 453 } 454 } 455 456 /** 457 * <p> 458 * Add an entity to this entity. 459 * </p> 460 * 461 * @param name name of the entity 462 * @param value vale of the entity 463 */ 464 public void addEntity(String name, int value) 465 { 466 map.add(name, value); 467 } 468 469 /** 470 * <p> 471 * Returns the name of the entity identified by the specified value. 472 * </p> 473 * 474 * @param value the value to locate 475 * @return entity name associated with the specified value 476 */ 477 public String entityName(int value) 478 { 479 return map.name(value); 480 } 481 482 /** 483 * <p> 484 * Returns the value of the entity identified by the specified name. 485 * </p> 486 * 487 * @param name the name to locate 488 * @return entity value associated with the specified name 489 */ 490 public int entityValue(String name) 491 { 492 return map.value(name); 493 } 494 495 /** 496 * <p> 497 * Escapes the characters in a <code>String</code>. 498 * </p> 499 * <p> 500 * For example, if you have called addEntity("foo", 0xA1), 501 * escape("\u00A1") will return "&foo;" 502 * </p> 503 * 504 * @param str The <code>String</code> to escape. 505 * @return A new escaped <code>String</code>. 506 */ 507 public String escape(String str) 508 { 509 // TODO: rewrite to use a Writer 510 StringBuffer buf = new StringBuffer(str.length() * 2); 511 for (int i = 0; i < str.length(); ++i) 512 { 513 char ch = str.charAt(i); 514 String entityName = this.entityName(ch); 515 if (entityName == null) 516 { 517 if (ch > 0x7F) 518 { 519 buf.append('&'); 520 buf.append('#'); 521 buf.append((int) ch); 522 buf.append(';'); 523 } 524 else 525 { 526 buf.append(ch); 527 } 528 } 529 else 530 { 531 buf.append('&'); 532 buf.append(entityName); 533 buf.append(';'); 534 } 535 } 536 return buf.toString(); 537 } 538 539 /** 540 * <p> 541 * Escapes the characters in the <code>String</code> passed and writes the 542 * result to the <code>Writer</code> passed. 543 * </p> 544 * 545 * @param writer The <code>Writer</code> to write the results of the 546 * escaping to. Assumed to be a non-null value. 547 * @param str The <code>String</code> to escape. Assumed to be a non-null 548 * value. 549 * @throws IOException when <code>Writer</code> passed throws the exception 550 * from calls to the {@link Writer#write(int)} methods. 551 * @see #escape(String) 552 * @see Writer 553 */ 554 public void escape(Writer writer, String str) throws IOException 555 { 556 int len = str.length(); 557 for (int i = 0; i < len; i++) 558 { 559 char c = str.charAt(i); 560 String entityName = this.entityName(c); 561 if (entityName == null) 562 { 563 if (c > 0x7F) 564 { 565 writer.write("&#"); 566 writer.write(Integer.toString(c, 10)); 567 writer.write(';'); 568 } 569 else 570 { 571 writer.write(c); 572 } 573 } 574 else 575 { 576 writer.write('&'); 577 writer.write(entityName); 578 writer.write(';'); 579 } 580 } 581 } 582 583 /** 584 * <p> 585 * Unescapes the entities in a <code>String</code>. 586 * </p> 587 * <p> 588 * For example, if you have called addEntity("foo", 0xA1), 589 * unescape("&foo;") will return "\u00A1" 590 * </p> 591 * 592 * @param str The <code>String</code> to escape. 593 * @return A new escaped <code>String</code> or str itself if no unescaping 594 * was necessary. 595 */ 596 public String unescape(String str) 597 { 598 int firstAmp = str.indexOf('&'); 599 if (firstAmp < 0) 600 { 601 return str; 602 } 603 604 StringBuffer buf = new StringBuffer(str.length()); 605 buf.append(str.substring(0, firstAmp)); 606 for (int i = firstAmp; i < str.length(); ++i) 607 { 608 char ch = str.charAt(i); 609 if (ch == '&') 610 { 611 int semi = str.indexOf(';', i + 1); 612 if (semi == -1) 613 { 614 buf.append(ch); 615 continue; 616 } 617 int amph = str.indexOf('&', i + 1); 618 if (amph != -1 && amph < semi) 619 { 620 // Then the text looks like &...&...; 621 buf.append(ch); 622 continue; 623 } 624 String entityName = str.substring(i + 1, semi); 625 int entityValue; 626 if (entityName.length() == 0) 627 { 628 entityValue = -1; 629 } 630 else if (entityName.charAt(0) == '#') 631 { 632 if (entityName.length() == 1) 633 { 634 entityValue = -1; 635 } 636 else 637 { 638 char charAt1 = entityName.charAt(1); 639 try 640 { 641 if (charAt1 == 'x' || charAt1 == 'X') 642 { 643 entityValue = Integer.valueOf(entityName.substring(2), 16).intValue(); 644 } 645 else 646 { 647 entityValue = Integer.parseInt(entityName.substring(1)); 648 } 649 if (entityValue > 0xFFFF) 650 { 651 entityValue = -1; 652 } 653 } 654 catch (NumberFormatException ex) 655 { 656 entityValue = -1; 657 } 658 } 659 } 660 else 661 { 662 entityValue = this.entityValue(entityName); 663 } 664 if (entityValue == -1) 665 { 666 buf.append('&'); 667 buf.append(entityName); 668 buf.append(';'); 669 } 670 else 671 { 672 buf.append((char) (entityValue)); 673 } 674 i = semi; 675 } 676 else 677 { 678 buf.append(ch); 679 } 680 } 681 return buf.toString(); 682 } 683 684 /** 685 * <p> 686 * Unescapes the escaped entities in the <code>String</code> passed and 687 * writes the result to the <code>Writer</code> passed. 688 * </p> 689 * 690 * @param writer The <code>Writer</code> to write the results to; assumed 691 * to be non-null. 692 * @param string The <code>String</code> to write the results to; assumed 693 * to be non-null. 694 * @throws IOException when <code>Writer</code> passed throws the exception 695 * from calls to the {@link Writer#write(int)} methods. 696 * @see #escape(String) 697 * @see Writer 698 */ 699 public void unescape(Writer writer, String string) throws IOException 700 { 701 int firstAmp = string.indexOf('&'); 702 if (firstAmp < 0) 703 { 704 writer.write(string); 705 return; 706 } 707 708 writer.write(string, 0, firstAmp); 709 int len = string.length(); 710 for (int i = firstAmp; i < len; i++) 711 { 712 char c = string.charAt(i); 713 if (c == '&') 714 { 715 int nextIdx = i + 1; 716 int semiColonIdx = string.indexOf(';', nextIdx); 717 if (semiColonIdx == -1) 718 { 719 writer.write(c); 720 continue; 721 } 722 int amphersandIdx = string.indexOf('&', i + 1); 723 if (amphersandIdx != -1 && amphersandIdx < semiColonIdx) 724 { 725 // Then the text looks like &...&...; 726 writer.write(c); 727 continue; 728 } 729 String entityContent = string.substring(nextIdx, semiColonIdx); 730 int entityValue = -1; 731 int entityContentLen = entityContent.length(); 732 if (entityContentLen > 0) 733 { 734 if (entityContent.charAt(0) == '#') 735 { // escaped value content is an integer (decimal or 736 // hexidecimal) 737 if (entityContentLen > 1) 738 { 739 char isHexChar = entityContent.charAt(1); 740 try 741 { 742 switch (isHexChar) 743 { 744 case 'X' : 745 case 'x' : 746 entityValue = Integer.parseInt(entityContent.substring(2), 16); 747 break; 748 default : 749 entityValue = Integer.parseInt(entityContent.substring(1), 10); 750 } 751 if (entityValue > 0xFFFF) 752 { 753 entityValue = -1; 754 } 755 } 756 catch (NumberFormatException e) 757 { 758 entityValue = -1; 759 } 760 } 761 } 762 else 763 { // escaped value content is an entity name 764 entityValue = this.entityValue(entityContent); 765 } 766 } 767 768 if (entityValue == -1) 769 { 770 writer.write('&'); 771 writer.write(entityContent); 772 writer.write(';'); 773 } 774 else 775 { 776 writer.write(entityValue); 777 } 778 i = semiColonIdx; // move index up to the semi-colon 779 } 780 else 781 { 782 writer.write(c); 783 } 784 } 785 } 786 787 private static interface EntityMap 788 { 789 /** 790 * <p> 791 * Add an entry to this entity map. 792 * </p> 793 * 794 * @param name the entity name 795 * @param value the entity value 796 */ 797 void add(String name, int value); 798 799 /** 800 * <p> 801 * Returns the name of the entity identified by the specified value. 802 * </p> 803 * 804 * @param value the value to locate 805 * @return entity name associated with the specified value 806 */ 807 String name(int value); 808 809 /** 810 * <p> 811 * Returns the value of the entity identified by the specified name. 812 * </p> 813 * 814 * @param name the name to locate 815 * @return entity value associated with the specified name 816 */ 817 int value(String name); 818 } 819 820 private static class PrimitiveEntityMap implements EntityMap 821 { 822 private Map mapNameToValue = new HashMap(); 823 private IntHashMap mapValueToName = new IntHashMap(); 824 825 /** 826 * {@inheritDoc} 827 */ 828 public void add(String name, int value) 829 { 830 mapNameToValue.put(name, new Integer(value)); 831 mapValueToName.put(value, name); 832 } 833 834 /** 835 * {@inheritDoc} 836 */ 837 public String name(int value) 838 { 839 return (String) mapValueToName.get(value); 840 } 841 842 /** 843 * {@inheritDoc} 844 */ 845 public int value(String name) 846 { 847 Object value = mapNameToValue.get(name); 848 if (value == null) 849 { 850 return -1; 851 } 852 return ((Integer) value).intValue(); 853 } 854 } 855 856 private static class LookupEntityMap extends PrimitiveEntityMap 857 { 858 private static final int LOOKUP_TABLE_SIZE = 256; 859 private String[] lookupTable; 860 861 /** 862 * {@inheritDoc} 863 */ 864 public String name(int value) 865 { 866 if (value < LOOKUP_TABLE_SIZE) 867 { 868 return lookupTable()[value]; 869 } 870 return super.name(value); 871 } 872 873 /** 874 * <p> 875 * Returns the lookup table for this entity map. The lookup table is 876 * created if it has not been previously. 877 * </p> 878 * 879 * @return the lookup table 880 */ 881 private String[] lookupTable() 882 { 883 if (lookupTable == null) 884 { 885 createLookupTable(); 886 } 887 return lookupTable; 888 } 889 890 /** 891 * <p> 892 * Creates an entity lookup table of LOOKUP_TABLE_SIZE elements, 893 * initialized with entity names. 894 * </p> 895 */ 896 private void createLookupTable() 897 { 898 lookupTable = new String[LOOKUP_TABLE_SIZE]; 899 for (int i = 0; i < LOOKUP_TABLE_SIZE; ++i) 900 { 901 lookupTable[i] = super.name(i); 902 } 903 } 904 } 905 906 /** 907 * <p> 908 * A hash map that uses primitive ints for the key rather than objects. 909 * </p> 910 * <p> 911 * Note that this class is for internal optimization purposes only, and may 912 * not be supported in future releases of Jakarta Commons Lang. Utilities of 913 * this sort may be included in future releases of Jakarta Commons 914 * Collections. 915 * </p> 916 * 917 * @author Justin Couch 918 * @author Alex Chaffee (alex@apache.org) 919 * @author Stephen Colebourne 920 * @since 2.0 921 * @version $Revision$ 922 * @see java.util.HashMap 923 */ 924 private static class IntHashMap 925 { 926 927 /** 928 * The hash table data. 929 */ 930 private transient Entry table[]; 931 932 /** 933 * The total number of entries in the hash table. 934 */ 935 private transient int count; 936 937 /** 938 * The table is rehashed when its size exceeds this threshold. (The value 939 * of this field is (int)(capacity * loadFactor).) 940 * 941 * @serial 942 */ 943 private int threshold; 944 945 /** 946 * The load factor for the hashtable. 947 * 948 * @serial 949 */ 950 private float loadFactor; 951 952 /** 953 * <p> 954 * Innerclass that acts as a datastructure to create a new entry in the 955 * table. 956 * </p> 957 */ 958 private static class Entry 959 { 960 int hash; 961 int key; 962 Object value; 963 Entry next; 964 965 /** 966 * <p> 967 * Create a new entry with the given values. 968 * </p> 969 * 970 * @param hash The code used to hash the object with 971 * @param key The key used to enter this in the table 972 * @param value The value for this key 973 * @param next A reference to the next entry in the table 974 */ 975 protected Entry(int hash, int key, Object value, Entry next) 976 { 977 this.hash = hash; 978 this.key = key; 979 this.value = value; 980 this.next = next; 981 } 982 } 983 984 /** 985 * <p> 986 * Constructs a new, empty hashtable with a default capacity and load 987 * factor, which is <code>20</code> and <code>0.75</code> 988 * respectively. 989 * </p> 990 */ 991 public IntHashMap() 992 { 993 this(20, 0.75f); 994 } 995 996 /** 997 * <p> 998 * Constructs a new, empty hashtable with the specified initial capacity 999 * and default load factor, which is <code>0.75</code>. 1000 * </p> 1001 * 1002 * @param initialCapacity the initial capacity of the hashtable. 1003 * @throws IllegalArgumentException if the initial capacity is less than 1004 * zero. 1005 */ 1006 public IntHashMap(int initialCapacity) 1007 { 1008 this(initialCapacity, 0.75f); 1009 } 1010 1011 /** 1012 * <p> 1013 * Constructs a new, empty hashtable with the specified initial capacity 1014 * and the specified load factor. 1015 * </p> 1016 * 1017 * @param initialCapacity the initial capacity of the hashtable. 1018 * @param loadFactor the load factor of the hashtable. 1019 * @throws IllegalArgumentException if the initial capacity is less than 1020 * zero, or if the load factor is nonpositive. 1021 */ 1022 public IntHashMap(int initialCapacity, float loadFactor) 1023 { 1024 super(); 1025 if (initialCapacity < 0) 1026 { 1027 throw new IllegalArgumentException("Illegal Capacity: " + initialCapacity); 1028 } 1029 if (loadFactor <= 0) 1030 { 1031 throw new IllegalArgumentException("Illegal Load: " + loadFactor); 1032 } 1033 if (initialCapacity == 0) 1034 { 1035 initialCapacity = 1; 1036 } 1037 1038 this.loadFactor = loadFactor; 1039 table = new Entry[initialCapacity]; 1040 threshold = (int) (initialCapacity * loadFactor); 1041 } 1042 1043 /** 1044 * <p> 1045 * Returns the number of keys in this hashtable. 1046 * </p> 1047 * 1048 * @return the number of keys in this hashtable. 1049 */ 1050 public int size() 1051 { 1052 return count; 1053 } 1054 1055 /** 1056 * <p> 1057 * Tests if this hashtable maps no keys to values. 1058 * </p> 1059 * 1060 * @return <code>true</code> if this hashtable maps no keys to values; 1061 * <code>false</code> otherwise. 1062 */ 1063 public boolean isEmpty() 1064 { 1065 return count == 0; 1066 } 1067 1068 /** 1069 * <p> 1070 * Tests if some key maps into the specified value in this hashtable. 1071 * This operation is more expensive than the <code>containsKey</code> 1072 * method. 1073 * </p> 1074 * <p> 1075 * Note that this method is identical in functionality to containsValue, 1076 * (which is part of the Map interface in the collections framework). 1077 * </p> 1078 * 1079 * @param value a value to search for. 1080 * @return <code>true</code> if and only if some key maps to the 1081 * <code>value</code> argument in this hashtable as determined 1082 * by the <tt>equals</tt> method; <code>false</code> 1083 * otherwise. 1084 * @throws NullPointerException if the value is <code>null</code>. 1085 * @see #containsKey(int) 1086 * @see #containsValue(Object) 1087 * @see java.util.Map 1088 */ 1089 public boolean contains(Object value) 1090 { 1091 if (value == null) 1092 { 1093 throw new IllegalArgumentException("parameter value may not be null"); 1094 } 1095 1096 Entry tab[] = table; 1097 for (int i = tab.length; i-- > 0;) 1098 { 1099 for (Entry e = tab[i]; e != null; e = e.next) 1100 { 1101 if (e.value.equals(value)) 1102 { 1103 return true; 1104 } 1105 } 1106 } 1107 return false; 1108 } 1109 1110 /** 1111 * <p> 1112 * Returns <code>true</code> if this HashMap maps one or more keys to 1113 * this value. 1114 * </p> 1115 * <p> 1116 * Note that this method is identical in functionality to contains (which 1117 * predates the Map interface). 1118 * </p> 1119 * 1120 * @param value value whose presence in this HashMap is to be tested. 1121 * @return boolean <code>true</code> if the value is contained 1122 * @see java.util.Map 1123 * @since JDK1.2 1124 */ 1125 public boolean containsValue(Object value) 1126 { 1127 return contains(value); 1128 } 1129 1130 /** 1131 * <p> 1132 * Tests if the specified object is a key in this hashtable. 1133 * </p> 1134 * 1135 * @param key possible key. 1136 * @return <code>true</code> if and only if the specified object is a 1137 * key in this hashtable, as determined by the <tt>equals</tt> 1138 * method; <code>false</code> otherwise. 1139 * @see #contains(Object) 1140 */ 1141 public boolean containsKey(int key) 1142 { 1143 Entry tab[] = table; 1144 int hash = key; 1145 int index = (hash & 0x7FFFFFFF) % tab.length; 1146 for (Entry e = tab[index]; e != null; e = e.next) 1147 { 1148 if (e.hash == hash) 1149 { 1150 return true; 1151 } 1152 } 1153 return false; 1154 } 1155 1156 /** 1157 * <p> 1158 * Returns the value to which the specified key is mapped in this map. 1159 * </p> 1160 * 1161 * @param key a key in the hashtable. 1162 * @return the value to which the key is mapped in this hashtable; 1163 * <code>null</code> if the key is not mapped to any value in 1164 * this hashtable. 1165 * @see #put(int, Object) 1166 */ 1167 public Object get(int key) 1168 { 1169 Entry tab[] = table; 1170 int hash = key; 1171 int index = (hash & 0x7FFFFFFF) % tab.length; 1172 for (Entry e = tab[index]; e != null; e = e.next) 1173 { 1174 if (e.hash == hash) 1175 { 1176 return e.value; 1177 } 1178 } 1179 return null; 1180 } 1181 1182 /** 1183 * <p> 1184 * Increases the capacity of and internally reorganizes this hashtable, 1185 * in order to accommodate and access its entries more efficiently. 1186 * </p> 1187 * <p> 1188 * This method is called automatically when the number of keys in the 1189 * hashtable exceeds this hashtable's capacity and load factor. 1190 * </p> 1191 */ 1192 protected void rehash() 1193 { 1194 int oldCapacity = table.length; 1195 Entry oldMap[] = table; 1196 1197 int newCapacity = oldCapacity * 2 + 1; 1198 Entry newMap[] = new Entry[newCapacity]; 1199 1200 threshold = (int) (newCapacity * loadFactor); 1201 table = newMap; 1202 1203 for (int i = oldCapacity; i-- > 0;) 1204 { 1205 for (Entry old = oldMap[i]; old != null;) 1206 { 1207 Entry e = old; 1208 old = old.next; 1209 1210 int index = (e.hash & 0x7FFFFFFF) % newCapacity; 1211 e.next = newMap[index]; 1212 newMap[index] = e; 1213 } 1214 } 1215 } 1216 1217 /** 1218 * <p> 1219 * Maps the specified <code>key</code> to the specified 1220 * <code>value</code> in this hashtable. The key cannot be 1221 * <code>null</code>. 1222 * </p> 1223 * <p> 1224 * The value can be retrieved by calling the <code>get</code> method 1225 * with a key that is equal to the original key. 1226 * </p> 1227 * 1228 * @param key the hashtable key. 1229 * @param value the value. 1230 * @return the previous value of the specified key in this hashtable, or 1231 * <code>null</code> if it did not have one. 1232 * @throws NullPointerException if the key is <code>null</code>. 1233 * @see #get(int) 1234 */ 1235 public Object put(int key, Object value) 1236 { 1237 // Makes sure the key is not already in the hashtable. 1238 Entry tab[] = table; 1239 int hash = key; 1240 int index = (hash & 0x7FFFFFFF) % tab.length; 1241 for (Entry e = tab[index]; e != null; e = e.next) 1242 { 1243 if (e.hash == hash) 1244 { 1245 Object old = e.value; 1246 e.value = value; 1247 return old; 1248 } 1249 } 1250 1251 if (count >= threshold) 1252 { 1253 // Rehash the table if the threshold is exceeded 1254 rehash(); 1255 1256 tab = table; 1257 index = (hash & 0x7FFFFFFF) % tab.length; 1258 } 1259 1260 // Creates the new entry. 1261 Entry e = new Entry(hash, key, value, tab[index]); 1262 tab[index] = e; 1263 count++; 1264 return null; 1265 } 1266 1267 /** 1268 * <p> 1269 * Removes the key (and its corresponding value) from this hashtable. 1270 * </p> 1271 * <p> 1272 * This method does nothing if the key is not present in the hashtable. 1273 * </p> 1274 * 1275 * @param key the key that needs to be removed. 1276 * @return the value to which the key had been mapped in this hashtable, 1277 * or <code>null</code> if the key did not have a mapping. 1278 */ 1279 public Object remove(int key) 1280 { 1281 Entry tab[] = table; 1282 int hash = key; 1283 int index = (hash & 0x7FFFFFFF) % tab.length; 1284 for (Entry e = tab[index], prev = null; e != null; prev = e, e = e.next) 1285 { 1286 if (e.hash == hash) 1287 { 1288 if (prev != null) 1289 { 1290 prev.next = e.next; 1291 } 1292 else 1293 { 1294 tab[index] = e.next; 1295 } 1296 count--; 1297 Object oldValue = e.value; 1298 e.value = null; 1299 return oldValue; 1300 } 1301 } 1302 return null; 1303 } 1304 1305 /** 1306 * <p>Clears this hashtable so that it contains no keys.</p> 1307 */ 1308 public synchronized void clear() 1309 { 1310 Entry tab[] = table; 1311 for (int index = tab.length; --index >= 0;) 1312 { 1313 tab[index] = null; 1314 } 1315 count = 0; 1316 } 1317 1318 } 1319 1320 } 1321 }