Advertisement
Guest User

StringExtensionHTML.swift

a guest
Sep 17th, 2015
588
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Swift 12.28 KB | None | 0 0
  1. // Very slightly adapted from http://stackoverflow.com/a/30141700/106244
  2. // 99.99% Credit to Martin R!
  3.  
  4. // Mapping from XML/HTML character entity reference to character
  5. // From http://en.wikipedia.org/wiki/List_of_XML_and_HTML_character_entity_references
  6. private let characterEntities : [String: Character] = [
  7.    
  8.     // XML predefined entities:
  9.     """     : "\"",
  10.     "&"      : "&",
  11.     "'"     : "'",
  12.     "&lt;"       : "<",
  13.     "&gt;"       : ">",
  14.    
  15.     // HTML character entity references:
  16.     "&nbsp;"     : "\u{00A0}",
  17.     "&iexcl;"    : "\u{00A1}",
  18.     "&cent;"     : "\u{00A2}",
  19.     "&pound;"    : "\u{00A3}",
  20.     "&curren;"   : "\u{00A4}",
  21.     "&yen;"      : "\u{00A5}",
  22.     "&brvbar;"   : "\u{00A6}",
  23.     "&sect;"     : "\u{00A7}",
  24.     "&uml;"      : "\u{00A8}",
  25.     "&copy;"     : "\u{00A9}",
  26.     "&ordf;"     : "\u{00AA}",
  27.     "&laquo;"    : "\u{00AB}",
  28.     "&not;"      : "\u{00AC}",
  29.     "&shy;"      : "\u{00AD}",
  30.     "&reg;"      : "\u{00AE}",
  31.     "&macr;"     : "\u{00AF}",
  32.     "&deg;"      : "\u{00B0}",
  33.     "&plusmn;"   : "\u{00B1}",
  34.     "&sup2;"     : "\u{00B2}",
  35.     "&sup3;"     : "\u{00B3}",
  36.     "&acute;"    : "\u{00B4}",
  37.     "&micro;"    : "\u{00B5}",
  38.     "&para;"     : "\u{00B6}",
  39.     "&middot;"   : "\u{00B7}",
  40.     "&cedil;"    : "\u{00B8}",
  41.     "&sup1;"     : "\u{00B9}",
  42.     "&ordm;"     : "\u{00BA}",
  43.     "&raquo;"    : "\u{00BB}",
  44.     "&frac14;"   : "\u{00BC}",
  45.     "&frac12;"   : "\u{00BD}",
  46.     "&frac34;"   : "\u{00BE}",
  47.     "&iquest;"   : "\u{00BF}",
  48.     "&Agrave;"   : "\u{00C0}",
  49.     "&Aacute;"   : "\u{00C1}",
  50.     "&Acirc;"    : "\u{00C2}",
  51.     "&Atilde;"   : "\u{00C3}",
  52.     "&Auml;"     : "\u{00C4}",
  53.     "&Aring;"    : "\u{00C5}",
  54.     "&AElig;"    : "\u{00C6}",
  55.     "&Ccedil;"   : "\u{00C7}",
  56.     "&Egrave;"   : "\u{00C8}",
  57.     "&Eacute;"   : "\u{00C9}",
  58.     "&Ecirc;"    : "\u{00CA}",
  59.     "&Euml;"     : "\u{00CB}",
  60.     "&Igrave;"   : "\u{00CC}",
  61.     "&Iacute;"   : "\u{00CD}",
  62.     "&Icirc;"    : "\u{00CE}",
  63.     "&Iuml;"     : "\u{00CF}",
  64.     "&ETH;"      : "\u{00D0}",
  65.     "&Ntilde;"   : "\u{00D1}",
  66.     "&Ograve;"   : "\u{00D2}",
  67.     "&Oacute;"   : "\u{00D3}",
  68.     "&Ocirc;"    : "\u{00D4}",
  69.     "&Otilde;"   : "\u{00D5}",
  70.     "&Ouml;"     : "\u{00D6}",
  71.     "&times;"    : "\u{00D7}",
  72.     "&Oslash;"   : "\u{00D8}",
  73.     "&Ugrave;"   : "\u{00D9}",
  74.     "&Uacute;"   : "\u{00DA}",
  75.     "&Ucirc;"    : "\u{00DB}",
  76.     "&Uuml;"     : "\u{00DC}",
  77.     "&Yacute;"   : "\u{00DD}",
  78.     "&THORN;"    : "\u{00DE}",
  79.     "&szlig;"    : "\u{00DF}",
  80.     "&agrave;"   : "\u{00E0}",
  81.     "&aacute;"   : "\u{00E1}",
  82.     "&acirc;"    : "\u{00E2}",
  83.     "&atilde;"   : "\u{00E3}",
  84.     "&auml;"     : "\u{00E4}",
  85.     "&aring;"    : "\u{00E5}",
  86.     "&aelig;"    : "\u{00E6}",
  87.     "&ccedil;"   : "\u{00E7}",
  88.     "&egrave;"   : "\u{00E8}",
  89.     "&eacute;"   : "\u{00E9}",
  90.     "&ecirc;"    : "\u{00EA}",
  91.     "&euml;"     : "\u{00EB}",
  92.     "&igrave;"   : "\u{00EC}",
  93.     "&iacute;"   : "\u{00ED}",
  94.     "&icirc;"    : "\u{00EE}",
  95.     "&iuml;"     : "\u{00EF}",
  96.     "&eth;"      : "\u{00F0}",
  97.     "&ntilde;"   : "\u{00F1}",
  98.     "&ograve;"   : "\u{00F2}",
  99.     "&oacute;"   : "\u{00F3}",
  100.     "&ocirc;"    : "\u{00F4}",
  101.     "&otilde;"   : "\u{00F5}",
  102.     "&ouml;"     : "\u{00F6}",
  103.     "&divide;"   : "\u{00F7}",
  104.     "&oslash;"   : "\u{00F8}",
  105.     "&ugrave;"   : "\u{00F9}",
  106.     "&uacute;"   : "\u{00FA}",
  107.     "&ucirc;"    : "\u{00FB}",
  108.     "&uuml;"     : "\u{00FC}",
  109.     "&yacute;"   : "\u{00FD}",
  110.     "&thorn;"    : "\u{00FE}",
  111.     "&yuml;"     : "\u{00FF}",
  112.     "&OElig;"    : "\u{0152}",
  113.     "&oelig;"    : "\u{0153}",
  114.     "&Scaron;"   : "\u{0160}",
  115.     "&scaron;"   : "\u{0161}",
  116.     "&Yuml;"     : "\u{0178}",
  117.     "&fnof;"     : "\u{0192}",
  118.     "&circ;"     : "\u{02C6}",
  119.     "&tilde;"    : "\u{02DC}",
  120.     "&Alpha;"    : "\u{0391}",
  121.     "&Beta;"     : "\u{0392}",
  122.     "&Gamma;"    : "\u{0393}",
  123.     "&Delta;"    : "\u{0394}",
  124.     "&Epsilon;"  : "\u{0395}",
  125.     "&Zeta;"     : "\u{0396}",
  126.     "&Eta;"      : "\u{0397}",
  127.     "&Theta;"    : "\u{0398}",
  128.     "&Iota;"     : "\u{0399}",
  129.     "&Kappa;"    : "\u{039A}",
  130.     "&Lambda;"   : "\u{039B}",
  131.     "&Mu;"       : "\u{039C}",
  132.     "&Nu;"       : "\u{039D}",
  133.     "&Xi;"       : "\u{039E}",
  134.     "&Omicron;"  : "\u{039F}",
  135.     "&Pi;"       : "\u{03A0}",
  136.     "&Rho;"      : "\u{03A1}",
  137.     "&Sigma;"    : "\u{03A3}",
  138.     "&Tau;"      : "\u{03A4}",
  139.     "&Upsilon;"  : "\u{03A5}",
  140.     "&Phi;"      : "\u{03A6}",
  141.     "&Chi;"      : "\u{03A7}",
  142.     "&Psi;"      : "\u{03A8}",
  143.     "&Omega;"    : "\u{03A9}",
  144.     "&alpha;"    : "\u{03B1}",
  145.     "&beta;"     : "\u{03B2}",
  146.     "&gamma;"    : "\u{03B3}",
  147.     "&delta;"    : "\u{03B4}",
  148.     "&epsilon;"  : "\u{03B5}",
  149.     "&zeta;"     : "\u{03B6}",
  150.     "&eta;"      : "\u{03B7}",
  151.     "&theta;"    : "\u{03B8}",
  152.     "&iota;"     : "\u{03B9}",
  153.     "&kappa;"    : "\u{03BA}",
  154.     "&lambda;"   : "\u{03BB}",
  155.     "&mu;"       : "\u{03BC}",
  156.     "&nu;"       : "\u{03BD}",
  157.     "&xi;"       : "\u{03BE}",
  158.     "&omicron;"  : "\u{03BF}",
  159.     "&pi;"       : "\u{03C0}",
  160.     "&rho;"      : "\u{03C1}",
  161.     "&sigmaf;"   : "\u{03C2}",
  162.     "&sigma;"    : "\u{03C3}",
  163.     "&tau;"      : "\u{03C4}",
  164.     "&upsilon;"  : "\u{03C5}",
  165.     "&phi;"      : "\u{03C6}",
  166.     "&chi;"      : "\u{03C7}",
  167.     "&psi;"      : "\u{03C8}",
  168.     "&omega;"    : "\u{03C9}",
  169.     "&thetasym;" : "\u{03D1}",
  170.     "&upsih;"    : "\u{03D2}",
  171.     "&piv;"      : "\u{03D6}",
  172.     "&ensp;"     : "\u{2002}",
  173.     "&emsp;"     : "\u{2003}",
  174.     "&thinsp;"   : "\u{2009}",
  175.     "&zwnj;"     : "\u{200C}",
  176.     "&zwj;"      : "\u{200D}",
  177.     "&lrm;"      : "\u{200E}",
  178.     "&rlm;"      : "\u{200F}",
  179.     "&ndash;"    : "\u{2013}",
  180.     "&mdash;"    : "\u{2014}",
  181.     "&lsquo;"    : "\u{2018}",
  182.     "&rsquo;"    : "\u{2019}",
  183.     "&sbquo;"    : "\u{201A}",
  184.     "&ldquo;"    : "\u{201C}",
  185.     "&rdquo;"    : "\u{201D}",
  186.     "&bdquo;"    : "\u{201E}",
  187.     "&dagger;"   : "\u{2020}",
  188.     "&Dagger;"   : "\u{2021}",
  189.     "&bull;"     : "\u{2022}",
  190.     "&hellip;"   : "\u{2026}",
  191.     "&permil;"   : "\u{2030}",
  192.     "&prime;"    : "\u{2032}",
  193.     "&Prime;"    : "\u{2033}",
  194.     "&lsaquo;"   : "\u{2039}",
  195.     "&rsaquo;"   : "\u{203A}",
  196.     "&oline;"    : "\u{203E}",
  197.     "&frasl;"    : "\u{2044}",
  198.     "&euro;"     : "\u{20AC}",
  199.     "&image;"    : "\u{2111}",
  200.     "&weierp;"   : "\u{2118}",
  201.     "&real;"     : "\u{211C}",
  202.     "&trade;"    : "\u{2122}",
  203.     "&alefsym;"  : "\u{2135}",
  204.     "&larr;"     : "\u{2190}",
  205.     "&uarr;"     : "\u{2191}",
  206.     "&rarr;"     : "\u{2192}",
  207.     "&darr;"     : "\u{2193}",
  208.     "&harr;"     : "\u{2194}",
  209.     "&crarr;"    : "\u{21B5}",
  210.     "&lArr;"     : "\u{21D0}",
  211.     "&uArr;"     : "\u{21D1}",
  212.     "&rArr;"     : "\u{21D2}",
  213.     "&dArr;"     : "\u{21D3}",
  214.     "&hArr;"     : "\u{21D4}",
  215.     "&forall;"   : "\u{2200}",
  216.     "&part;"     : "\u{2202}",
  217.     "&exist;"    : "\u{2203}",
  218.     "&empty;"    : "\u{2205}",
  219.     "&nabla;"    : "\u{2207}",
  220.     "&isin;"     : "\u{2208}",
  221.     "&notin;"    : "\u{2209}",
  222.     "&ni;"       : "\u{220B}",
  223.     "&prod;"     : "\u{220F}",
  224.     "&sum;"      : "\u{2211}",
  225.     "&minus;"    : "\u{2212}",
  226.     "&lowast;"   : "\u{2217}",
  227.     "&radic;"    : "\u{221A}",
  228.     "&prop;"     : "\u{221D}",
  229.     "&infin;"    : "\u{221E}",
  230.     "&ang;"      : "\u{2220}",
  231.     "&and;"      : "\u{2227}",
  232.     "&or;"       : "\u{2228}",
  233.     "&cap;"      : "\u{2229}",
  234.     "&cup;"      : "\u{222A}",
  235.     "&int;"      : "\u{222B}",
  236.     "&there4;"   : "\u{2234}",
  237.     "&sim;"      : "\u{223C}",
  238.     "&cong;"     : "\u{2245}",
  239.     "&asymp;"    : "\u{2248}",
  240.     "&ne;"       : "\u{2260}",
  241.     "&equiv;"    : "\u{2261}",
  242.     "&le;"       : "\u{2264}",
  243.     "&ge;"       : "\u{2265}",
  244.     "&sub;"      : "\u{2282}",
  245.     "&sup;"      : "\u{2283}",
  246.     "&nsub;"     : "\u{2284}",
  247.     "&sube;"     : "\u{2286}",
  248.     "&supe;"     : "\u{2287}",
  249.     "&oplus;"    : "\u{2295}",
  250.     "&otimes;"   : "\u{2297}",
  251.     "&perp;"     : "\u{22A5}",
  252.     "&sdot;"     : "\u{22C5}",
  253.     "&lceil;"    : "\u{2308}",
  254.     "&rceil;"    : "\u{2309}",
  255.     "&lfloor;"   : "\u{230A}",
  256.     "&rfloor;"   : "\u{230B}",
  257.     "&lang;"     : "\u{2329}",
  258.     "&rang;"     : "\u{232A}",
  259.     "&loz;"      : "\u{25CA}",
  260.     "&spades;"   : "\u{2660}",
  261.     "&clubs;"    : "\u{2663}",
  262.     "&hearts;"   : "\u{2665}",
  263.     "&diams;"    : "\u{2666}",
  264.    
  265. ]
  266.  
  267. extension String {
  268.    
  269.     /// Returns a new string made by replacing in the `String`
  270.     /// all HTML character entity references with the corresponding
  271.     /// character.
  272.     var stringByDecodingHTMLEntities: String {
  273.         return decodeHTMLEntities().decodedString
  274.     }
  275.    
  276.     /// Returns a tuple containing the string made by relpacing in the
  277.     /// `String` all HTML character entity references with the corresponding
  278.     /// character. Also returned is an array of offset information describing
  279.     /// the location and length offsets for each replacement. This allows
  280.     /// for the correct adjust any attributes that may be associated with
  281.     /// with substrings within the `String`
  282.     func decodeHTMLEntities() -> (decodedString: String, replacementOffsets: [(index: String.Index, offset: String.Index.Distance)]) {
  283.        
  284.         // ===== Utility functions =====
  285.        
  286.         // Record the index offsets of each replacement
  287.         // This allows anyone to correctly adjust any attributes that may be
  288.         // associated with substrings within the string
  289.         var replacementOffsets: [(index: String.Index, offset: String.Index.Distance)] = []
  290.        
  291.         // Convert the number in the string to the corresponding
  292.         // Unicode character, e.g.
  293.         //    decodeNumeric("64", 10)   --> "@"
  294.         //    decodeNumeric("20ac", 16) --> "€"
  295.         func decodeNumeric(string : String, base : Int32) -> Character? {
  296.             let code = UInt32(string)
  297.             return Character(UnicodeScalar(code!))
  298.         }
  299.        
  300.         // Decode the HTML character entity to the corresponding
  301.         // Unicode character, return `nil` for invalid input.
  302.         //     decode("&#64;")    --> "@"
  303.         //     decode("&#x20ac;") --> "€"
  304.         //     decode("&lt;")     --> "<"
  305.         //     decode("&foo;")    --> nil
  306.         func decode(entity : String) -> Character? {
  307.             if entity.hasPrefix("&#x") || entity.hasPrefix("&#X"){
  308.                 return decodeNumeric(entity.substringFromIndex(entity.startIndex.advancedBy(3)), base: 16)
  309.             } else if entity.hasPrefix("&#") {
  310.                 return decodeNumeric(entity.substringFromIndex(entity.startIndex.advancedBy(2)), base: 10)
  311.             } else {
  312.                 return characterEntities[entity]
  313.             }
  314.         }
  315.        
  316.         // ===== Method starts here =====
  317.        
  318.         var result = ""
  319.         var position = startIndex
  320.        
  321.         // Find the next '&' and copy the characters preceding it to `result`:
  322.         while let ampRange = self.rangeOfString("&", range: position ..< endIndex) {
  323.             result.appendContentsOf(self[position ..< ampRange.startIndex])
  324.             position = ampRange.startIndex
  325.            
  326.             // Find the next ';' and copy everything from '&' to ';' into `entity`
  327.             if let semiRange = self.rangeOfString(";", range: position ..< endIndex) {
  328.                 let entity = self[position ..< semiRange.endIndex]
  329.                 if let decoded = decode(entity) {
  330.                    
  331.                     // Replace by decoded character:
  332.                     result.append(decoded)
  333.                    
  334.                     // Record offset
  335.                     let offset = (index: semiRange.endIndex, offset: 1 - position.distanceTo(semiRange.endIndex))
  336.                     replacementOffsets.append(offset)
  337.                    
  338.                 } else {
  339.                    
  340.                     // Invalid entity, copy verbatim:
  341.                     result.appendContentsOf(entity)
  342.                    
  343.                 }
  344.                 position = semiRange.endIndex
  345.             } else {
  346.                 // No matching ';'.
  347.                 break
  348.             }
  349.         }
  350.        
  351.         // Copy remaining characters to `result`:
  352.         result.appendContentsOf(self[position ..< endIndex])
  353.        
  354.         // Return results
  355.         return (decodedString: result, replacementOffsets: replacementOffsets)
  356.        
  357.     }
  358.    
  359. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement