<?php

function utf8_html_entity_decode($s, $is_htmlspecialchars = false) {
    #оптимизация скорости
    if (strlen($s) < 4  #по минимальной длине сущности - 4 байта: &#d; &xx;
            || ($pos = strpos($s, '&') === false) || strpos($s, ';', $pos) === false)
        return $s;
    $table = array(
        #Latin-1 Entities:
        '&nbsp;' => "\xc2\xa0", #no-break space = non-breaking space
        '&iexcl;' => "\xc2\xa1", #inverted exclamation mark
        '&cent;' => "\xc2\xa2", #cent sign
        '&pound;' => "\xc2\xa3", #pound sign
        '&curren;' => "\xc2\xa4", #currency sign
        '&yen;' => "\xc2\xa5", #yen sign = yuan sign
        '&brvbar;' => "\xc2\xa6", #broken bar = broken vertical bar
        '&sect;' => "\xc2\xa7", #section sign
        '&uml;' => "\xc2\xa8", #diaeresis = spacing diaeresis
        '&copy;' => "\xc2\xa9", #copyright sign
        '&ordf;' => "\xc2\xaa", #feminine ordinal indicator
        '&laquo;' => "\xc2\xab", #left-pointing double angle quotation mark = left pointing guillemet («)
        '&not;' => "\xc2\xac", #not sign
        '&shy;' => "\xc2\xad", #soft hyphen = discretionary hyphen
        '&reg;' => "\xc2\xae", #registered sign = registered trade mark sign
        '&macr;' => "\xc2\xaf", #macron = spacing macron = overline = APL overbar
        '&deg;' => "\xc2\xb0", #degree sign
        '&plusmn;' => "\xc2\xb1", #plus-minus sign = plus-or-minus sign
        '&sup2;' => "\xc2\xb2", #superscript two = superscript digit two = squared
        '&sup3;' => "\xc2\xb3", #superscript three = superscript digit three = cubed
        '&acute;' => "\xc2\xb4", #acute accent = spacing acute
        '&micro;' => "\xc2\xb5", #micro sign
        '&para;' => "\xc2\xb6", #pilcrow sign = paragraph sign
        '&middot;' => "\xc2\xb7", #middle dot = Georgian comma = Greek middle dot
        '&cedil;' => "\xc2\xb8", #cedilla = spacing cedilla
        '&sup1;' => "\xc2\xb9", #superscript one = superscript digit one
        '&ordm;' => "\xc2\xba", #masculine ordinal indicator
        '&raquo;' => "\xc2\xbb", #right-pointing double angle quotation mark = right pointing guillemet (»)
        '&frac14;' => "\xc2\xbc", #vulgar fraction one quarter = fraction one quarter
        '&frac12;' => "\xc2\xbd", #vulgar fraction one half = fraction one half
        '&frac34;' => "\xc2\xbe", #vulgar fraction three quarters = fraction three quarters
        '&iquest;' => "\xc2\xbf", #inverted question mark = turned question mark
        #Latin capital letter
        '&Agrave;' => "\xc3\x80", #Latin capital letter A with grave = Latin capital letter A grave
        '&Aacute;' => "\xc3\x81", #Latin capital letter A with acute
        '&Acirc;' => "\xc3\x82", #Latin capital letter A with circumflex
        '&Atilde;' => "\xc3\x83", #Latin capital letter A with tilde
        '&Auml;' => "\xc3\x84", #Latin capital letter A with diaeresis
        '&Aring;' => "\xc3\x85", #Latin capital letter A with ring above = Latin capital letter A ring
        '&AElig;' => "\xc3\x86", #Latin capital letter AE = Latin capital ligature AE
        '&Ccedil;' => "\xc3\x87", #Latin capital letter C with cedilla
        '&Egrave;' => "\xc3\x88", #Latin capital letter E with grave
        '&Eacute;' => "\xc3\x89", #Latin capital letter E with acute
        '&Ecirc;' => "\xc3\x8a", #Latin capital letter E with circumflex
        '&Euml;' => "\xc3\x8b", #Latin capital letter E with diaeresis
        '&Igrave;' => "\xc3\x8c", #Latin capital letter I with grave
        '&Iacute;' => "\xc3\x8d", #Latin capital letter I with acute
        '&Icirc;' => "\xc3\x8e", #Latin capital letter I with circumflex
        '&Iuml;' => "\xc3\x8f", #Latin capital letter I with diaeresis
        '&ETH;' => "\xc3\x90", #Latin capital letter ETH
        '&Ntilde;' => "\xc3\x91", #Latin capital letter N with tilde
        '&Ograve;' => "\xc3\x92", #Latin capital letter O with grave
        '&Oacute;' => "\xc3\x93", #Latin capital letter O with acute
        '&Ocirc;' => "\xc3\x94", #Latin capital letter O with circumflex
        '&Otilde;' => "\xc3\x95", #Latin capital letter O with tilde
        '&Ouml;' => "\xc3\x96", #Latin capital letter O with diaeresis
        '&times;' => "\xc3\x97", #multiplication sign
        '&Oslash;' => "\xc3\x98", #Latin capital letter O with stroke = Latin capital letter O slash
        '&Ugrave;' => "\xc3\x99", #Latin capital letter U with grave
        '&Uacute;' => "\xc3\x9a", #Latin capital letter U with acute
        '&Ucirc;' => "\xc3\x9b", #Latin capital letter U with circumflex
        '&Uuml;' => "\xc3\x9c", #Latin capital letter U with diaeresis
        '&Yacute;' => "\xc3\x9d", #Latin capital letter Y with acute
        '&THORN;' => "\xc3\x9e", #Latin capital letter THORN
        #Latin small letter
        '&szlig;' => "\xc3\x9f", #Latin small letter sharp s = ess-zed
        '&agrave;' => "\xc3\xa0", #Latin small letter a with grave = Latin small letter a grave
        '&aacute;' => "\xc3\xa1", #Latin small letter a with acute
        '&acirc;' => "\xc3\xa2", #Latin small letter a with circumflex
        '&atilde;' => "\xc3\xa3", #Latin small letter a with tilde
        '&auml;' => "\xc3\xa4", #Latin small letter a with diaeresis
        '&aring;' => "\xc3\xa5", #Latin small letter a with ring above = Latin small letter a ring
        '&aelig;' => "\xc3\xa6", #Latin small letter ae = Latin small ligature ae
        '&ccedil;' => "\xc3\xa7", #Latin small letter c with cedilla
        '&egrave;' => "\xc3\xa8", #Latin small letter e with grave
        '&eacute;' => "\xc3\xa9", #Latin small letter e with acute
        '&ecirc;' => "\xc3\xaa", #Latin small letter e with circumflex
        '&euml;' => "\xc3\xab", #Latin small letter e with diaeresis
        '&igrave;' => "\xc3\xac", #Latin small letter i with grave
        '&iacute;' => "\xc3\xad", #Latin small letter i with acute
        '&icirc;' => "\xc3\xae", #Latin small letter i with circumflex
        '&iuml;' => "\xc3\xaf", #Latin small letter i with diaeresis
        '&eth;' => "\xc3\xb0", #Latin small letter eth
        '&ntilde;' => "\xc3\xb1", #Latin small letter n with tilde
        '&ograve;' => "\xc3\xb2", #Latin small letter o with grave
        '&oacute;' => "\xc3\xb3", #Latin small letter o with acute
        '&ocirc;' => "\xc3\xb4", #Latin small letter o with circumflex
        '&otilde;' => "\xc3\xb5", #Latin small letter o with tilde
        '&ouml;' => "\xc3\xb6", #Latin small letter o with diaeresis
        '&divide;' => "\xc3\xb7", #division sign
        '&oslash;' => "\xc3\xb8", #Latin small letter o with stroke = Latin small letter o slash
        '&ugrave;' => "\xc3\xb9", #Latin small letter u with grave
        '&uacute;' => "\xc3\xba", #Latin small letter u with acute
        '&ucirc;' => "\xc3\xbb", #Latin small letter u with circumflex
        '&uuml;' => "\xc3\xbc", #Latin small letter u with diaeresis
        '&yacute;' => "\xc3\xbd", #Latin small letter y with acute
        '&thorn;' => "\xc3\xbe", #Latin small letter thorn
        '&yuml;' => "\xc3\xbf", #Latin small letter y with diaeresis
        #Symbols and Greek Letters:
        '&fnof;' => "\xc6\x92", #Latin small f with hook = function = florin
        '&Alpha;' => "\xce\x91", #Greek capital letter alpha
        '&Beta;' => "\xce\x92", #Greek capital letter beta
        '&Gamma;' => "\xce\x93", #Greek capital letter gamma
        '&Delta;' => "\xce\x94", #Greek capital letter delta
        '&Epsilon;' => "\xce\x95", #Greek capital letter epsilon
        '&Zeta;' => "\xce\x96", #Greek capital letter zeta
        '&Eta;' => "\xce\x97", #Greek capital letter eta
        '&Theta;' => "\xce\x98", #Greek capital letter theta
        '&Iota;' => "\xce\x99", #Greek capital letter iota
        '&Kappa;' => "\xce\x9a", #Greek capital letter kappa
        '&Lambda;' => "\xce\x9b", #Greek capital letter lambda
        '&Mu;' => "\xce\x9c", #Greek capital letter mu
        '&Nu;' => "\xce\x9d", #Greek capital letter nu
        '&Xi;' => "\xce\x9e", #Greek capital letter xi
        '&Omicron;' => "\xce\x9f", #Greek capital letter omicron
        '&Pi;' => "\xce\xa0", #Greek capital letter pi
        '&Rho;' => "\xce\xa1", #Greek capital letter rho
        '&Sigma;' => "\xce\xa3", #Greek capital letter sigma
        '&Tau;' => "\xce\xa4", #Greek capital letter tau
        '&Upsilon;' => "\xce\xa5", #Greek capital letter upsilon
        '&Phi;' => "\xce\xa6", #Greek capital letter phi
        '&Chi;' => "\xce\xa7", #Greek capital letter chi
        '&Psi;' => "\xce\xa8", #Greek capital letter psi
        '&Omega;' => "\xce\xa9", #Greek capital letter omega
        '&alpha;' => "\xce\xb1", #Greek small letter alpha
        '&beta;' => "\xce\xb2", #Greek small letter beta
        '&gamma;' => "\xce\xb3", #Greek small letter gamma
        '&delta;' => "\xce\xb4", #Greek small letter delta
        '&epsilon;' => "\xce\xb5", #Greek small letter epsilon
        '&zeta;' => "\xce\xb6", #Greek small letter zeta
        '&eta;' => "\xce\xb7", #Greek small letter eta
        '&theta;' => "\xce\xb8", #Greek small letter theta
        '&iota;' => "\xce\xb9", #Greek small letter iota
        '&kappa;' => "\xce\xba", #Greek small letter kappa
        '&lambda;' => "\xce\xbb", #Greek small letter lambda
        '&mu;' => "\xce\xbc", #Greek small letter mu
        '&nu;' => "\xce\xbd", #Greek small letter nu
        '&xi;' => "\xce\xbe", #Greek small letter xi
        '&omicron;' => "\xce\xbf", #Greek small letter omicron
        '&pi;' => "\xcf\x80", #Greek small letter pi
        '&rho;' => "\xcf\x81", #Greek small letter rho
        '&sigmaf;' => "\xcf\x82", #Greek small letter final sigma
        '&sigma;' => "\xcf\x83", #Greek small letter sigma
        '&tau;' => "\xcf\x84", #Greek small letter tau
        '&upsilon;' => "\xcf\x85", #Greek small letter upsilon
        '&phi;' => "\xcf\x86", #Greek small letter phi
        '&chi;' => "\xcf\x87", #Greek small letter chi
        '&psi;' => "\xcf\x88", #Greek small letter psi
        '&omega;' => "\xcf\x89", #Greek small letter omega
        '&thetasym;' => "\xcf\x91", #Greek small letter theta symbol
        '&upsih;' => "\xcf\x92", #Greek upsilon with hook symbol
        '&piv;' => "\xcf\x96", #Greek pi symbol
        '&bull;' => "\xe2\x80\xa2", #bullet = black small circle
        '&hellip;' => "\xe2\x80\xa6", #horizontal ellipsis = three dot leader
        '&prime;' => "\xe2\x80\xb2", #prime = minutes = feet (для обозначения минут и футов)
        '&Prime;' => "\xe2\x80\xb3", #double prime = seconds = inches (для обозначения секунд и дюймов).
        '&oline;' => "\xe2\x80\xbe", #overline = spacing overscore
        '&frasl;' => "\xe2\x81\x84", #fraction slash
        '&weierp;' => "\xe2\x84\x98", #script capital P = power set = Weierstrass p
        '&image;' => "\xe2\x84\x91", #blackletter capital I = imaginary part
        '&real;' => "\xe2\x84\x9c", #blackletter capital R = real part symbol
        '&trade;' => "\xe2\x84\xa2", #trade mark sign
        '&alefsym;' => "\xe2\x84\xb5", #alef symbol = first transfinite cardinal
        '&larr;' => "\xe2\x86\x90", #leftwards arrow
        '&uarr;' => "\xe2\x86\x91", #upwards arrow
        '&rarr;' => "\xe2\x86\x92", #rightwards arrow
        '&darr;' => "\xe2\x86\x93", #downwards arrow
        '&harr;' => "\xe2\x86\x94", #left right arrow
        '&crarr;' => "\xe2\x86\xb5", #downwards arrow with corner leftwards = carriage return
        '&lArr;' => "\xe2\x87\x90", #leftwards double arrow
        '&uArr;' => "\xe2\x87\x91", #upwards double arrow
        '&rArr;' => "\xe2\x87\x92", #rightwards double arrow
        '&dArr;' => "\xe2\x87\x93", #downwards double arrow
        '&hArr;' => "\xe2\x87\x94", #left right double arrow
        '&forall;' => "\xe2\x88\x80", #for all
        '&part;' => "\xe2\x88\x82", #partial differential
        '&exist;' => "\xe2\x88\x83", #there exists
        '&empty;' => "\xe2\x88\x85", #empty set = null set = diameter
        '&nabla;' => "\xe2\x88\x87", #nabla = backward difference
        '&isin;' => "\xe2\x88\x88", #element of
        '&notin;' => "\xe2\x88\x89", #not an element of
        '&ni;' => "\xe2\x88\x8b", #contains as member
        '&prod;' => "\xe2\x88\x8f", #n-ary product = product sign
        '&sum;' => "\xe2\x88\x91", #n-ary sumation
        '&minus;' => "\xe2\x88\x92", #minus sign
        '&lowast;' => "\xe2\x88\x97", #asterisk operator
        '&radic;' => "\xe2\x88\x9a", #square root = radical sign
        '&prop;' => "\xe2\x88\x9d", #proportional to
        '&infin;' => "\xe2\x88\x9e", #infinity
        '&ang;' => "\xe2\x88\xa0", #angle
        '&and;' => "\xe2\x88\xa7", #logical and = wedge
        '&or;' => "\xe2\x88\xa8", #logical or = vee
        '&cap;' => "\xe2\x88\xa9", #intersection = cap
        '&cup;' => "\xe2\x88\xaa", #union = cup
        '&int;' => "\xe2\x88\xab", #integral
        '&there4;' => "\xe2\x88\xb4", #therefore
        '&sim;' => "\xe2\x88\xbc", #tilde operator = varies with = similar to
        '&cong;' => "\xe2\x89\x85", #approximately equal to
        '&asymp;' => "\xe2\x89\x88", #almost equal to = asymptotic to
        '&ne;' => "\xe2\x89\xa0", #not equal to
        '&equiv;' => "\xe2\x89\xa1", #identical to
        '&le;' => "\xe2\x89\xa4", #less-than or equal to
        '&ge;' => "\xe2\x89\xa5", #greater-than or equal to
        '&sub;' => "\xe2\x8a\x82", #subset of
        '&sup;' => "\xe2\x8a\x83", #superset of
        '&nsub;' => "\xe2\x8a\x84", #not a subset of
        '&sube;' => "\xe2\x8a\x86", #subset of or equal to
        '&supe;' => "\xe2\x8a\x87", #superset of or equal to
        '&oplus;' => "\xe2\x8a\x95", #circled plus = direct sum
        '&otimes;' => "\xe2\x8a\x97", #circled times = vector product
        '&perp;' => "\xe2\x8a\xa5", #up tack = orthogonal to = perpendicular
        '&sdot;' => "\xe2\x8b\x85", #dot operator
        '&lceil;' => "\xe2\x8c\x88", #left ceiling = APL upstile
        '&rceil;' => "\xe2\x8c\x89", #right ceiling
        '&lfloor;' => "\xe2\x8c\x8a", #left floor = APL downstile
        '&rfloor;' => "\xe2\x8c\x8b", #right floor
        '&lang;' => "\xe2\x8c\xa9", #left-pointing angle bracket = bra
        '&rang;' => "\xe2\x8c\xaa", #right-pointing angle bracket = ket
        '&loz;' => "\xe2\x97\x8a", #lozenge
        '&spades;' => "\xe2\x99\xa0", #black spade suit
        '&clubs;' => "\xe2\x99\xa3", #black club suit = shamrock
        '&hearts;' => "\xe2\x99\xa5", #black heart suit = valentine
        '&diams;' => "\xe2\x99\xa6", #black diamond suit
        #Other Special Characters:
        '&OElig;' => "\xc5\x92", #Latin capital ligature OE
        '&oelig;' => "\xc5\x93", #Latin small ligature oe
        '&Scaron;' => "\xc5\xa0", #Latin capital letter S with caron
        '&scaron;' => "\xc5\xa1", #Latin small letter s with caron
        '&Yuml;' => "\xc5\xb8", #Latin capital letter Y with diaeresis
        '&circ;' => "\xcb\x86", #modifier letter circumflex accent
        '&tilde;' => "\xcb\x9c", #small tilde
        '&ensp;' => "\xe2\x80\x82", #en space
        '&emsp;' => "\xe2\x80\x83", #em space
        '&thinsp;' => "\xe2\x80\x89", #thin space
        '&zwnj;' => "\xe2\x80\x8c", #zero width non-joiner
        '&zwj;' => "\xe2\x80\x8d", #zero width joiner
        '&lrm;' => "\xe2\x80\x8e", #left-to-right mark
        '&rlm;' => "\xe2\x80\x8f", #right-to-left mark
        '&ndash;' => "\xe2\x80\x93", #en dash
        '&mdash;' => "\xe2\x80\x94", #em dash
        '&lsquo;' => "\xe2\x80\x98", #left single quotation mark
        '&rsquo;' => "\xe2\x80\x99", #right single quotation mark (and apostrophe!)
        '&sbquo;' => "\xe2\x80\x9a", #single low-9 quotation mark
        '&ldquo;' => "\xe2\x80\x9c", #left double quotation mark
        '&rdquo;' => "\xe2\x80\x9d", #right double quotation mark
        '&bdquo;' => "\xe2\x80\x9e", #double low-9 quotation mark
        '&dagger;' => "\xe2\x80\xa0", #dagger
        '&Dagger;' => "\xe2\x80\xa1", #double dagger
        '&permil;' => "\xe2\x80\xb0", #per mille sign
        '&lsaquo;' => "\xe2\x80\xb9", #single left-pointing angle quotation mark
        '&rsaquo;' => "\xe2\x80\xba", #single right-pointing angle quotation mark
        '&euro;' => "\xe2\x82\xac", #euro sign
    );
    $htmlspecialchars = array(
        '&quot;' => "\x22", #quotation mark = APL quote (") &#34;
        '&amp;' => "\x26", #ampersand                  (&) &#38;
        '&lt;' => "\x3c", #less-than sign             (<) &#60;
        '&gt;' => "\x3e", #greater-than sign          (>) &#62;
    );

    if ($is_htmlspecialchars)
        $table += $htmlspecialchars;

    #заменяем именованные сущности:
    #оптимизация скорости: заменяем только те сущности, которые используются в html коде!
    #эта часть кода работает быстрее, чем $s = strtr($s, $table);
    preg_match_all('/&[a-zA-Z]+\d*;/s', $s, $m, null, $pos);
    foreach (array_unique($m[0]) as $entity) {
        if (array_key_exists($entity, $table))
            $s = str_replace($entity, $table[$entity], $s);
    }#foreach

    if (($pos = strpos($s, '&#')) !== false) {  #speed optimization
        #заменяем числовые dec и hex сущности:
        $htmlspecialchars_flip = array_flip($htmlspecialchars);
        $s = preg_replace(
                '/&#((x)[\da-fA-F]{2,4}|\d{1,4});/se', '(array_key_exists($a = pack("C", $d = ("$2") ? hexdec("$1") : "$1"), $htmlspecialchars_flip) && ! $is_htmlspecialchars) ?
             $htmlspecialchars_flip[$a] :
             iconv("UCS-2BE", "UTF-8", pack("n", $d))', $s, -1, $pos);
    }
    return $s;
}
