This repository has been archived on 2026-05-26. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
avh-plan-ios/Pods/SwiftSoup/Sources/Entities.swift
T

383 lines
49 KiB
Swift
Raw Normal View History

//
// Entities.swift
// SwifSoup
//
// Created by Nabil Chatbi on 29/09/16.
// Copyright © 2016 Nabil Chatbi.. All rights reserved.
//
import Foundation
/**
* HTML entities, and escape routines.
* Source: <a href="http://www.w3.org/TR/html5/named-character-references.html#named-character-references">W3C HTML
* named character references</a>.
*/
public class Entities {
private static let empty = -1
private static let emptyName = ""
private static let codepointRadix: Int = 36
public struct EscapeMode: Equatable {
/** Restricted entities suitable for XHTML output: lt, gt, amp, and quot only. */
public static let xhtml: EscapeMode = EscapeMode(string: Entities.xhtml, size: 4, id: 0)
/** Default HTML output entities. */
public static let base: EscapeMode = EscapeMode(string: Entities.base, size: 106, id: 1)
/** Complete HTML entities. */
public static let extended: EscapeMode = EscapeMode(string: Entities.full, size: 2125, id: 2)
fileprivate let value: Int
// table of named references to their codepoints. sorted so we can binary search. built by BuildEntities.
fileprivate var nameKeys: [String]
fileprivate var codeVals: [Int] // limitation is the few references with multiple characters; those go into multipoints.
// table of codepoints to named entities.
fileprivate var codeKeys: [Int] // we don' support multicodepoints to single named value currently
fileprivate var nameVals: [String]
public static func == (left: EscapeMode, right: EscapeMode) -> Bool {
return left.value == right.value
}
static func != (left: EscapeMode, right: EscapeMode) -> Bool {
return left.value != right.value
}
private static let codeDelims: [UnicodeScalar] = [",", ";"]
init(string: String, size: Int, id: Int) {
nameKeys = [String](repeating: "", count: size)
codeVals = [Int](repeating: 0, count: size)
codeKeys = [Int](repeating: 0, count: size)
nameVals = [String](repeating: "", count: size)
value = id
//Load()
var i = 0
let reader: CharacterReader = CharacterReader(string)
while (!reader.isEmpty()) {
// NotNestedLessLess=10913,824;1887
let name: String = reader.consumeTo("=")
reader.advance()
let cp1: Int = Int(reader.consumeToAny(EscapeMode.codeDelims), radix: codepointRadix) ?? 0
let codeDelim: UnicodeScalar = reader.current()
reader.advance()
let cp2: Int
if (codeDelim == ",") {
cp2 = Int(reader.consumeTo(";"), radix: codepointRadix) ?? 0
reader.advance()
} else {
cp2 = empty
}
let index: Int = Int(reader.consumeTo("\n"), radix: codepointRadix) ?? 0
reader.advance()
nameKeys[i] = name
codeVals[i] = cp1
codeKeys[index] = cp1
nameVals[index] = name
if (cp2 != empty) {
var s = String()
s.append(Character(UnicodeScalar(cp1)!))
s.append(Character(UnicodeScalar(cp2)!))
multipoints[name] = s
}
i = i + 1
}
}
// init(string: String, size: Int, id: Int) {
// nameKeys = [String](repeating: "", count: size)
// codeVals = [Int](repeating: 0, count: size)
// codeKeys = [Int](repeating: 0, count: size)
// nameVals = [String](repeating: "", count: size)
// value = id
//
// let components = string.components(separatedBy: "\n")
//
// var i = 0
// for entry in components {
// let match = Entities.entityPattern.matcher(in: entry)
// if (match.find()) {
// let name = match.group(1)!
// let cp1 = Int(match.group(2)!, radix: codepointRadix)
// //let cp2 = Int(Int.parseInt(s: match.group(3), radix: codepointRadix))
// let cp2 = match.group(3) != nil ? Int(match.group(3)!, radix: codepointRadix) : empty
// let index = Int(match.group(4)!, radix: codepointRadix)
//
// nameKeys[i] = name
// codeVals[i] = cp1!
// codeKeys[index!] = cp1!
// nameVals[index!] = name
//
// if (cp2 != empty) {
// var s = String()
// s.append(Character(UnicodeScalar(cp1!)!))
// s.append(Character(UnicodeScalar(cp2!)!))
// multipoints[name] = s
// }
// i += 1
// }
// }
// }
public func codepointForName(_ name: String) -> Int {
// for s in nameKeys {
// if s == name {
// return codeVals[nameKeys.index(of: s)!]
// }
// }
guard let index = nameKeys.index(of: name) else {
return empty
}
return codeVals[index]
}
public func nameForCodepoint(_ codepoint: Int ) -> String {
//let ss = codeKeys.index(of: codepoint)
var index = -1
for s in codeKeys {
if s == codepoint {
index = codeKeys.index(of: codepoint)!
}
}
if (index >= 0) {
// the results are ordered so lower case versions of same codepoint come after uppercase, and we prefer to emit lower
// (and binary search for same item with multi results is undefined
return (index < nameVals.count-1 && codeKeys[index+1] == codepoint) ?
nameVals[index+1] : nameVals[index]
}
return emptyName
}
private func size() -> Int {
return nameKeys.count
}
}
private static var multipoints: Dictionary<String, String> = Dictionary<String, String>() // name -> multiple character references
private init() {
}
/**
* Check if the input is a known named entity
* @param name the possible entity name (e.g. "lt" or "amp")
* @return true if a known named entity
*/
public static func isNamedEntity(_ name: String ) -> Bool {
return (EscapeMode.extended.codepointForName(name) != empty)
}
/**
* Check if the input is a known named entity in the base entity set.
* @param name the possible entity name (e.g. "lt" or "amp")
* @return true if a known named entity in the base set
* @see #isNamedEntity(String)
*/
public static func isBaseNamedEntity(_ name: String) -> Bool {
return EscapeMode.base.codepointForName(name) != empty
}
/**
* Get the Character value of the named entity
* @param name named entity (e.g. "lt" or "amp")
* @return the Character value of the named entity (e.g. '{@literal <}' or '{@literal &}')
* @deprecated does not support characters outside the BMP or multiple character names
*/
public static func getCharacterByName(name: String) -> Character {
return Character.convertFromIntegerLiteral(value: EscapeMode.extended.codepointForName(name))
}
/**
* Get the character(s) represented by the named entitiy
* @param name entity (e.g. "lt" or "amp")
* @return the string value of the character(s) represented by this entity, or "" if not defined
*/
public static func getByName(name: String) -> String {
let val = multipoints[name]
if (val != nil) {return val!}
let codepoint = EscapeMode.extended.codepointForName(name)
if (codepoint != empty) {
return String(Character(UnicodeScalar(codepoint)!))
}
return emptyName
}
public static func codepointsForName(_ name: String, codepoints: inout [UnicodeScalar]) -> Int {
if let val: String = multipoints[name] {
codepoints[0] = val.unicodeScalar(0)
codepoints[1] = val.unicodeScalar(1)
return 2
}
let codepoint = EscapeMode.extended.codepointForName(name)
if (codepoint != empty) {
codepoints[0] = UnicodeScalar(codepoint)!
return 1
}
return 0
}
public static func escape(_ string: String, _ encode: String.Encoding = .utf8 ) -> String {
return Entities.escape(string, OutputSettings().charset(encode).escapeMode(Entities.EscapeMode.extended))
}
public static func escape(_ string: String, _ out: OutputSettings) -> String {
let accum = StringBuilder()//string.characters.count * 2
escape(accum, string, out, false, false, false)
// try {
//
// } catch (IOException e) {
// throw new SerializationException(e) // doesn't happen
// }
return accum.toString()
}
// this method is ugly, and does a lot. but other breakups cause rescanning and stringbuilder generations
static func escape(_ accum: StringBuilder, _ string: String, _ out: OutputSettings, _ inAttribute: Bool, _ normaliseWhite: Bool, _ stripLeadingWhite: Bool ) {
var lastWasWhite = false
var reachedNonWhite = false
let escapeMode: EscapeMode = out.escapeMode()
let encoder: String.Encoding = out.encoder()
//let length = UInt32(string.characters.count)
var codePoint: UnicodeScalar
for ch in string.unicodeScalars {
codePoint = ch
if (normaliseWhite) {
if (codePoint.isWhitespace) {
if ((stripLeadingWhite && !reachedNonWhite) || lastWasWhite) {
continue
}
accum.append(UnicodeScalar.Space)
lastWasWhite = true
continue
} else {
lastWasWhite = false
reachedNonWhite = true
}
}
// surrogate pairs, split implementation for efficiency on single char common case (saves creating strings, char[]):
if (codePoint.value < Character.MIN_SUPPLEMENTARY_CODE_POINT) {
let c = codePoint
// html specific and required escapes:
switch (codePoint) {
case UnicodeScalar.Ampersand:
accum.append("&amp;")
break
case UnicodeScalar(UInt32(0xA0))!:
if (escapeMode != EscapeMode.xhtml) {
accum.append("&nbsp;")
} else {
accum.append("&#xa0;")
}
break
case UnicodeScalar.LessThan:
// escape when in character data or when in a xml attribue val; not needed in html attr val
if (!inAttribute || escapeMode == EscapeMode.xhtml) {
accum.append("&lt;")
} else {
accum.append(c)
}
break
case UnicodeScalar.GreaterThan:
if (!inAttribute) {
accum.append("&gt;")
} else {
accum.append(c)}
break
case "\"":
if (inAttribute) {
accum.append("&quot;")
} else {
accum.append(c)
}
break
default:
if (canEncode(c, encoder)) {
accum.append(c)
} else {
appendEncoded(accum: accum, escapeMode: escapeMode, codePoint: codePoint)
}
}
} else {
if (encoder.canEncode(String(codePoint))) // uses fallback encoder for simplicity
{
accum.append(String(codePoint))
} else {
appendEncoded(accum: accum, escapeMode: escapeMode, codePoint: codePoint)
}
}
}
}
private static func appendEncoded(accum: StringBuilder, escapeMode: EscapeMode, codePoint: UnicodeScalar) {
let name = escapeMode.nameForCodepoint(Int(codePoint.value))
if (name != emptyName) // ok for identity check
{accum.append(UnicodeScalar.Ampersand).append(name).append(";")
} else {
accum.append("&#x").append(String.toHexString(n: Int(codePoint.value)) ).append(";")
}
}
public static func unescape(_ string: String)throws-> String {
return try unescape(string: string, strict: false)
}
/**
* Unescape the input string.
* @param string to un-HTML-escape
* @param strict if "strict" (that is, requires trailing ';' char, otherwise that's optional)
* @return unescaped string
*/
public static func unescape(string: String, strict: Bool)throws -> String {
return try Parser.unescapeEntities(string, strict)
}
/*
* Provides a fast-path for Encoder.canEncode, which drastically improves performance on Android post JellyBean.
* After KitKat, the implementation of canEncode degrades to the point of being useless. For non ASCII or UTF,
* performance may be bad. We can add more encoders for common character sets that are impacted by performance
* issues on Android if required.
*
* Benchmarks: *
* OLD toHtml() impl v New (fastpath) in millis
* Wiki: 1895, 16
* CNN: 6378, 55
* Alterslash: 3013, 28
* Jsoup: 167, 2
*/
private static func canEncode(_ c: UnicodeScalar, _ fallback: String.Encoding) -> Bool {
// todo add more charset tests if impacted by Android's bad perf in canEncode
switch (fallback) {
case String.Encoding.ascii:
return c.value < 0x80
case String.Encoding.utf8:
return true // real is:!(Character.isLowSurrogate(c) || Character.isHighSurrogate(c)) - but already check above
default:
return fallback.canEncode(String(Character(c)))
}
}
static let xhtml: String = "amp=12;1\ngt=1q;3\nlt=1o;2\nquot=y;0"
static let base: String = "AElig=5i;1c\nAMP=12;2\nAacute=5d;17\nAcirc=5e;18\nAgrave=5c;16\nAring=5h;1b\nAtilde=5f;19\nAuml=5g;1a\nCOPY=4p;h\nCcedil=5j;1d\nETH=5s;1m\nEacute=5l;1f\nEcirc=5m;1g\nEgrave=5k;1e\nEuml=5n;1h\nGT=1q;6\nIacute=5p;1j\nIcirc=5q;1k\nIgrave=5o;1i\nIuml=5r;1l\nLT=1o;4\nNtilde=5t;1n\nOacute=5v;1p\nOcirc=5w;1q\nOgrave=5u;1o\nOslash=60;1u\nOtilde=5x;1r\nOuml=5y;1s\nQUOT=y;0\nREG=4u;n\nTHORN=66;20\nUacute=62;1w\nUcirc=63;1x\nUgrave=61;1v\nUuml=64;1y\nYacute=65;1z\naacute=69;23\nacirc=6a;24\nacute=50;u\naelig=6e;28\nagrave=68;22\namp=12;3\naring=6d;27\natilde=6b;25\nauml=6c;26\nbrvbar=4m;e\nccedil=6f;29\ncedil=54;y\ncent=4i;a\ncopy=4p;i\ncurren=4k;c\ndeg=4w;q\ndivide=6v;2p\neacute=6h;2b\necirc=6i;2c\negrave=6g;2a\neth=6o;2i\neuml=6j;2d\nfrac12=59;13\nfrac14=58;12\nfrac34=5a;14\ngt=1q;7\niacute=6l;2f\nicirc=6m;2g\niexcl=4h;9\nigrave=6k;2e\niquest=5b;15\niuml=6n;2h\nlaquo=4r;k\nlt=1o;5\nmacr=4v;p\nmicro=51;v\nmiddot=53;x\nnbsp=4g;8\nnot=4s;l\nntilde=6p;2j\noacute=6r;2l\nocirc=6s;2m\nograve=6q;2k\nordf=4q;j\nordm=56;10\noslash=6w;2q\notilde=6t;2n\nouml=6u;2o\npara=52;w\nplusmn=4x;r\npound=4j;b\nquot=y;1\nraquo=57;11\nreg=4u;o\nsect=4n;f\nshy=4t;m\nsup1=55;z\nsup2=4y;s\nsup3=4z;t\nszlig=67;21\nthorn=72;2w\ntimes=5z;1t\nuacute=6y;2s\nucirc=6z;2t\nugrave=6x;2r\numl=4o;g\nuuml=70;2u\nyacute=71;2v\nyen=4l;d\nyuml=73;2x"
static let full: String = "AElig=5i;2v\nAMP=12;8\nAacute=5d;2p\nAbreve=76;4k\nAcirc=5e;2q\nAcy=sw;av\nAfr=2kn8;1kh\nAgrave=5c;2o\nAlpha=pd;8d\nAmacr=74;4i\nAnd=8cz;1e1\nAogon=78;4m\nAopf=2koo;1ls\nApplyFunction=6e9;ew\nAring=5h;2t\nAscr=2kkc;1jc\nAssign=6s4;s6\nAtilde=5f;2r\nAuml=5g;2s\nBackslash=6qe;o1\nBarv=8h3;1it\nBarwed=6x2;120\nBcy=sx;aw\nBecause=6r9;pw\nBernoullis=6jw;gn\nBeta=pe;8e\nBfr=2kn9;1ki\nBopf=2kop;1lt\nBreve=k8;82\nBscr=6jw;gp\nBumpeq=6ry;ro\nCHcy=tj;bi\nCOPY=4p;1q\nCacute=7a;4o\nCap=6vm;zz\nCapitalDifferentialD=6kl;h8\nCayleys=6jx;gq\nCcaron=7g;4u\nCcedil=5j;2w\nCcirc=7c;4q\nCconint=6r4;pn\nCdot=7e;4s\nCedilla=54;2e\nCenterDot=53;2b\nCfr=6jx;gr\nChi=pz;8y\nCircleDot=6u1;x8\nCircleMinus=6ty;x3\nCirclePlus=6tx;x1\nCircleTimes=6tz;x5\nClockwiseContourIntegral=6r6;pp\nCloseCurlyDoubleQuote=6cd;e0\nCloseCurlyQuote=6c9;dt\nColon=6rb;q1\nColone=8dw;1en\nCongruent=6sh;sn\nConint=6r3;pm\nContourIntegral=6r2;pi\nCopf=6iq;f7\nCoproduct=6q8;nq\nCounterClockwiseContourIntegral=6r7;pr\nCross=8bz;1d8\nCscr=2kke;1jd\nCup=6vn;100\nCupCap=6rx;rk\nDD=6kl;h9\nDDotrahd=841;184\nDJcy=si;ai\nDScy=sl;al\nDZcy=sv;au\nDagger=6ch;e7\nDarr=6n5;j5\nDashv=8h0;1ir\nDcaron=7i;4w\nDcy=t0;az\nDel=6pz;n9\nDelta=pg;8g\nDfr=2knb;1kj\nDiacriticalAcute=50;27\nDiacriticalDot=k9;84\nDiacriticalDoubleAcute=kd;8a\nDiacriticalGrave=2o;13\nDiacriticalTilde=kc;88\nDiamond=6v8;za\nDifferentialD=6km;ha\nDopf=2kor;1lu\nDot=4o;1n\nDotDot=6ho;f5\nDotEqual=6s0;rw\nDoubleContourIntegral=6r3;pl\nDoubleDot=4o;1m\nDoubleDownArrow=6oj;m0\nDoubleLeftArrow=6og;lq\nDoubleLeftRightArrow=6ok;m3\nDoubleLeftTee=8h0;1iq\nDoubleLongLeftArrow=7w8;17g\nDoubleLongLeftRightArrow=7wa;17m\nDoubleLongRightArrow=7w9;17j\nDoubleRightArrow=6oi;lw\nDoubleRightTee=6ug;xz\nDoubleUpArrow=6oh;lt\nDoubleUpDownArrow=6ol;m7\nDoubleVerticalBar=6qt;ov\nDownArrow=6mr;i8\nDownArrowBar=843;186\nDownArrowUpArrow=6ph;mn\nDownBreve=lt;8c\nDownLeftRightVector=85s;198\nDownLeftTeeVector=866;19m\nDownLeftVector=6nx;ke\nDownLeftVectorBar=85y;19e\nDownRightTeeVector=867;19n\nDownRightVector=6o1;kq\nDownRightVectorBar=85z;19f\nDownTee=6uc;xs\nDownTeeArrow=6nb;jh\nDownarrow=6oj;m1\nDscr=2kkf;1je\nDstrok=7k;4y\nENG=96;6g\nETH=5s;35\nEacute=5l;2y\nEcaron=7u;56\nEcirc=5m;2z\nEcy=tp;bo\nEdot=7q;52\nEfr=2knc;1kk\nEgrave=5k;2x\nElement=6q0;na\nEmacr=7m;50\nEmptySmallSquare=7i3;15x\nEmptyVerySmallSquare=7fv;150\nEogon=7s;54\nEopf=2kos;1lv\nEpsilon=ph;8h\nEqual=8dx;1eo\nEqualTilde=6rm;qp\nEquilibrium=6oc;li\nEscr=6k0;gu\nEsim=8dv;1em\nEta=pj;8j\nEuml=5n;30\nExists=6pv;mz\nExponentialE=6kn;hc\nFcy=tg;bf\nFfr=2knd;1kl\nFilledSmallSquare=7i4;15y\nFilledVerySmallSquare=7fu;14w\nFopf=2kot;1lw\nForAll=6ps;ms\nFouriertrf=6k1;gv\nFscr=6k1;gw\nGJcy=sj;aj\nGT=1q;r\nGamma=pf;8f\nGammad=rg;a5\nGbreve=7y;5a\nGcedil=82;5e\nGcirc=7w;58\nGcy=sz;ay\nGdot=80;5c\nGfr=2kne;1km\nGg=6vt;10c\nGopf=2kou;1lx\nGreaterEqual=6sl;sv\nGreaterEqualLess=6vv;10i\nGreaterFullEqual=6sn;t6\nGreaterGreater=8f6;1gh\nGreaterLess=6t3;ul\nGreaterSlantEqual=8e6;1f5\nGreaterTilde=6sz;ub\nGscr=2kki;1jf\nGt=6sr;tr\nHARDcy=tm;bl\nHacek=jr;80\nHat=2m;10\nHcirc=84;5f\nHfr=6j0;fe\nHilbertSpace=6iz;fa\nHopf=6j1;fg\nHorizontalLine=7b4;13i\nHscr=6iz;fc\nHstrok=86;5h\nHumpDownHump=6ry;rn\nHumpEqual=6rz;rs\nIEcy=t1;b0\nIJlig=8i;5s\nIOcy=sh;ah\nIacute=5p;32\nIcirc=5q;33\nIcy=t4;b3\nIdot=8g;5p\nIfr=6j5;fq\nIgrave=5o;31\nIm=6j5;fr\nImacr=8a;5l\nImaginaryI=6ko;hf\nImplies=6oi;ly\nInt=6r0;pf\nIntegral=6qz;pd\nIntersection=6v6;z4\nInvisibleComma=6eb;f0\nInvisibleTimes=6ea;ey\nIogon=8e;5n\nIopf=2kow;1ly\nIota=pl;8l\nIscr=6j4;fn\nItilde=88;5j\nIukcy=sm;am\nIuml=5r;34\nJcirc=8k;5u\nJcy=t5;b4\nJfr=2knh;1kn\nJopf=2kox;1lz\nJscr=2kkl;1jg\nJsercy=so;ao\nJukcy=sk;ak\nKHcy=th;bg\nKJcy=ss;as\nKappa=pm;8m\nKcedil=8m;5w\nKcy=t6;b5\nKfr=2kni;1ko\nKopf=2koy;1m0\nKscr=2kkm;1jh\nLJcy=sp;ap\nLT=1o;m\nLacute=8p;5z\nLambda=pn;8n\nLang=7vu;173\nLaplacetrf=6j6;fs\nLarr=6n2;j1\nLcaron=8t;63\nLcedil=8r;61\nLcy=t7;b6\nLeftAngleBracket=7vs;16x\nLeftArrow=6mo;hu\nLeftArrowBar=6p0;mj\nLeftArrowRightArrow=6o6;l3\nLeftCeiling=6x4;121\nLeftDoubleBracket=7vq;16t\nLeftDownTeeVector=869;19p\nLe
}