/******************************************************************************* * Copyright (c) 2010-2011 VIVO Harvester Team. For full list of contributors, please see the AUTHORS file provided. * All rights reserved. * This program and the accompanying materials are made available under the terms of the new BSD license which accompanies this distribution, and is available at http://www.opensource.org/licenses/bsd-license.html ******************************************************************************/ package org.vivoweb.harvester.util; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * @author Christopher Haines (hainesc@ctrip.ufl.edu) */ public class SpecialEntities { /** * html encode mapping */ private static Map<String, String> htmlEncode = null; /** * html decode mapping */ private static Map<String, String> htmlDecode = null; /** * xml encode mapping */ private static Map<String, String> xmlEncode = null; /** * xml decode mapping */ private static Map<String, String> xmlDecode = null; /** * list of accepted characters */ private static List<Character> acceptedChars = null; /** * Gets the html encode mapping * @return html encode mapping */ private static Map<String, String> getHtmlEncode() { if(htmlEncode == null) { htmlEncode = new HashMap<String, String>(); htmlEncode.put(" ", " "); htmlEncode.put(""", """); htmlEncode.put("&", "&"); htmlEncode.put("'", "'"); htmlEncode.put("<", "<"); htmlEncode.put(">", ">"); htmlEncode.put(" ", " ");// be carefull with this one (non-breaking white space) htmlEncode.put("¡", "¡"); htmlEncode.put("¢", "¢"); htmlEncode.put("£", "£"); htmlEncode.put("¤", "¤"); htmlEncode.put("¥", "¥"); htmlEncode.put("¦", "¦"); htmlEncode.put("§", "§"); htmlEncode.put("¨", "¨"); htmlEncode.put("©", "©"); htmlEncode.put("ª", "ª"); htmlEncode.put("«", "«"); htmlEncode.put("¬", "¬"); htmlEncode.put("­", "­"); htmlEncode.put("®", "®"); htmlEncode.put("¯", "¯"); htmlEncode.put("°", "°"); htmlEncode.put("±", "±"); htmlEncode.put("²", "²"); htmlEncode.put("³", "³"); htmlEncode.put("´", "´"); htmlEncode.put("µ", "µ"); htmlEncode.put("¶", "¶"); htmlEncode.put("·", "·"); htmlEncode.put("¸", "¸"); htmlEncode.put("¹", "¹"); htmlEncode.put("º", "º"); htmlEncode.put("»", "»"); htmlEncode.put("¼", "¼"); htmlEncode.put("½", "½"); htmlEncode.put("¾", "¾"); htmlEncode.put("¿", "¿"); htmlEncode.put("À", "À"); htmlEncode.put("Á", "Á"); htmlEncode.put("Â", "Â"); htmlEncode.put("Ã", "Ã"); htmlEncode.put("Ä", "Ä"); htmlEncode.put("Å", "Å"); htmlEncode.put("Æ", "Æ"); htmlEncode.put("Ç", "Ç"); htmlEncode.put("È", "È"); htmlEncode.put("É", "É"); htmlEncode.put("Ê", "Ê"); htmlEncode.put("Ë", "Ë"); htmlEncode.put("Ì", "Ì"); htmlEncode.put("Í", "Í"); htmlEncode.put("Î", "Î"); htmlEncode.put("Ï", "Ï"); htmlEncode.put("Ð", "Ð"); htmlEncode.put("Ñ", "Ñ"); htmlEncode.put("Ò", "Ò"); htmlEncode.put("Ó", "Ó"); htmlEncode.put("Ô", "Ô"); htmlEncode.put("Õ", "Õ"); htmlEncode.put("Ö", "Ö"); htmlEncode.put("×", "×"); htmlEncode.put("Ø", "Ø"); htmlEncode.put("Ù", "Ù"); htmlEncode.put("Ú", "Ú"); htmlEncode.put("Û", "Û"); htmlEncode.put("Ü", "Ü"); htmlEncode.put("Ý", "Ý"); htmlEncode.put("Þ", "Þ"); htmlEncode.put("ß", "ß"); htmlEncode.put("à", "à"); htmlEncode.put("á", "á"); htmlEncode.put("â", "â"); htmlEncode.put("ã", "ã"); htmlEncode.put("ä", "ä"); htmlEncode.put("å", "å"); htmlEncode.put("æ", "æ"); htmlEncode.put("ç", "ç"); htmlEncode.put("è", "è"); htmlEncode.put("é", "é"); htmlEncode.put("ê", "ê"); htmlEncode.put("ë", "ë"); htmlEncode.put("ì", "ì"); htmlEncode.put("í", "í"); htmlEncode.put("î", "î"); htmlEncode.put("ï", "ï"); htmlEncode.put("ð", "ð"); htmlEncode.put("ñ", "ñ"); htmlEncode.put("ò", "ò"); htmlEncode.put("ó", "ó"); htmlEncode.put("ô", "ô"); htmlEncode.put("õ", "õ"); htmlEncode.put("ö", "ö"); htmlEncode.put("÷", "÷"); htmlEncode.put("ø", "ø"); htmlEncode.put("ù", "ù"); htmlEncode.put("ú", "ú"); htmlEncode.put("û", "û"); htmlEncode.put("ü", "ü"); htmlEncode.put("ý", "ý"); htmlEncode.put("þ", "þ"); htmlEncode.put("ÿ", "ÿ"); htmlEncode.put("Œ", "Œ"); htmlEncode.put("œ", "œ"); htmlEncode.put("Š", "Š"); htmlEncode.put("š", "š"); htmlEncode.put("Ÿ", "Ÿ"); htmlEncode.put("ƒ", "ƒ"); htmlEncode.put("ˆ", "ˆ"); htmlEncode.put("˜", "˜"); htmlEncode.put("Α", "Α"); htmlEncode.put("Β", "Β"); htmlEncode.put("Γ", "Γ"); htmlEncode.put("Δ", "Δ"); htmlEncode.put("Ε", "Ε"); htmlEncode.put("Ζ", "Ζ"); htmlEncode.put("Η", "Η"); htmlEncode.put("Θ", "Θ"); htmlEncode.put("Ι", "Ι"); htmlEncode.put("Κ", "Κ"); htmlEncode.put("Λ", "Λ"); htmlEncode.put("Μ", "Μ"); htmlEncode.put("Ν", "Ν"); htmlEncode.put("Ξ", "Ξ"); htmlEncode.put("Ο", "Ο"); htmlEncode.put("Π", "Π"); htmlEncode.put("Ρ", "Ρ"); htmlEncode.put("Σ", "Σ"); htmlEncode.put("Τ", "Τ"); htmlEncode.put("Υ", "Υ"); htmlEncode.put("Φ", "Φ"); htmlEncode.put("Χ", "Χ"); htmlEncode.put("Ψ", "Ψ"); htmlEncode.put("Ω", "Ω"); htmlEncode.put("α", "α"); htmlEncode.put("β", "β"); htmlEncode.put("γ", "γ"); htmlEncode.put("δ", "δ"); htmlEncode.put("ε", "ε"); htmlEncode.put("ζ", "ζ"); htmlEncode.put("η", "η"); htmlEncode.put("θ", "θ"); htmlEncode.put("ι", "ι"); htmlEncode.put("κ", "κ"); htmlEncode.put("λ", "λ"); htmlEncode.put("μ", "μ"); htmlEncode.put("ν", "ν"); htmlEncode.put("ξ", "ξ"); htmlEncode.put("ο", "ο"); htmlEncode.put("π", "π"); htmlEncode.put("ρ", "ρ"); htmlEncode.put("ς", "ς"); htmlEncode.put("σ", "σ"); htmlEncode.put("τ", "τ"); htmlEncode.put("υ", "υ"); htmlEncode.put("φ", "φ"); htmlEncode.put("χ", "χ"); htmlEncode.put("ψ", "ψ"); htmlEncode.put("ω", "ω"); htmlEncode.put("ϑ", "ϑ"); htmlEncode.put("ϒ", "ϒ"); htmlEncode.put("ϖ", "ϖ"); htmlEncode.put(" ", " "); htmlEncode.put(" ", " "); htmlEncode.put(" ", " "); htmlEncode.put("‌", "‌"); htmlEncode.put("‍", "‍"); htmlEncode.put("‎", "‎"); htmlEncode.put("‏", "‏"); htmlEncode.put("–", "–"); htmlEncode.put("—", "—"); htmlEncode.put("‘", "‘"); htmlEncode.put("’", "’"); htmlEncode.put("‚", "‚"); htmlEncode.put("“", "“"); htmlEncode.put("”", "”"); htmlEncode.put("„", "„"); htmlEncode.put("†", "†"); htmlEncode.put("‡", "‡"); htmlEncode.put("•", "•"); htmlEncode.put("…", "…"); htmlEncode.put("‰", "‰"); htmlEncode.put("′", "′"); htmlEncode.put("″", "″"); htmlEncode.put("‹", "‹"); htmlEncode.put("›", "›"); htmlEncode.put("‾", "‾"); htmlEncode.put("⁄", "⁄"); htmlEncode.put("€", "€"); htmlEncode.put("℘", "℘"); htmlEncode.put("ℑ", "ℑ"); htmlEncode.put("ℜ", "ℜ"); htmlEncode.put("™", "™"); htmlEncode.put("ℵ", "ℵ"); htmlEncode.put("←", "←"); htmlEncode.put("↑", "↑"); htmlEncode.put("→", "→"); htmlEncode.put("↓", "↓"); htmlEncode.put("↔", "↔"); htmlEncode.put("↵", "↵"); htmlEncode.put("⇐", "⇐"); htmlEncode.put("⇑", "⇑"); htmlEncode.put("⇒", "⇒"); htmlEncode.put("⇓", "⇓"); htmlEncode.put("⇔", "⇔"); htmlEncode.put("∀", "∀"); htmlEncode.put("∂", "∂"); htmlEncode.put("∃", "∃"); htmlEncode.put("∅", "∅"); htmlEncode.put("∇", "∇"); htmlEncode.put("∈", "∈"); htmlEncode.put("∉", "∉"); htmlEncode.put("∋", "∋"); htmlEncode.put("∏", "∏"); htmlEncode.put("∑", "∑"); htmlEncode.put("−", "−"); htmlEncode.put("∗", "∗"); htmlEncode.put("√", "√"); htmlEncode.put("∝", "∝"); htmlEncode.put("∞", "∞"); htmlEncode.put("∠", "∠"); htmlEncode.put("∧", "∧"); htmlEncode.put("∨", "∨"); htmlEncode.put("∩", "∩"); htmlEncode.put("∪", "∪"); htmlEncode.put("∫", "∫"); htmlEncode.put("∴", "∴"); htmlEncode.put("∼", "∼"); htmlEncode.put("≅", "≅"); htmlEncode.put("≈", "≈"); htmlEncode.put("≠", "≠"); htmlEncode.put("≡", "≡"); htmlEncode.put("≤", "≤"); htmlEncode.put("≥", "≥"); htmlEncode.put("⊂", "⊂"); htmlEncode.put("⊃", "⊃"); htmlEncode.put("⊄", "⊄"); htmlEncode.put("⊆", "⊆"); htmlEncode.put("⊇", "⊇"); htmlEncode.put("⊕", "⊕"); htmlEncode.put("⊗", "⊗"); htmlEncode.put("⊥", "⊥"); htmlEncode.put("⋅", "⋅"); htmlEncode.put("⌈", "⌈"); htmlEncode.put("⌉", "⌉"); htmlEncode.put("⌊", "⌊"); htmlEncode.put("⌋", "⌋"); htmlEncode.put("〈", "⟨"); htmlEncode.put("〉", "⟩"); htmlEncode.put("◊", "◊"); htmlEncode.put("♠", "♠"); htmlEncode.put("♣", "♣"); htmlEncode.put("♥", "♥"); htmlEncode.put("♦", "♦"); } return htmlEncode; } /** * Gets the html decode mapping * @return html decode mapping */ private static Map<String, String> getHtmlDecode() { if(htmlDecode == null) { htmlDecode = new HashMap<String, String>(); for(String ch : getHtmlEncode().keySet()) { htmlDecode.put(getHtmlEncode().get(ch), ch); } } return htmlDecode; } /** * Gets the xml encode mapping * @return xml encode mapping */ private static Map<String, String> getXmlEncode() { if(xmlEncode == null) { xmlEncode = new HashMap<String, String>(); xmlEncode.put(" ", " "); xmlEncode.put(""", """); xmlEncode.put("&", "&"); xmlEncode.put("'", "'"); xmlEncode.put("<", "<"); xmlEncode.put(">", ">"); } return xmlEncode; } /** * Gets the xml decode mapping * @return xml decode mapping */ private static Map<String, String> getXmlDecode() { if(xmlDecode == null) { xmlDecode = new HashMap<String, String>(); for(String ch : getXmlEncode().keySet()) { xmlDecode.put(getXmlEncode().get(ch), ch); } } return xmlDecode; } /** * Gets the list of accepted characters * @return the character list */ private static List<Character> getAcceptedChars() { if(acceptedChars == null) { acceptedChars = new LinkedList<Character>(); acceptedChars.add(Character.valueOf('a')); acceptedChars.add(Character.valueOf('b')); acceptedChars.add(Character.valueOf('c')); acceptedChars.add(Character.valueOf('d')); acceptedChars.add(Character.valueOf('e')); acceptedChars.add(Character.valueOf('f')); acceptedChars.add(Character.valueOf('g')); acceptedChars.add(Character.valueOf('h')); acceptedChars.add(Character.valueOf('i')); acceptedChars.add(Character.valueOf('j')); acceptedChars.add(Character.valueOf('k')); acceptedChars.add(Character.valueOf('l')); acceptedChars.add(Character.valueOf('m')); acceptedChars.add(Character.valueOf('n')); acceptedChars.add(Character.valueOf('o')); acceptedChars.add(Character.valueOf('p')); acceptedChars.add(Character.valueOf('q')); acceptedChars.add(Character.valueOf('r')); acceptedChars.add(Character.valueOf('s')); acceptedChars.add(Character.valueOf('t')); acceptedChars.add(Character.valueOf('u')); acceptedChars.add(Character.valueOf('v')); acceptedChars.add(Character.valueOf('w')); acceptedChars.add(Character.valueOf('x')); acceptedChars.add(Character.valueOf('y')); acceptedChars.add(Character.valueOf('z')); acceptedChars.add(Character.valueOf('A')); acceptedChars.add(Character.valueOf('B')); acceptedChars.add(Character.valueOf('C')); acceptedChars.add(Character.valueOf('D')); acceptedChars.add(Character.valueOf('E')); acceptedChars.add(Character.valueOf('F')); acceptedChars.add(Character.valueOf('G')); acceptedChars.add(Character.valueOf('H')); acceptedChars.add(Character.valueOf('I')); acceptedChars.add(Character.valueOf('J')); acceptedChars.add(Character.valueOf('K')); acceptedChars.add(Character.valueOf('L')); acceptedChars.add(Character.valueOf('M')); acceptedChars.add(Character.valueOf('N')); acceptedChars.add(Character.valueOf('O')); acceptedChars.add(Character.valueOf('P')); acceptedChars.add(Character.valueOf('Q')); acceptedChars.add(Character.valueOf('R')); acceptedChars.add(Character.valueOf('S')); acceptedChars.add(Character.valueOf('T')); acceptedChars.add(Character.valueOf('U')); acceptedChars.add(Character.valueOf('V')); acceptedChars.add(Character.valueOf('W')); acceptedChars.add(Character.valueOf('X')); acceptedChars.add(Character.valueOf('Y')); acceptedChars.add(Character.valueOf('Z')); acceptedChars.add(Character.valueOf('0')); acceptedChars.add(Character.valueOf('1')); acceptedChars.add(Character.valueOf('2')); acceptedChars.add(Character.valueOf('3')); acceptedChars.add(Character.valueOf('4')); acceptedChars.add(Character.valueOf('5')); acceptedChars.add(Character.valueOf('6')); acceptedChars.add(Character.valueOf('7')); acceptedChars.add(Character.valueOf('8')); acceptedChars.add(Character.valueOf('9')); acceptedChars.add(Character.valueOf('.')); acceptedChars.add(Character.valueOf(',')); acceptedChars.add(Character.valueOf(';')); acceptedChars.add(Character.valueOf(':')); acceptedChars.add(Character.valueOf('?')); acceptedChars.add(Character.valueOf('/')); acceptedChars.add(Character.valueOf('\\')); acceptedChars.add(Character.valueOf('|')); acceptedChars.add(Character.valueOf('(')); acceptedChars.add(Character.valueOf(')')); acceptedChars.add(Character.valueOf('{')); acceptedChars.add(Character.valueOf('}')); acceptedChars.add(Character.valueOf('[')); acceptedChars.add(Character.valueOf(']')); acceptedChars.add(Character.valueOf('~')); acceptedChars.add(Character.valueOf('`')); acceptedChars.add(Character.valueOf('!')); acceptedChars.add(Character.valueOf('@')); acceptedChars.add(Character.valueOf('#')); acceptedChars.add(Character.valueOf('$')); acceptedChars.add(Character.valueOf('%')); acceptedChars.add(Character.valueOf('^')); acceptedChars.add(Character.valueOf('*')); acceptedChars.add(Character.valueOf('-')); acceptedChars.add(Character.valueOf('_')); acceptedChars.add(Character.valueOf(' ')); acceptedChars.add(Character.valueOf('+')); acceptedChars.add(Character.valueOf('=')); acceptedChars.add(Character.valueOf('\t')); acceptedChars.add(Character.valueOf('\n')); acceptedChars.add(Character.valueOf('\r')); acceptedChars.add(Character.valueOf('\f')); } return acceptedChars; } /** * Converts all special characters to HTML-entities * @param s input string * @param exceptions force these characters to be encoded * @return html encoded string */ public static String htmlEncode(String s, char... exceptions) { return encode(s, "html", exceptions); } /** * Converts all HTML-special-character-entities to special characters * @param s input string * @return html decoded string */ public static String htmlDecode(String s) { return decode(s, "html"); } /** * Converts all special characters to XML-entities * @param s input string * @param exceptions force these characters to be encoded * @return xml encoded string */ public static String xmlEncode(String s, char... exceptions) { return encode(s, "xml", exceptions); } /** * Converts all XML-special-character-entities to special characters * @param s input string * @return xml decoded string */ public static String xmlDecode(String s) { return decode(s, "xml"); } /** * Converts all special characters to encoded-entities * @param s input string * @param encodeType the type of encoding * @param exceptions force these characters to be encoded * @return encoded string */ private static String encode(String s, String encodeType, char... exceptions) { if(s == null){ return null; } Map<String, String> encodeMap; List<Character> accept = new LinkedList<Character>(getAcceptedChars()); for(char c : exceptions) { accept.remove(Character.valueOf(c)); } if(encodeType.equalsIgnoreCase("html")) { encodeMap = getHtmlEncode(); } else if(encodeType.equalsIgnoreCase("xml")) { encodeMap = getXmlEncode(); } else { throw new IllegalArgumentException("encodeType must be xml or html only"); } StringBuilder b = new StringBuilder(s.length()); for(int i = 0; i < s.length(); i++) { char ch = s.charAt(i); if(accept.contains(Character.valueOf(ch))) { b.append(ch); } else { StringBuilder b2 = new StringBuilder(); if(Character.isISOControl(ch)) { // ignore } else if(Character.isWhitespace(ch)) { b2.append("&#").append((int)ch).append(";"); } else if(Character.isHighSurrogate(ch)) { int codePoint; if((i + 1 < s.length()) && Character.isSurrogatePair(ch, s.charAt(i + 1)) && Character.isDefined(codePoint = (Character.toCodePoint(ch, s.charAt(i + 1))))) { b2.append("&#").append(codePoint).append(";"); } i++; } else if(Character.isLowSurrogate(ch)) { i++; } else if(Character.isDefined(ch)) { b2.append("&#").append((int)ch).append(";"); } if(encodeMap.containsKey(b2.toString())) { b.append(encodeMap.get(b2.toString())); } else { b.append(b2.toString()); } } } return b.toString(); } /** * Converts all special-character-entities to special characters * @param s input string * @param decodeType the type of decoding * @return decoded string */ private static String decode(String s, String decodeType) { Map<String, String> decodeMap; if(decodeType.equalsIgnoreCase("html")) { decodeMap = getHtmlDecode(); } else if(decodeType.equalsIgnoreCase("xml")) { decodeMap = getXmlDecode(); } else { throw new IllegalArgumentException("decodeType must be xml or html only"); } String str = s; for(String htmlEntity : decodeMap.keySet()) { str = str.replaceAll(htmlEntity, decodeMap.get(htmlEntity)); } Matcher m = Pattern.compile("&#(.*?);").matcher(str); Set<String> charCodes = new HashSet<String>(); while(m.find()) { charCodes.add(m.group(1)); } for(String ch : charCodes) { int charCode = Integer.parseInt(ch); if(Character.isDefined((char)charCode)) { str = str.replaceAll("&#" + ch + ";", "" + (char)charCode); } else if(Character.isDefined(charCode)) { StringBuilder b2 = new StringBuilder(); for(char c : Character.toChars(charCode)) { b2.append(c); } str = str.replaceAll("&#" + ch + ";", b2.toString()); } } return str; } }