First Commit
This commit is contained in:
parent
4e574755c8
commit
9ab1c64a58
|
@ -0,0 +1,33 @@
|
|||
# OS X
|
||||
.DS_Store
|
||||
|
||||
# Xcode
|
||||
build/
|
||||
*.pbxuser
|
||||
!default.pbxuser
|
||||
*.mode1v3
|
||||
!default.mode1v3
|
||||
*.mode2v3
|
||||
!default.mode2v3
|
||||
*.perspectivev3
|
||||
!default.perspectivev3
|
||||
xcuserdata/
|
||||
*.xccheckout
|
||||
profile
|
||||
*.moved-aside
|
||||
DerivedData
|
||||
*.hmap
|
||||
*.ipa
|
||||
|
||||
# Bundler
|
||||
.bundle
|
||||
|
||||
Carthage
|
||||
# We recommend against adding the Pods directory to your .gitignore. However
|
||||
# you should judge for yourself, the pros and cons are mentioned at:
|
||||
# http://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control
|
||||
#
|
||||
# Note: if you ignore the Pods directory, make sure to uncomment
|
||||
# `pod install` in .travis.yml
|
||||
#
|
||||
# Pods/
|
|
@ -0,0 +1,14 @@
|
|||
# references:
|
||||
# * http://www.objc.io/issue-6/travis-ci.html
|
||||
# * https://github.com/supermarin/xcpretty#usage
|
||||
|
||||
osx_image: xcode7.3
|
||||
language: objective-c
|
||||
# cache: cocoapods
|
||||
# podfile: Example/Podfile
|
||||
# before_install:
|
||||
# - gem install cocoapods # Since Travis is not always on latest version
|
||||
# - pod install --project-directory=Example
|
||||
script:
|
||||
- set -o pipefail && xcodebuild test -workspace Example/SwiftSoup.xcworkspace -scheme SwiftSoup-Example -sdk iphonesimulator9.3 ONLY_ACTIVE_ARCH=NO | xcpretty
|
||||
- pod lib lint
|
|
@ -0,0 +1,9 @@
|
|||
use_frameworks!
|
||||
|
||||
target 'SwiftSoup_Example' do
|
||||
pod 'SwiftSoup', :path => '../'
|
||||
|
||||
target 'SwiftSoup_Tests' do
|
||||
inherit! :search_paths
|
||||
end
|
||||
end
|
|
@ -0,0 +1,16 @@
|
|||
PODS:
|
||||
- SwiftSoup (0.1.0)
|
||||
|
||||
DEPENDENCIES:
|
||||
- SwiftSoup (from `../`)
|
||||
|
||||
EXTERNAL SOURCES:
|
||||
SwiftSoup:
|
||||
:path: ../
|
||||
|
||||
SPEC CHECKSUMS:
|
||||
SwiftSoup: 65270e935086a16d49c0d44e3d8225b1974c1e56
|
||||
|
||||
PODFILE CHECKSUM: 1a9fe573c1621ea34a0bf6b2ac20737e78fb44f5
|
||||
|
||||
COCOAPODS: 1.1.1
|
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"name": "SwiftSoup",
|
||||
"version": "0.1.0",
|
||||
"summary": "A short description of SwiftSoup.",
|
||||
"description": "TODO: Add long description of the pod here.",
|
||||
"homepage": "https://github.com/<GITHUB_USERNAME>/SwiftSoup",
|
||||
"license": {
|
||||
"type": "MIT",
|
||||
"file": "LICENSE"
|
||||
},
|
||||
"authors": {
|
||||
"Nabil Chatbi": "scinfu@gmail.com"
|
||||
},
|
||||
"source": {
|
||||
"git": "https://github.com/<GITHUB_USERNAME>/SwiftSoup.git",
|
||||
"tag": "0.1.0"
|
||||
},
|
||||
"platforms": {
|
||||
"ios": "8.0"
|
||||
},
|
||||
"source_files": "Sources/**/*"
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
PODS:
|
||||
- SwiftSoup (0.1.0)
|
||||
|
||||
DEPENDENCIES:
|
||||
- SwiftSoup (from `../`)
|
||||
|
||||
EXTERNAL SOURCES:
|
||||
SwiftSoup:
|
||||
:path: ../
|
||||
|
||||
SPEC CHECKSUMS:
|
||||
SwiftSoup: 65270e935086a16d49c0d44e3d8225b1974c1e56
|
||||
|
||||
PODFILE CHECKSUM: 1a9fe573c1621ea34a0bf6b2ac20737e78fb44f5
|
||||
|
||||
COCOAPODS: 1.1.1
|
|
@ -0,0 +1,996 @@
|
|||
// !$*UTF8*$!
|
||||
{
|
||||
archiveVersion = 1;
|
||||
classes = {
|
||||
};
|
||||
objectVersion = 46;
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
002D62642D8CEDF2E1191BF75FACD20B /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CBB3DE36805AF21409EC968A9691732F /* Foundation.framework */; };
|
||||
046EE2C3DC66B213E7C1CA4C18D21BDE /* CharacterReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2ABDF13416D7D1D91CA7A5FADD332C74 /* CharacterReader.swift */; };
|
||||
0B96546975F09A16AF5B58ADAD07D836 /* OrderedDictionary.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5360949EB5EFCF0A93044D503EBA742F /* OrderedDictionary.swift */; };
|
||||
0C00C6C747599CFDF5F6B24882805D3B /* Pattern.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE180F4C5BB78F4E96BF059D53F367A8 /* Pattern.swift */; };
|
||||
0E271CA89F92DBA45B7A5CBEE034677C /* Node.swift in Sources */ = {isa = PBXBuildFile; fileRef = FB471736E36C74D81CFAE8311682D1FC /* Node.swift */; };
|
||||
14CBBF6979175DFA403353026AF921AD /* SwiftSoup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7242FC14BACD589D1A179A9AD7BAF006 /* SwiftSoup.swift */; };
|
||||
152F942F9163BA73EFE63836730A509C /* CharacterExt.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB9018B393BCF66324D703E044819372 /* CharacterExt.swift */; };
|
||||
167CCEF3F536EBE4AE225EEC3931E140 /* Document.swift in Sources */ = {isa = PBXBuildFile; fileRef = 82988835723EBDDCA448C8D864BC6E5E /* Document.swift */; };
|
||||
2221DD54AF56B7A1A523AA23BD50F518 /* Element.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9CC80B79F86B6C228AC8AE8958FDD99A /* Element.swift */; };
|
||||
224B00870DBD6E3C027E0F48529299F0 /* Pods-SwiftSoup_Tests-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = 9A13C1AE315FCE52AA80EA793FB52974 /* Pods-SwiftSoup_Tests-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
2685E8BD6A52B360669F1827F895F1D1 /* Attributes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1507C71FE3947EEBB161AEEC557F4ED7 /* Attributes.swift */; };
|
||||
33271D1BDAC8AE8B94A6A98BFEF27EB5 /* StringUtil.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8EA75F2C2A22365072408FD0E5FC85FC /* StringUtil.swift */; };
|
||||
34002818420A404CAD7EEED27BE91909 /* Entities.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D78F90D48222D61D164D27BCBD324EA /* Entities.swift */; };
|
||||
3738DD3B6139982ED89FA65A03ED7CDF /* TokenQueue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 936CB70626A1ADD38D9D7FCC3B1AAF80 /* TokenQueue.swift */; };
|
||||
3998C53D7AF890DE54BCBBC66FE3D224 /* TreeBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78CFB56ADB2B9AE546EF6784F097410C /* TreeBuilder.swift */; };
|
||||
39DBBA02FA8960C7E1D22D8F8C3C6C61 /* HtmlTreeBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06C6C7104028E23AC47C6BE2F8D04AE3 /* HtmlTreeBuilder.swift */; };
|
||||
3BD4379478AFD01FD4D534D2E9756FB5 /* Tag.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1B4AA6FBC85DA613E5E1D5B70DCA4D3 /* Tag.swift */; };
|
||||
40B3931D1A43FE6CED321CA06924247F /* Attribute.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50B6312C72917FF2DE7DBBC3DF9305D9 /* Attribute.swift */; };
|
||||
443630095F300D77E9A98F1CDBC76EE1 /* entities-full.properties in Sources */ = {isa = PBXBuildFile; fileRef = 2DB91D3D81AFEBB033A4A204D2C54F06 /* entities-full.properties */; };
|
||||
46D87C2A45960ACD0AFC495CB1CA3B41 /* SwiftSoup-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = 61FE6B5CF4CE8A2DCD2981FAB9238305 /* SwiftSoup-dummy.m */; };
|
||||
497D1E445E7C1BD89066AAD5F3135530 /* SwiftSoup-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = 86277BB2DC0004156AD6B7281A73523F /* SwiftSoup-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
4A2320886B9D2C5A0776D00B199CCE0C /* Token.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD13886354E5F9AB8E709D46F6456B9A /* Token.swift */; };
|
||||
4C990FF8ADD272D8CD0CC3CAF6EF5CB4 /* Parser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30060438C87E4E11551717C4ED0D315C /* Parser.swift */; };
|
||||
4CF5A482A7DD5B83AC6A82438BA3B900 /* Pods-SwiftSoup_Example-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = A48D6A3B687B788EE54D7B13C76C4D6D /* Pods-SwiftSoup_Example-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
56C4C58453202C93CFEB6E6360DF3BDC /* FormElement.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DC19FC05520571FD8C5FAA97C55FA03 /* FormElement.swift */; };
|
||||
578E49588BD2CEF7E3D181C4BF043153 /* TextNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12DB23CD34BF9F222FDDFEA273113718 /* TextNode.swift */; };
|
||||
5D2B5390FD802CF02545F3B86F0957AC /* UnicodeScalar.swift in Sources */ = {isa = PBXBuildFile; fileRef = F99562D84B9C46859CFDEB78AF310354 /* UnicodeScalar.swift */; };
|
||||
6D7FEF0A34F3CFFEF279BCCE7E0AEA61 /* XmlTreeBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4469E88615746AB2B571BB4C5BA30058 /* XmlTreeBuilder.swift */; };
|
||||
75F3FAB5C4909DBF20EFA5492E2D65CC /* Selector.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76A81A4A96447E122A83F21CBD094E60 /* Selector.swift */; };
|
||||
78A122668CDD6D881ED7AA3A8C3BCF49 /* Tokeniser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44FD19004597BE4A2923FAAAFF88CC45 /* Tokeniser.swift */; };
|
||||
7D3AE191DA466414DE259B25DFB51DE4 /* DocumentType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 567E63E5DBA155B26871048F946B972E /* DocumentType.swift */; };
|
||||
7F36F953A8F5B7A2B5ED0F75018521E1 /* Pods-SwiftSoup_Example-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = A5910965D75B191A94A7CC670675B132 /* Pods-SwiftSoup_Example-dummy.m */; };
|
||||
7FCC4289EF764C44943F35268186100A /* StreamReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = B82DC475BBD28451CE78C3B8966E4F45 /* StreamReader.swift */; };
|
||||
8B18529255A4D2912118C0EF97DF9214 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CBB3DE36805AF21409EC968A9691732F /* Foundation.framework */; };
|
||||
8D2B01EF71AA3D1E9F6B37AD937BECFB /* Info.plist in Sources */ = {isa = PBXBuildFile; fileRef = 47237B30CA7E64E1AF09B349FDD1A4E5 /* Info.plist */; };
|
||||
8D8D1C7B9C1A05EFE00994D389A7E3A6 /* NodeVisitor.swift in Sources */ = {isa = PBXBuildFile; fileRef = DFB6437362B28F0205376B24B9BC6D32 /* NodeVisitor.swift */; };
|
||||
8F76CE103BDE591A5700E576E0D9537F /* OrderedSet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99886793DB7FD30382536ECAC9E11975 /* OrderedSet.swift */; };
|
||||
92C43C28486F8E25A4D9F6808B0BAFAF /* HttpStatusException.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EB3551AEF8492ECFAA535280CCF0754 /* HttpStatusException.swift */; };
|
||||
975666083C849DDAF24ED61D77B50A58 /* SerializationException.swift in Sources */ = {isa = PBXBuildFile; fileRef = B559589BA22A97DCE361C8A0AE4035CA /* SerializationException.swift */; };
|
||||
9A612F324083433E7052DFAF5330C28C /* SwiftSoup.h in Headers */ = {isa = PBXBuildFile; fileRef = 0AC48681B890FC121FFA16E08109C84C /* SwiftSoup.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
9CB4B9C70AE0851DDA8D6A83C2D5424F /* Comment.swift in Sources */ = {isa = PBXBuildFile; fileRef = D72F731FE248DF928BCBBF28446DCFA0 /* Comment.swift */; };
|
||||
9D57F0543F27D36E20FD358EB417BA28 /* TokeniserState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EED31DB23F6CC5628448F3B8B29F5AC /* TokeniserState.swift */; };
|
||||
A7FBB860634A97D9FE48E0B4CA21D5E5 /* Elements.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99C4FF39A5E2E2D980B3FA8E581BB59B /* Elements.swift */; };
|
||||
ADC09CD82024D753518D310E5F4F8B18 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CBB3DE36805AF21409EC968A9691732F /* Foundation.framework */; };
|
||||
AF27F689E86800BD4B963E1C78826638 /* Collector.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE040D732CB71B2626ED4C9EBB821391 /* Collector.swift */; };
|
||||
B229513A7693D0581FE8532C07304F6E /* DataUtil.swift in Sources */ = {isa = PBXBuildFile; fileRef = F89C607333D4D6048458407310DB8519 /* DataUtil.swift */; };
|
||||
B33C5D2B632941728CA3EE554EF1C7CD /* CombiningEvaluator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 268B5360AD7809F52FC5E89E2C3F87CC /* CombiningEvaluator.swift */; };
|
||||
B3623633783DE758F0EDEEAE43B2E0AB /* Exception.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38EBF22E2FEC40E3928E2D632454F3EB /* Exception.swift */; };
|
||||
B47BCB199B83D6C4FC1F767FF1A3D3D7 /* ParseError.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5218B1A2E049D2EB9E26E7846C480B8 /* ParseError.swift */; };
|
||||
B68E8F4480CE1472D87ABF6B2EA5C652 /* Cleaner.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB88C1605CEBDE7C782C80930F328A34 /* Cleaner.swift */; };
|
||||
BFEAD21C0D7FB8FED317B256D41FCC53 /* IntExt.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7E360A23790335CE19B0F6FAF86875D /* IntExt.swift */; };
|
||||
C15273E0848340AC3A36A4E004409D27 /* SimpleDictionary.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B092201569D1E108455321B9C28A8D3 /* SimpleDictionary.swift */; };
|
||||
CAE845A3B341709F2A4C1100B4BF4D62 /* Evaluator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 522569DA8E668BF836ED15C597B48EDA /* Evaluator.swift */; };
|
||||
D4A315C9CECA974480363EC4D8791896 /* String.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9FE6482C88444A5B564933158D828FD /* String.swift */; };
|
||||
D54C3454792CE41EC87DFAFC69F820B0 /* entities-xhtml.properties in Sources */ = {isa = PBXBuildFile; fileRef = D59BFEF5D94E61547E7DF6562C0C1F39 /* entities-xhtml.properties */; };
|
||||
D9AD0616B3A1E3473399A9BFFF9290CA /* Pods-SwiftSoup_Tests-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = 3621670A817919A8C0A86DCFBF3CBE33 /* Pods-SwiftSoup_Tests-dummy.m */; };
|
||||
DA0D1B76DD628DC9B25FD4C56A612446 /* XmlDeclaration.swift in Sources */ = {isa = PBXBuildFile; fileRef = A6BC2AB88C89E3E18963825C3CF6F523 /* XmlDeclaration.swift */; };
|
||||
DB6FF4BF26A0880A1CDFC35F0D19E3CE /* HtmlTreeBuilderState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B6DC81B038945DC2C7FC4228AC54ECC /* HtmlTreeBuilderState.swift */; };
|
||||
DC4D6C33F7DA733E456E784D5B9EED75 /* StringBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE5F6867EFEC4E1CADF467F084945DBC /* StringBuilder.swift */; };
|
||||
E0FE7B3239D73AB82FFD25F01A0539E5 /* Whitelist.swift in Sources */ = {isa = PBXBuildFile; fileRef = B998C157C5162F3D1E94988E98E94C9B /* Whitelist.swift */; };
|
||||
E3A2130AFE1AB2D3519CAEEB0166114A /* ParseSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = D57B814E8C162D35FC7C3055763F1057 /* ParseSettings.swift */; };
|
||||
EBC067C810088546B5AC0CAB5B3C2568 /* entities-base.properties in Sources */ = {isa = PBXBuildFile; fileRef = 1FA4D67EAF575EFE2C48FFD4CA64BCC6 /* entities-base.properties */; };
|
||||
EF2C150CFCBA3FDE7D6F43F8FDFC01F7 /* Connection.swift in Sources */ = {isa = PBXBuildFile; fileRef = AEBBD8020AE436C26953CBE340841EDF /* Connection.swift */; };
|
||||
EFF77DCBF9E216563CBF0EF4252D4522 /* NodeTraversor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D64B03490EF66010518F944EDC967EA /* NodeTraversor.swift */; };
|
||||
F27F55DF1A036EF11DA2E024E78A621A /* BooleanAttribute.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9984BE2FD3B0A4EAACEA2F6CFAE1DE2 /* BooleanAttribute.swift */; };
|
||||
F5B30F06A7DF85EAADF1A4F3C224D228 /* StructuralEvaluator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3353C24A8DE7478C4BD53F32D0F6B176 /* StructuralEvaluator.swift */; };
|
||||
F5F9A02B91006E4F573EDCF831BE61CE /* ArrayExt.swift in Sources */ = {isa = PBXBuildFile; fileRef = E652F0A79AE18452343B80408EEB5160 /* ArrayExt.swift */; };
|
||||
F76467AB927D18B33254D37DEC8F7EB1 /* QueryParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55BBB90DCAF483D9212A9C53DB59AA79 /* QueryParser.swift */; };
|
||||
F8E8DB84A2D8DE472EF0AEA4F870DEE5 /* ParseErrorList.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314D2E83FCC9307329853EC5F83FA67C /* ParseErrorList.swift */; };
|
||||
FE4831CFFF544C9D1D92CE04E811335C /* DataNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 70A35732D5D2277691A864293F6BCDF3 /* DataNode.swift */; };
|
||||
FF6E9E17FE98E415380A4D13D263A787 /* Validate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7E50B57CBB3551E433E6828EC47E1B68 /* Validate.swift */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
20FAD70829E0492D24A4733190F1A7FE /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = D41D8CD98F00B204E9800998ECF8427E /* Project object */;
|
||||
proxyType = 1;
|
||||
remoteGlobalIDString = 0E6F1092E8D6723D68E94C0D77268078;
|
||||
remoteInfo = SwiftSoup;
|
||||
};
|
||||
/* End PBXContainerItemProxy section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
00182DC944CA296286A62F2FCD17BCC7 /* Pods-SwiftSoup_Example-acknowledgements.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "Pods-SwiftSoup_Example-acknowledgements.plist"; sourceTree = "<group>"; };
|
||||
01D5707BD68CC8B59127108DBABA2742 /* Pods-SwiftSoup_Example.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = "Pods-SwiftSoup_Example.debug.xcconfig"; sourceTree = "<group>"; };
|
||||
06C6C7104028E23AC47C6BE2F8D04AE3 /* HtmlTreeBuilder.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = HtmlTreeBuilder.swift; sourceTree = "<group>"; };
|
||||
0AC48681B890FC121FFA16E08109C84C /* SwiftSoup.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = SwiftSoup.h; sourceTree = "<group>"; };
|
||||
0D78F90D48222D61D164D27BCBD324EA /* Entities.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = Entities.swift; sourceTree = "<group>"; };
|
||||
0EB3551AEF8492ECFAA535280CCF0754 /* HttpStatusException.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = HttpStatusException.swift; sourceTree = "<group>"; };
|
||||
113AA2B190DF913175E886564BAE38B1 /* Pods_SwiftSoup_Tests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; name = Pods_SwiftSoup_Tests.framework; path = "Pods-SwiftSoup_Tests.framework"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
12DB23CD34BF9F222FDDFEA273113718 /* TextNode.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = TextNode.swift; sourceTree = "<group>"; };
|
||||
1507C71FE3947EEBB161AEEC557F4ED7 /* Attributes.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = Attributes.swift; sourceTree = "<group>"; };
|
||||
1C503D1B76EEC108FF1DBA52DF134729 /* SwiftSoup.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = SwiftSoup.xcconfig; sourceTree = "<group>"; };
|
||||
1FA4D67EAF575EFE2C48FFD4CA64BCC6 /* entities-base.properties */ = {isa = PBXFileReference; includeInIndex = 1; path = "entities-base.properties"; sourceTree = "<group>"; };
|
||||
204CCAE05D3FFFA81CDD0E01C6489E65 /* Pods-SwiftSoup_Tests.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; path = "Pods-SwiftSoup_Tests.modulemap"; sourceTree = "<group>"; };
|
||||
268B5360AD7809F52FC5E89E2C3F87CC /* CombiningEvaluator.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = CombiningEvaluator.swift; sourceTree = "<group>"; };
|
||||
294CE072C0CEF2828FD9C93219A1123A /* Pods-SwiftSoup_Tests-resources.sh */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.script.sh; path = "Pods-SwiftSoup_Tests-resources.sh"; sourceTree = "<group>"; };
|
||||
2ABDF13416D7D1D91CA7A5FADD332C74 /* CharacterReader.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = CharacterReader.swift; sourceTree = "<group>"; };
|
||||
2DB91D3D81AFEBB033A4A204D2C54F06 /* entities-full.properties */ = {isa = PBXFileReference; includeInIndex = 1; path = "entities-full.properties"; sourceTree = "<group>"; };
|
||||
30060438C87E4E11551717C4ED0D315C /* Parser.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = Parser.swift; sourceTree = "<group>"; };
|
||||
314D2E83FCC9307329853EC5F83FA67C /* ParseErrorList.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ParseErrorList.swift; sourceTree = "<group>"; };
|
||||
32B7CC5938FD2E8D7116A0C9A4B00672 /* Pods-SwiftSoup_Tests-acknowledgements.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "Pods-SwiftSoup_Tests-acknowledgements.plist"; sourceTree = "<group>"; };
|
||||
3353C24A8DE7478C4BD53F32D0F6B176 /* StructuralEvaluator.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = StructuralEvaluator.swift; sourceTree = "<group>"; };
|
||||
3621670A817919A8C0A86DCFBF3CBE33 /* Pods-SwiftSoup_Tests-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "Pods-SwiftSoup_Tests-dummy.m"; sourceTree = "<group>"; };
|
||||
38EBF22E2FEC40E3928E2D632454F3EB /* Exception.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = Exception.swift; sourceTree = "<group>"; };
|
||||
3BB1640348DAECB182A277E0A30EF6CC /* Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
414B305911C034AC3D64DE0F984CDE67 /* Pods-SwiftSoup_Example.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = "Pods-SwiftSoup_Example.release.xcconfig"; sourceTree = "<group>"; };
|
||||
4469E88615746AB2B571BB4C5BA30058 /* XmlTreeBuilder.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = XmlTreeBuilder.swift; sourceTree = "<group>"; };
|
||||
44FD19004597BE4A2923FAAAFF88CC45 /* Tokeniser.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = Tokeniser.swift; sourceTree = "<group>"; };
|
||||
47237B30CA7E64E1AF09B349FDD1A4E5 /* Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
4BB2497488397D41257A8711C045ED57 /* Pods-SwiftSoup_Example-acknowledgements.markdown */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; path = "Pods-SwiftSoup_Example-acknowledgements.markdown"; sourceTree = "<group>"; };
|
||||
4EED31DB23F6CC5628448F3B8B29F5AC /* TokeniserState.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = TokeniserState.swift; sourceTree = "<group>"; };
|
||||
50B6312C72917FF2DE7DBBC3DF9305D9 /* Attribute.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = Attribute.swift; sourceTree = "<group>"; };
|
||||
522569DA8E668BF836ED15C597B48EDA /* Evaluator.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = Evaluator.swift; sourceTree = "<group>"; };
|
||||
5360949EB5EFCF0A93044D503EBA742F /* OrderedDictionary.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = OrderedDictionary.swift; sourceTree = "<group>"; };
|
||||
55BBB90DCAF483D9212A9C53DB59AA79 /* QueryParser.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = QueryParser.swift; sourceTree = "<group>"; };
|
||||
567E63E5DBA155B26871048F946B972E /* DocumentType.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = DocumentType.swift; sourceTree = "<group>"; };
|
||||
5B092201569D1E108455321B9C28A8D3 /* SimpleDictionary.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = SimpleDictionary.swift; sourceTree = "<group>"; };
|
||||
5F82C956216398FEAF7B93D254B54BE8 /* Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
61FE6B5CF4CE8A2DCD2981FAB9238305 /* SwiftSoup-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "SwiftSoup-dummy.m"; sourceTree = "<group>"; };
|
||||
6B6DC81B038945DC2C7FC4228AC54ECC /* HtmlTreeBuilderState.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = HtmlTreeBuilderState.swift; sourceTree = "<group>"; };
|
||||
6DC19FC05520571FD8C5FAA97C55FA03 /* FormElement.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = FormElement.swift; sourceTree = "<group>"; };
|
||||
70A35732D5D2277691A864293F6BCDF3 /* DataNode.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = DataNode.swift; sourceTree = "<group>"; };
|
||||
7162A7D31B5F72A4450E569664E90C8F /* Pods-SwiftSoup_Tests-acknowledgements.markdown */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; path = "Pods-SwiftSoup_Tests-acknowledgements.markdown"; sourceTree = "<group>"; };
|
||||
7242FC14BACD589D1A179A9AD7BAF006 /* SwiftSoup.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = SwiftSoup.swift; sourceTree = "<group>"; };
|
||||
76A81A4A96447E122A83F21CBD094E60 /* Selector.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = Selector.swift; sourceTree = "<group>"; };
|
||||
78CFB56ADB2B9AE546EF6784F097410C /* TreeBuilder.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = TreeBuilder.swift; sourceTree = "<group>"; };
|
||||
7DA9E9BDCD6228257E2CBB0F15BC100E /* Pods-SwiftSoup_Example-frameworks.sh */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.script.sh; path = "Pods-SwiftSoup_Example-frameworks.sh"; sourceTree = "<group>"; };
|
||||
7E50B57CBB3551E433E6828EC47E1B68 /* Validate.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = Validate.swift; sourceTree = "<group>"; };
|
||||
7FD5B2CB7927AAB9C2AAB663C43CFED9 /* SwiftSoup-prefix.pch */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "SwiftSoup-prefix.pch"; sourceTree = "<group>"; };
|
||||
82988835723EBDDCA448C8D864BC6E5E /* Document.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = Document.swift; sourceTree = "<group>"; };
|
||||
86277BB2DC0004156AD6B7281A73523F /* SwiftSoup-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "SwiftSoup-umbrella.h"; sourceTree = "<group>"; };
|
||||
8EA75F2C2A22365072408FD0E5FC85FC /* StringUtil.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = StringUtil.swift; sourceTree = "<group>"; };
|
||||
8F10269D0936F1310903EB87575CFADD /* Pods-SwiftSoup_Tests-frameworks.sh */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.script.sh; path = "Pods-SwiftSoup_Tests-frameworks.sh"; sourceTree = "<group>"; };
|
||||
907EB3D4B5FED81AD134DA3AAF344519 /* Pods-SwiftSoup_Example-resources.sh */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.script.sh; path = "Pods-SwiftSoup_Example-resources.sh"; sourceTree = "<group>"; };
|
||||
915CC5D5E15760D98CC6BC8476F7D06B /* SwiftSoup.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; path = SwiftSoup.modulemap; sourceTree = "<group>"; };
|
||||
936CB70626A1ADD38D9D7FCC3B1AAF80 /* TokenQueue.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = TokenQueue.swift; sourceTree = "<group>"; };
|
||||
93A4A3777CF96A4AAC1D13BA6DCCEA73 /* Podfile */ = {isa = PBXFileReference; explicitFileType = text.script.ruby; includeInIndex = 1; lastKnownFileType = text; name = Podfile; path = ../Podfile; sourceTree = SOURCE_ROOT; xcLanguageSpecificationIdentifier = xcode.lang.ruby; };
|
||||
99886793DB7FD30382536ECAC9E11975 /* OrderedSet.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = OrderedSet.swift; sourceTree = "<group>"; };
|
||||
99C4FF39A5E2E2D980B3FA8E581BB59B /* Elements.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = Elements.swift; sourceTree = "<group>"; };
|
||||
9A13C1AE315FCE52AA80EA793FB52974 /* Pods-SwiftSoup_Tests-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "Pods-SwiftSoup_Tests-umbrella.h"; sourceTree = "<group>"; };
|
||||
9CC80B79F86B6C228AC8AE8958FDD99A /* Element.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = Element.swift; sourceTree = "<group>"; };
|
||||
9D64B03490EF66010518F944EDC967EA /* NodeTraversor.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = NodeTraversor.swift; sourceTree = "<group>"; };
|
||||
A2636B6454B78AB1C4E711FBC4FAD8DB /* Pods_SwiftSoup_Example.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; name = Pods_SwiftSoup_Example.framework; path = "Pods-SwiftSoup_Example.framework"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
A48D6A3B687B788EE54D7B13C76C4D6D /* Pods-SwiftSoup_Example-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "Pods-SwiftSoup_Example-umbrella.h"; sourceTree = "<group>"; };
|
||||
A5910965D75B191A94A7CC670675B132 /* Pods-SwiftSoup_Example-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "Pods-SwiftSoup_Example-dummy.m"; sourceTree = "<group>"; };
|
||||
A6BC2AB88C89E3E18963825C3CF6F523 /* XmlDeclaration.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = XmlDeclaration.swift; sourceTree = "<group>"; };
|
||||
AEBBD8020AE436C26953CBE340841EDF /* Connection.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = Connection.swift; sourceTree = "<group>"; };
|
||||
B559589BA22A97DCE361C8A0AE4035CA /* SerializationException.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = SerializationException.swift; sourceTree = "<group>"; };
|
||||
B64EC6880BA4C9F852D5AEDDA09C849E /* Pods-SwiftSoup_Tests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = "Pods-SwiftSoup_Tests.release.xcconfig"; sourceTree = "<group>"; };
|
||||
B82DC475BBD28451CE78C3B8966E4F45 /* StreamReader.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = StreamReader.swift; sourceTree = "<group>"; };
|
||||
B9984BE2FD3B0A4EAACEA2F6CFAE1DE2 /* BooleanAttribute.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = BooleanAttribute.swift; sourceTree = "<group>"; };
|
||||
B998C157C5162F3D1E94988E98E94C9B /* Whitelist.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = Whitelist.swift; sourceTree = "<group>"; };
|
||||
BB88C1605CEBDE7C782C80930F328A34 /* Cleaner.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = Cleaner.swift; sourceTree = "<group>"; };
|
||||
C99DFBA702050A739A4D92693E5D7530 /* Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
CBB3DE36805AF21409EC968A9691732F /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS10.0.sdk/System/Library/Frameworks/Foundation.framework; sourceTree = DEVELOPER_DIR; };
|
||||
CE180F4C5BB78F4E96BF059D53F367A8 /* Pattern.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = Pattern.swift; sourceTree = "<group>"; };
|
||||
D38450E42FB6BEDCA5262784F78DC687 /* SwiftSoup.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; name = SwiftSoup.framework; path = SwiftSoup.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
D57B814E8C162D35FC7C3055763F1057 /* ParseSettings.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ParseSettings.swift; sourceTree = "<group>"; };
|
||||
D59BFEF5D94E61547E7DF6562C0C1F39 /* entities-xhtml.properties */ = {isa = PBXFileReference; includeInIndex = 1; path = "entities-xhtml.properties"; sourceTree = "<group>"; };
|
||||
D72F731FE248DF928BCBBF28446DCFA0 /* Comment.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = Comment.swift; sourceTree = "<group>"; };
|
||||
D7E360A23790335CE19B0F6FAF86875D /* IntExt.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = IntExt.swift; sourceTree = "<group>"; };
|
||||
DD13886354E5F9AB8E709D46F6456B9A /* Token.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = Token.swift; sourceTree = "<group>"; };
|
||||
DE5F6867EFEC4E1CADF467F084945DBC /* StringBuilder.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = StringBuilder.swift; sourceTree = "<group>"; };
|
||||
DFB6437362B28F0205376B24B9BC6D32 /* NodeVisitor.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = NodeVisitor.swift; sourceTree = "<group>"; };
|
||||
DFB7AD6FD245FA811CB2A068CB5BE76C /* Pods-SwiftSoup_Example.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; path = "Pods-SwiftSoup_Example.modulemap"; sourceTree = "<group>"; };
|
||||
E641DEAF0EE6D339715961FF0A71E852 /* Pods-SwiftSoup_Tests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = "Pods-SwiftSoup_Tests.debug.xcconfig"; sourceTree = "<group>"; };
|
||||
E652F0A79AE18452343B80408EEB5160 /* ArrayExt.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ArrayExt.swift; sourceTree = "<group>"; };
|
||||
E9FE6482C88444A5B564933158D828FD /* String.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = String.swift; sourceTree = "<group>"; };
|
||||
EB9018B393BCF66324D703E044819372 /* CharacterExt.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = CharacterExt.swift; sourceTree = "<group>"; };
|
||||
EE040D732CB71B2626ED4C9EBB821391 /* Collector.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = Collector.swift; sourceTree = "<group>"; };
|
||||
F1B4AA6FBC85DA613E5E1D5B70DCA4D3 /* Tag.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = Tag.swift; sourceTree = "<group>"; };
|
||||
F5218B1A2E049D2EB9E26E7846C480B8 /* ParseError.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ParseError.swift; sourceTree = "<group>"; };
|
||||
F89C607333D4D6048458407310DB8519 /* DataUtil.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = DataUtil.swift; sourceTree = "<group>"; };
|
||||
F99562D84B9C46859CFDEB78AF310354 /* UnicodeScalar.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = UnicodeScalar.swift; sourceTree = "<group>"; };
|
||||
FB471736E36C74D81CFAE8311682D1FC /* Node.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = Node.swift; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
1B809091BAAAAB0FBA06D2C48E0B8A02 /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
002D62642D8CEDF2E1191BF75FACD20B /* Foundation.framework in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
8206366D36D986949EDCEC1EECF99F62 /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
8B18529255A4D2912118C0EF97DF9214 /* Foundation.framework in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
9FA9553856A848ABD5BCE9930E792FC4 /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
ADC09CD82024D753518D310E5F4F8B18 /* Foundation.framework in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXFrameworksBuildPhase section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
1499FBCBF5CFC28F7BC75152BB2E3B27 /* select */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
EE040D732CB71B2626ED4C9EBB821391 /* Collector.swift */,
|
||||
268B5360AD7809F52FC5E89E2C3F87CC /* CombiningEvaluator.swift */,
|
||||
99C4FF39A5E2E2D980B3FA8E581BB59B /* Elements.swift */,
|
||||
522569DA8E668BF836ED15C597B48EDA /* Evaluator.swift */,
|
||||
9D64B03490EF66010518F944EDC967EA /* NodeTraversor.swift */,
|
||||
DFB6437362B28F0205376B24B9BC6D32 /* NodeVisitor.swift */,
|
||||
55BBB90DCAF483D9212A9C53DB59AA79 /* QueryParser.swift */,
|
||||
76A81A4A96447E122A83F21CBD094E60 /* Selector.swift */,
|
||||
3353C24A8DE7478C4BD53F32D0F6B176 /* StructuralEvaluator.swift */,
|
||||
);
|
||||
name = select;
|
||||
path = select;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
29A558495F90CDE958D90210D873470A /* Development Pods */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
61DB0160A0A82E64CB14A60CD843F28E /* SwiftSoup */,
|
||||
);
|
||||
name = "Development Pods";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
44660FD6F30E33F42A5D30CDF9F95CF9 /* nodes */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
50B6312C72917FF2DE7DBBC3DF9305D9 /* Attribute.swift */,
|
||||
1507C71FE3947EEBB161AEEC557F4ED7 /* Attributes.swift */,
|
||||
B9984BE2FD3B0A4EAACEA2F6CFAE1DE2 /* BooleanAttribute.swift */,
|
||||
D72F731FE248DF928BCBBF28446DCFA0 /* Comment.swift */,
|
||||
70A35732D5D2277691A864293F6BCDF3 /* DataNode.swift */,
|
||||
82988835723EBDDCA448C8D864BC6E5E /* Document.swift */,
|
||||
567E63E5DBA155B26871048F946B972E /* DocumentType.swift */,
|
||||
9CC80B79F86B6C228AC8AE8958FDD99A /* Element.swift */,
|
||||
0D78F90D48222D61D164D27BCBD324EA /* Entities.swift */,
|
||||
1FA4D67EAF575EFE2C48FFD4CA64BCC6 /* entities-base.properties */,
|
||||
2DB91D3D81AFEBB033A4A204D2C54F06 /* entities-full.properties */,
|
||||
D59BFEF5D94E61547E7DF6562C0C1F39 /* entities-xhtml.properties */,
|
||||
6DC19FC05520571FD8C5FAA97C55FA03 /* FormElement.swift */,
|
||||
FB471736E36C74D81CFAE8311682D1FC /* Node.swift */,
|
||||
12DB23CD34BF9F222FDDFEA273113718 /* TextNode.swift */,
|
||||
A6BC2AB88C89E3E18963825C3CF6F523 /* XmlDeclaration.swift */,
|
||||
);
|
||||
name = nodes;
|
||||
path = nodes;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
4A4FF72ED3CE56B750EBED0BE05F6603 /* safety */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
BB88C1605CEBDE7C782C80930F328A34 /* Cleaner.swift */,
|
||||
B998C157C5162F3D1E94988E98E94C9B /* Whitelist.swift */,
|
||||
);
|
||||
name = safety;
|
||||
path = safety;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
55F83493343EED360A8EA99A27E9B10F /* Products */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
A2636B6454B78AB1C4E711FBC4FAD8DB /* Pods_SwiftSoup_Example.framework */,
|
||||
113AA2B190DF913175E886564BAE38B1 /* Pods_SwiftSoup_Tests.framework */,
|
||||
D38450E42FB6BEDCA5262784F78DC687 /* SwiftSoup.framework */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
61DB0160A0A82E64CB14A60CD843F28E /* SwiftSoup */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
732E3A962D0A2F298543FF29E26DD280 /* Sources */,
|
||||
6707FB5FCF2210E36F550371F21EBB5A /* Support Files */,
|
||||
);
|
||||
name = SwiftSoup;
|
||||
path = ../..;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
6707FB5FCF2210E36F550371F21EBB5A /* Support Files */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
3BB1640348DAECB182A277E0A30EF6CC /* Info.plist */,
|
||||
915CC5D5E15760D98CC6BC8476F7D06B /* SwiftSoup.modulemap */,
|
||||
1C503D1B76EEC108FF1DBA52DF134729 /* SwiftSoup.xcconfig */,
|
||||
61FE6B5CF4CE8A2DCD2981FAB9238305 /* SwiftSoup-dummy.m */,
|
||||
7FD5B2CB7927AAB9C2AAB663C43CFED9 /* SwiftSoup-prefix.pch */,
|
||||
86277BB2DC0004156AD6B7281A73523F /* SwiftSoup-umbrella.h */,
|
||||
);
|
||||
name = "Support Files";
|
||||
path = "Example/Pods/Target Support Files/SwiftSoup";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
72952F04235CC9273A58AF701F459061 /* Targets Support Files */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
74FDB5F0D55B145D4A74C524EA1A86D3 /* Pods-SwiftSoup_Example */,
|
||||
B778239D4C3F05EB5EBD9D7F860C6D42 /* Pods-SwiftSoup_Tests */,
|
||||
);
|
||||
name = "Targets Support Files";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
732E3A962D0A2F298543FF29E26DD280 /* Sources */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
AEBBD8020AE436C26953CBE340841EDF /* Connection.swift */,
|
||||
0EB3551AEF8492ECFAA535280CCF0754 /* HttpStatusException.swift */,
|
||||
47237B30CA7E64E1AF09B349FDD1A4E5 /* Info.plist */,
|
||||
B559589BA22A97DCE361C8A0AE4035CA /* SerializationException.swift */,
|
||||
0AC48681B890FC121FFA16E08109C84C /* SwiftSoup.h */,
|
||||
7242FC14BACD589D1A179A9AD7BAF006 /* SwiftSoup.swift */,
|
||||
84374473213922310A79CDE74F3208FC /* helper */,
|
||||
44660FD6F30E33F42A5D30CDF9F95CF9 /* nodes */,
|
||||
8EFA71549158D7B51A8B5128028121E6 /* parser */,
|
||||
4A4FF72ED3CE56B750EBED0BE05F6603 /* safety */,
|
||||
1499FBCBF5CFC28F7BC75152BB2E3B27 /* select */,
|
||||
A89D8E2FBE96FED98BF2255625545D05 /* shared */,
|
||||
);
|
||||
name = Sources;
|
||||
path = Sources;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
74FDB5F0D55B145D4A74C524EA1A86D3 /* Pods-SwiftSoup_Example */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
C99DFBA702050A739A4D92693E5D7530 /* Info.plist */,
|
||||
DFB7AD6FD245FA811CB2A068CB5BE76C /* Pods-SwiftSoup_Example.modulemap */,
|
||||
4BB2497488397D41257A8711C045ED57 /* Pods-SwiftSoup_Example-acknowledgements.markdown */,
|
||||
00182DC944CA296286A62F2FCD17BCC7 /* Pods-SwiftSoup_Example-acknowledgements.plist */,
|
||||
A5910965D75B191A94A7CC670675B132 /* Pods-SwiftSoup_Example-dummy.m */,
|
||||
7DA9E9BDCD6228257E2CBB0F15BC100E /* Pods-SwiftSoup_Example-frameworks.sh */,
|
||||
907EB3D4B5FED81AD134DA3AAF344519 /* Pods-SwiftSoup_Example-resources.sh */,
|
||||
A48D6A3B687B788EE54D7B13C76C4D6D /* Pods-SwiftSoup_Example-umbrella.h */,
|
||||
01D5707BD68CC8B59127108DBABA2742 /* Pods-SwiftSoup_Example.debug.xcconfig */,
|
||||
414B305911C034AC3D64DE0F984CDE67 /* Pods-SwiftSoup_Example.release.xcconfig */,
|
||||
);
|
||||
name = "Pods-SwiftSoup_Example";
|
||||
path = "Target Support Files/Pods-SwiftSoup_Example";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
7531C8F8DE19F1AA3C8A7AC97A91DC29 /* iOS */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
CBB3DE36805AF21409EC968A9691732F /* Foundation.framework */,
|
||||
);
|
||||
name = iOS;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
7DB346D0F39D3F0E887471402A8071AB = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
93A4A3777CF96A4AAC1D13BA6DCCEA73 /* Podfile */,
|
||||
29A558495F90CDE958D90210D873470A /* Development Pods */,
|
||||
BC3CA7F9E30CC8F7E2DD044DD34432FC /* Frameworks */,
|
||||
55F83493343EED360A8EA99A27E9B10F /* Products */,
|
||||
72952F04235CC9273A58AF701F459061 /* Targets Support Files */,
|
||||
);
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
84374473213922310A79CDE74F3208FC /* helper */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
F89C607333D4D6048458407310DB8519 /* DataUtil.swift */,
|
||||
38EBF22E2FEC40E3928E2D632454F3EB /* Exception.swift */,
|
||||
DE5F6867EFEC4E1CADF467F084945DBC /* StringBuilder.swift */,
|
||||
8EA75F2C2A22365072408FD0E5FC85FC /* StringUtil.swift */,
|
||||
7E50B57CBB3551E433E6828EC47E1B68 /* Validate.swift */,
|
||||
);
|
||||
name = helper;
|
||||
path = helper;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
8EFA71549158D7B51A8B5128028121E6 /* parser */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
2ABDF13416D7D1D91CA7A5FADD332C74 /* CharacterReader.swift */,
|
||||
06C6C7104028E23AC47C6BE2F8D04AE3 /* HtmlTreeBuilder.swift */,
|
||||
6B6DC81B038945DC2C7FC4228AC54ECC /* HtmlTreeBuilderState.swift */,
|
||||
F5218B1A2E049D2EB9E26E7846C480B8 /* ParseError.swift */,
|
||||
314D2E83FCC9307329853EC5F83FA67C /* ParseErrorList.swift */,
|
||||
30060438C87E4E11551717C4ED0D315C /* Parser.swift */,
|
||||
D57B814E8C162D35FC7C3055763F1057 /* ParseSettings.swift */,
|
||||
F1B4AA6FBC85DA613E5E1D5B70DCA4D3 /* Tag.swift */,
|
||||
DD13886354E5F9AB8E709D46F6456B9A /* Token.swift */,
|
||||
44FD19004597BE4A2923FAAAFF88CC45 /* Tokeniser.swift */,
|
||||
4EED31DB23F6CC5628448F3B8B29F5AC /* TokeniserState.swift */,
|
||||
936CB70626A1ADD38D9D7FCC3B1AAF80 /* TokenQueue.swift */,
|
||||
78CFB56ADB2B9AE546EF6784F097410C /* TreeBuilder.swift */,
|
||||
4469E88615746AB2B571BB4C5BA30058 /* XmlTreeBuilder.swift */,
|
||||
);
|
||||
name = parser;
|
||||
path = parser;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
A89D8E2FBE96FED98BF2255625545D05 /* shared */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
E652F0A79AE18452343B80408EEB5160 /* ArrayExt.swift */,
|
||||
EB9018B393BCF66324D703E044819372 /* CharacterExt.swift */,
|
||||
D7E360A23790335CE19B0F6FAF86875D /* IntExt.swift */,
|
||||
5360949EB5EFCF0A93044D503EBA742F /* OrderedDictionary.swift */,
|
||||
99886793DB7FD30382536ECAC9E11975 /* OrderedSet.swift */,
|
||||
CE180F4C5BB78F4E96BF059D53F367A8 /* Pattern.swift */,
|
||||
5B092201569D1E108455321B9C28A8D3 /* SimpleDictionary.swift */,
|
||||
B82DC475BBD28451CE78C3B8966E4F45 /* StreamReader.swift */,
|
||||
E9FE6482C88444A5B564933158D828FD /* String.swift */,
|
||||
F99562D84B9C46859CFDEB78AF310354 /* UnicodeScalar.swift */,
|
||||
);
|
||||
name = shared;
|
||||
path = shared;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
B778239D4C3F05EB5EBD9D7F860C6D42 /* Pods-SwiftSoup_Tests */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
5F82C956216398FEAF7B93D254B54BE8 /* Info.plist */,
|
||||
204CCAE05D3FFFA81CDD0E01C6489E65 /* Pods-SwiftSoup_Tests.modulemap */,
|
||||
7162A7D31B5F72A4450E569664E90C8F /* Pods-SwiftSoup_Tests-acknowledgements.markdown */,
|
||||
32B7CC5938FD2E8D7116A0C9A4B00672 /* Pods-SwiftSoup_Tests-acknowledgements.plist */,
|
||||
3621670A817919A8C0A86DCFBF3CBE33 /* Pods-SwiftSoup_Tests-dummy.m */,
|
||||
8F10269D0936F1310903EB87575CFADD /* Pods-SwiftSoup_Tests-frameworks.sh */,
|
||||
294CE072C0CEF2828FD9C93219A1123A /* Pods-SwiftSoup_Tests-resources.sh */,
|
||||
9A13C1AE315FCE52AA80EA793FB52974 /* Pods-SwiftSoup_Tests-umbrella.h */,
|
||||
E641DEAF0EE6D339715961FF0A71E852 /* Pods-SwiftSoup_Tests.debug.xcconfig */,
|
||||
B64EC6880BA4C9F852D5AEDDA09C849E /* Pods-SwiftSoup_Tests.release.xcconfig */,
|
||||
);
|
||||
name = "Pods-SwiftSoup_Tests";
|
||||
path = "Target Support Files/Pods-SwiftSoup_Tests";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
BC3CA7F9E30CC8F7E2DD044DD34432FC /* Frameworks */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
7531C8F8DE19F1AA3C8A7AC97A91DC29 /* iOS */,
|
||||
);
|
||||
name = Frameworks;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXHeadersBuildPhase section */
|
||||
59639E61F8FE628490F0DFF05C373AA2 /* Headers */ = {
|
||||
isa = PBXHeadersBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
224B00870DBD6E3C027E0F48529299F0 /* Pods-SwiftSoup_Tests-umbrella.h in Headers */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
9909CEEA6C070095D1C0325AC0DEEF10 /* Headers */ = {
|
||||
isa = PBXHeadersBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
4CF5A482A7DD5B83AC6A82438BA3B900 /* Pods-SwiftSoup_Example-umbrella.h in Headers */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
DEFF44DD60080AF77483F3758E76B218 /* Headers */ = {
|
||||
isa = PBXHeadersBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
497D1E445E7C1BD89066AAD5F3135530 /* SwiftSoup-umbrella.h in Headers */,
|
||||
9A612F324083433E7052DFAF5330C28C /* SwiftSoup.h in Headers */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXHeadersBuildPhase section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
00EFD3A2ECFDC662A893E3B8C84DF2C8 /* Pods-SwiftSoup_Tests */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 8133BE31BA5836ECB596E3E3075C924D /* Build configuration list for PBXNativeTarget "Pods-SwiftSoup_Tests" */;
|
||||
buildPhases = (
|
||||
743ECAFB1BD11C767A4161C7E3D54959 /* Sources */,
|
||||
1B809091BAAAAB0FBA06D2C48E0B8A02 /* Frameworks */,
|
||||
59639E61F8FE628490F0DFF05C373AA2 /* Headers */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
name = "Pods-SwiftSoup_Tests";
|
||||
productName = "Pods-SwiftSoup_Tests";
|
||||
productReference = 113AA2B190DF913175E886564BAE38B1 /* Pods_SwiftSoup_Tests.framework */;
|
||||
productType = "com.apple.product-type.framework";
|
||||
};
|
||||
0E6F1092E8D6723D68E94C0D77268078 /* SwiftSoup */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 93611F58904508B082580ECF76CF61F2 /* Build configuration list for PBXNativeTarget "SwiftSoup" */;
|
||||
buildPhases = (
|
||||
05A0EC635BAF6FA864FE878199C02ACD /* Sources */,
|
||||
9FA9553856A848ABD5BCE9930E792FC4 /* Frameworks */,
|
||||
DEFF44DD60080AF77483F3758E76B218 /* Headers */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
name = SwiftSoup;
|
||||
productName = SwiftSoup;
|
||||
productReference = D38450E42FB6BEDCA5262784F78DC687 /* SwiftSoup.framework */;
|
||||
productType = "com.apple.product-type.framework";
|
||||
};
|
||||
BEA04E101EB941C28F67E6F6433515D7 /* Pods-SwiftSoup_Example */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 5FE454748DB4479DCAC40B11D8858295 /* Build configuration list for PBXNativeTarget "Pods-SwiftSoup_Example" */;
|
||||
buildPhases = (
|
||||
583EA812AB87EF1B1C2328ABE4551751 /* Sources */,
|
||||
8206366D36D986949EDCEC1EECF99F62 /* Frameworks */,
|
||||
9909CEEA6C070095D1C0325AC0DEEF10 /* Headers */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
2872E98128677FC3CF34D0F3779E2142 /* PBXTargetDependency */,
|
||||
);
|
||||
name = "Pods-SwiftSoup_Example";
|
||||
productName = "Pods-SwiftSoup_Example";
|
||||
productReference = A2636B6454B78AB1C4E711FBC4FAD8DB /* Pods_SwiftSoup_Example.framework */;
|
||||
productType = "com.apple.product-type.framework";
|
||||
};
|
||||
/* End PBXNativeTarget section */
|
||||
|
||||
/* Begin PBXProject section */
|
||||
D41D8CD98F00B204E9800998ECF8427E /* Project object */ = {
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
LastSwiftUpdateCheck = 0730;
|
||||
LastUpgradeCheck = 0700;
|
||||
};
|
||||
buildConfigurationList = 2D8E8EC45A3A1A1D94AE762CB5028504 /* Build configuration list for PBXProject "Pods" */;
|
||||
compatibilityVersion = "Xcode 3.2";
|
||||
developmentRegion = English;
|
||||
hasScannedForEncodings = 0;
|
||||
knownRegions = (
|
||||
en,
|
||||
);
|
||||
mainGroup = 7DB346D0F39D3F0E887471402A8071AB;
|
||||
productRefGroup = 55F83493343EED360A8EA99A27E9B10F /* Products */;
|
||||
projectDirPath = "";
|
||||
projectRoot = "";
|
||||
targets = (
|
||||
BEA04E101EB941C28F67E6F6433515D7 /* Pods-SwiftSoup_Example */,
|
||||
00EFD3A2ECFDC662A893E3B8C84DF2C8 /* Pods-SwiftSoup_Tests */,
|
||||
0E6F1092E8D6723D68E94C0D77268078 /* SwiftSoup */,
|
||||
);
|
||||
};
|
||||
/* End PBXProject section */
|
||||
|
||||
/* Begin PBXSourcesBuildPhase section */
|
||||
05A0EC635BAF6FA864FE878199C02ACD /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
F5F9A02B91006E4F573EDCF831BE61CE /* ArrayExt.swift in Sources */,
|
||||
40B3931D1A43FE6CED321CA06924247F /* Attribute.swift in Sources */,
|
||||
2685E8BD6A52B360669F1827F895F1D1 /* Attributes.swift in Sources */,
|
||||
F27F55DF1A036EF11DA2E024E78A621A /* BooleanAttribute.swift in Sources */,
|
||||
152F942F9163BA73EFE63836730A509C /* CharacterExt.swift in Sources */,
|
||||
046EE2C3DC66B213E7C1CA4C18D21BDE /* CharacterReader.swift in Sources */,
|
||||
B68E8F4480CE1472D87ABF6B2EA5C652 /* Cleaner.swift in Sources */,
|
||||
AF27F689E86800BD4B963E1C78826638 /* Collector.swift in Sources */,
|
||||
B33C5D2B632941728CA3EE554EF1C7CD /* CombiningEvaluator.swift in Sources */,
|
||||
9CB4B9C70AE0851DDA8D6A83C2D5424F /* Comment.swift in Sources */,
|
||||
EF2C150CFCBA3FDE7D6F43F8FDFC01F7 /* Connection.swift in Sources */,
|
||||
FE4831CFFF544C9D1D92CE04E811335C /* DataNode.swift in Sources */,
|
||||
B229513A7693D0581FE8532C07304F6E /* DataUtil.swift in Sources */,
|
||||
167CCEF3F536EBE4AE225EEC3931E140 /* Document.swift in Sources */,
|
||||
7D3AE191DA466414DE259B25DFB51DE4 /* DocumentType.swift in Sources */,
|
||||
2221DD54AF56B7A1A523AA23BD50F518 /* Element.swift in Sources */,
|
||||
A7FBB860634A97D9FE48E0B4CA21D5E5 /* Elements.swift in Sources */,
|
||||
EBC067C810088546B5AC0CAB5B3C2568 /* entities-base.properties in Sources */,
|
||||
443630095F300D77E9A98F1CDBC76EE1 /* entities-full.properties in Sources */,
|
||||
D54C3454792CE41EC87DFAFC69F820B0 /* entities-xhtml.properties in Sources */,
|
||||
34002818420A404CAD7EEED27BE91909 /* Entities.swift in Sources */,
|
||||
CAE845A3B341709F2A4C1100B4BF4D62 /* Evaluator.swift in Sources */,
|
||||
B3623633783DE758F0EDEEAE43B2E0AB /* Exception.swift in Sources */,
|
||||
56C4C58453202C93CFEB6E6360DF3BDC /* FormElement.swift in Sources */,
|
||||
39DBBA02FA8960C7E1D22D8F8C3C6C61 /* HtmlTreeBuilder.swift in Sources */,
|
||||
DB6FF4BF26A0880A1CDFC35F0D19E3CE /* HtmlTreeBuilderState.swift in Sources */,
|
||||
92C43C28486F8E25A4D9F6808B0BAFAF /* HttpStatusException.swift in Sources */,
|
||||
8D2B01EF71AA3D1E9F6B37AD937BECFB /* Info.plist in Sources */,
|
||||
BFEAD21C0D7FB8FED317B256D41FCC53 /* IntExt.swift in Sources */,
|
||||
0E271CA89F92DBA45B7A5CBEE034677C /* Node.swift in Sources */,
|
||||
EFF77DCBF9E216563CBF0EF4252D4522 /* NodeTraversor.swift in Sources */,
|
||||
8D8D1C7B9C1A05EFE00994D389A7E3A6 /* NodeVisitor.swift in Sources */,
|
||||
0B96546975F09A16AF5B58ADAD07D836 /* OrderedDictionary.swift in Sources */,
|
||||
8F76CE103BDE591A5700E576E0D9537F /* OrderedSet.swift in Sources */,
|
||||
B47BCB199B83D6C4FC1F767FF1A3D3D7 /* ParseError.swift in Sources */,
|
||||
F8E8DB84A2D8DE472EF0AEA4F870DEE5 /* ParseErrorList.swift in Sources */,
|
||||
4C990FF8ADD272D8CD0CC3CAF6EF5CB4 /* Parser.swift in Sources */,
|
||||
E3A2130AFE1AB2D3519CAEEB0166114A /* ParseSettings.swift in Sources */,
|
||||
0C00C6C747599CFDF5F6B24882805D3B /* Pattern.swift in Sources */,
|
||||
F76467AB927D18B33254D37DEC8F7EB1 /* QueryParser.swift in Sources */,
|
||||
75F3FAB5C4909DBF20EFA5492E2D65CC /* Selector.swift in Sources */,
|
||||
975666083C849DDAF24ED61D77B50A58 /* SerializationException.swift in Sources */,
|
||||
C15273E0848340AC3A36A4E004409D27 /* SimpleDictionary.swift in Sources */,
|
||||
7FCC4289EF764C44943F35268186100A /* StreamReader.swift in Sources */,
|
||||
D4A315C9CECA974480363EC4D8791896 /* String.swift in Sources */,
|
||||
DC4D6C33F7DA733E456E784D5B9EED75 /* StringBuilder.swift in Sources */,
|
||||
33271D1BDAC8AE8B94A6A98BFEF27EB5 /* StringUtil.swift in Sources */,
|
||||
F5B30F06A7DF85EAADF1A4F3C224D228 /* StructuralEvaluator.swift in Sources */,
|
||||
46D87C2A45960ACD0AFC495CB1CA3B41 /* SwiftSoup-dummy.m in Sources */,
|
||||
14CBBF6979175DFA403353026AF921AD /* SwiftSoup.swift in Sources */,
|
||||
3BD4379478AFD01FD4D534D2E9756FB5 /* Tag.swift in Sources */,
|
||||
578E49588BD2CEF7E3D181C4BF043153 /* TextNode.swift in Sources */,
|
||||
4A2320886B9D2C5A0776D00B199CCE0C /* Token.swift in Sources */,
|
||||
78A122668CDD6D881ED7AA3A8C3BCF49 /* Tokeniser.swift in Sources */,
|
||||
9D57F0543F27D36E20FD358EB417BA28 /* TokeniserState.swift in Sources */,
|
||||
3738DD3B6139982ED89FA65A03ED7CDF /* TokenQueue.swift in Sources */,
|
||||
3998C53D7AF890DE54BCBBC66FE3D224 /* TreeBuilder.swift in Sources */,
|
||||
5D2B5390FD802CF02545F3B86F0957AC /* UnicodeScalar.swift in Sources */,
|
||||
FF6E9E17FE98E415380A4D13D263A787 /* Validate.swift in Sources */,
|
||||
E0FE7B3239D73AB82FFD25F01A0539E5 /* Whitelist.swift in Sources */,
|
||||
DA0D1B76DD628DC9B25FD4C56A612446 /* XmlDeclaration.swift in Sources */,
|
||||
6D7FEF0A34F3CFFEF279BCCE7E0AEA61 /* XmlTreeBuilder.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
583EA812AB87EF1B1C2328ABE4551751 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
7F36F953A8F5B7A2B5ED0F75018521E1 /* Pods-SwiftSoup_Example-dummy.m in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
743ECAFB1BD11C767A4161C7E3D54959 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
D9AD0616B3A1E3473399A9BFFF9290CA /* Pods-SwiftSoup_Tests-dummy.m in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXSourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXTargetDependency section */
|
||||
2872E98128677FC3CF34D0F3779E2142 /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
name = SwiftSoup;
|
||||
target = 0E6F1092E8D6723D68E94C0D77268078 /* SwiftSoup */;
|
||||
targetProxy = 20FAD70829E0492D24A4733190F1A7FE /* PBXContainerItemProxy */;
|
||||
};
|
||||
/* End PBXTargetDependency section */
|
||||
|
||||
/* Begin XCBuildConfiguration section */
|
||||
0A4B4CA1C66A1B1CFE9142C386E9580A /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 01D5707BD68CC8B59127108DBABA2742 /* Pods-SwiftSoup_Example.debug.xcconfig */;
|
||||
buildSettings = {
|
||||
"CODE_SIGN_IDENTITY[sdk=appletvos*]" = "";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "";
|
||||
"CODE_SIGN_IDENTITY[sdk=watchos*]" = "";
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
DEFINES_MODULE = YES;
|
||||
DYLIB_COMPATIBILITY_VERSION = 1;
|
||||
DYLIB_CURRENT_VERSION = 1;
|
||||
DYLIB_INSTALL_NAME_BASE = "@rpath";
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
INFOPLIST_FILE = "Target Support Files/Pods-SwiftSoup_Example/Info.plist";
|
||||
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 8.3;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
|
||||
MACH_O_TYPE = staticlib;
|
||||
MODULEMAP_FILE = "Target Support Files/Pods-SwiftSoup_Example/Pods-SwiftSoup_Example.modulemap";
|
||||
MTL_ENABLE_DEBUG_INFO = YES;
|
||||
OTHER_LDFLAGS = "";
|
||||
OTHER_LIBTOOLFLAGS = "";
|
||||
PODS_ROOT = "$(SRCROOT)";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.${PRODUCT_NAME:rfc1034identifier}";
|
||||
PRODUCT_NAME = Pods_SwiftSoup_Example;
|
||||
SDKROOT = iphoneos;
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
VERSION_INFO_PREFIX = "";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
335273B9B316C9B70F274417280A0BAC /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 1C503D1B76EEC108FF1DBA52DF134729 /* SwiftSoup.xcconfig */;
|
||||
buildSettings = {
|
||||
"CODE_SIGN_IDENTITY[sdk=appletvos*]" = "";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "";
|
||||
"CODE_SIGN_IDENTITY[sdk=watchos*]" = "";
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
DEFINES_MODULE = YES;
|
||||
DYLIB_COMPATIBILITY_VERSION = 1;
|
||||
DYLIB_CURRENT_VERSION = 1;
|
||||
DYLIB_INSTALL_NAME_BASE = "@rpath";
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_PREFIX_HEADER = "Target Support Files/SwiftSoup/SwiftSoup-prefix.pch";
|
||||
INFOPLIST_FILE = "Target Support Files/SwiftSoup/Info.plist";
|
||||
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
|
||||
MODULEMAP_FILE = "Target Support Files/SwiftSoup/SwiftSoup.modulemap";
|
||||
MTL_ENABLE_DEBUG_INFO = YES;
|
||||
PRODUCT_NAME = SwiftSoup;
|
||||
SDKROOT = iphoneos;
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_VERSION = 3.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
VERSION_INFO_PREFIX = "";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
3D8EED55FB8A9CA05383AE81EB1C5307 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = B64EC6880BA4C9F852D5AEDDA09C849E /* Pods-SwiftSoup_Tests.release.xcconfig */;
|
||||
buildSettings = {
|
||||
"CODE_SIGN_IDENTITY[sdk=appletvos*]" = "";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "";
|
||||
"CODE_SIGN_IDENTITY[sdk=watchos*]" = "";
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
DEFINES_MODULE = YES;
|
||||
DYLIB_COMPATIBILITY_VERSION = 1;
|
||||
DYLIB_CURRENT_VERSION = 1;
|
||||
DYLIB_INSTALL_NAME_BASE = "@rpath";
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
INFOPLIST_FILE = "Target Support Files/Pods-SwiftSoup_Tests/Info.plist";
|
||||
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 8.3;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
|
||||
MACH_O_TYPE = staticlib;
|
||||
MODULEMAP_FILE = "Target Support Files/Pods-SwiftSoup_Tests/Pods-SwiftSoup_Tests.modulemap";
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
OTHER_LDFLAGS = "";
|
||||
OTHER_LIBTOOLFLAGS = "";
|
||||
PODS_ROOT = "$(SRCROOT)";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.${PRODUCT_NAME:rfc1034identifier}";
|
||||
PRODUCT_NAME = Pods_SwiftSoup_Tests;
|
||||
SDKROOT = iphoneos;
|
||||
SKIP_INSTALL = YES;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
VERSION_INFO_PREFIX = "";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
8A8DB2D30A1E704BE9ECBAF9F67031A7 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = E641DEAF0EE6D339715961FF0A71E852 /* Pods-SwiftSoup_Tests.debug.xcconfig */;
|
||||
buildSettings = {
|
||||
"CODE_SIGN_IDENTITY[sdk=appletvos*]" = "";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "";
|
||||
"CODE_SIGN_IDENTITY[sdk=watchos*]" = "";
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
DEFINES_MODULE = YES;
|
||||
DYLIB_COMPATIBILITY_VERSION = 1;
|
||||
DYLIB_CURRENT_VERSION = 1;
|
||||
DYLIB_INSTALL_NAME_BASE = "@rpath";
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
INFOPLIST_FILE = "Target Support Files/Pods-SwiftSoup_Tests/Info.plist";
|
||||
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 8.3;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
|
||||
MACH_O_TYPE = staticlib;
|
||||
MODULEMAP_FILE = "Target Support Files/Pods-SwiftSoup_Tests/Pods-SwiftSoup_Tests.modulemap";
|
||||
MTL_ENABLE_DEBUG_INFO = YES;
|
||||
OTHER_LDFLAGS = "";
|
||||
OTHER_LIBTOOLFLAGS = "";
|
||||
PODS_ROOT = "$(SRCROOT)";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.${PRODUCT_NAME:rfc1034identifier}";
|
||||
PRODUCT_NAME = Pods_SwiftSoup_Tests;
|
||||
SDKROOT = iphoneos;
|
||||
SKIP_INSTALL = YES;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
VERSION_INFO_PREFIX = "";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
8DED8AD26D381A6ACFF202E5217EC498 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
CODE_SIGNING_REQUIRED = NO;
|
||||
COPY_PHASE_STRIP = YES;
|
||||
ENABLE_NS_ASSERTIONS = NO;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||
"POD_CONFIGURATION_RELEASE=1",
|
||||
"$(inherited)",
|
||||
);
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 8.3;
|
||||
PROVISIONING_PROFILE_SPECIFIER = NO_SIGNING/;
|
||||
STRIP_INSTALLED_PRODUCT = NO;
|
||||
SYMROOT = "${SRCROOT}/../build";
|
||||
VALIDATE_PRODUCT = YES;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
9E1E4E48AF2EAB23169E611BF694090A /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
CODE_SIGNING_REQUIRED = NO;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
ENABLE_TESTABILITY = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||
GCC_DYNAMIC_NO_PIC = NO;
|
||||
GCC_OPTIMIZATION_LEVEL = 0;
|
||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||
"POD_CONFIGURATION_DEBUG=1",
|
||||
"DEBUG=1",
|
||||
"$(inherited)",
|
||||
);
|
||||
GCC_SYMBOLS_PRIVATE_EXTERN = NO;
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 8.3;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
PROVISIONING_PROFILE_SPECIFIER = NO_SIGNING/;
|
||||
STRIP_INSTALLED_PRODUCT = NO;
|
||||
SYMROOT = "${SRCROOT}/../build";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
B24B8EFA360D695517E8E143943B3090 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 1C503D1B76EEC108FF1DBA52DF134729 /* SwiftSoup.xcconfig */;
|
||||
buildSettings = {
|
||||
"CODE_SIGN_IDENTITY[sdk=appletvos*]" = "";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "";
|
||||
"CODE_SIGN_IDENTITY[sdk=watchos*]" = "";
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
DEFINES_MODULE = YES;
|
||||
DYLIB_COMPATIBILITY_VERSION = 1;
|
||||
DYLIB_CURRENT_VERSION = 1;
|
||||
DYLIB_INSTALL_NAME_BASE = "@rpath";
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_PREFIX_HEADER = "Target Support Files/SwiftSoup/SwiftSoup-prefix.pch";
|
||||
INFOPLIST_FILE = "Target Support Files/SwiftSoup/Info.plist";
|
||||
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
|
||||
MODULEMAP_FILE = "Target Support Files/SwiftSoup/SwiftSoup.modulemap";
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
PRODUCT_NAME = SwiftSoup;
|
||||
SDKROOT = iphoneos;
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_VERSION = 3.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
VERSION_INFO_PREFIX = "";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
B8101AF3E381F735E699FD7E609A65C2 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 414B305911C034AC3D64DE0F984CDE67 /* Pods-SwiftSoup_Example.release.xcconfig */;
|
||||
buildSettings = {
|
||||
"CODE_SIGN_IDENTITY[sdk=appletvos*]" = "";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "";
|
||||
"CODE_SIGN_IDENTITY[sdk=watchos*]" = "";
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
DEFINES_MODULE = YES;
|
||||
DYLIB_COMPATIBILITY_VERSION = 1;
|
||||
DYLIB_CURRENT_VERSION = 1;
|
||||
DYLIB_INSTALL_NAME_BASE = "@rpath";
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
INFOPLIST_FILE = "Target Support Files/Pods-SwiftSoup_Example/Info.plist";
|
||||
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 8.3;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
|
||||
MACH_O_TYPE = staticlib;
|
||||
MODULEMAP_FILE = "Target Support Files/Pods-SwiftSoup_Example/Pods-SwiftSoup_Example.modulemap";
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
OTHER_LDFLAGS = "";
|
||||
OTHER_LIBTOOLFLAGS = "";
|
||||
PODS_ROOT = "$(SRCROOT)";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.${PRODUCT_NAME:rfc1034identifier}";
|
||||
PRODUCT_NAME = Pods_SwiftSoup_Example;
|
||||
SDKROOT = iphoneos;
|
||||
SKIP_INSTALL = YES;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
VERSION_INFO_PREFIX = "";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
/* End XCBuildConfiguration section */
|
||||
|
||||
/* Begin XCConfigurationList section */
|
||||
2D8E8EC45A3A1A1D94AE762CB5028504 /* Build configuration list for PBXProject "Pods" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
9E1E4E48AF2EAB23169E611BF694090A /* Debug */,
|
||||
8DED8AD26D381A6ACFF202E5217EC498 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
5FE454748DB4479DCAC40B11D8858295 /* Build configuration list for PBXNativeTarget "Pods-SwiftSoup_Example" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
0A4B4CA1C66A1B1CFE9142C386E9580A /* Debug */,
|
||||
B8101AF3E381F735E699FD7E609A65C2 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
8133BE31BA5836ECB596E3E3075C924D /* Build configuration list for PBXNativeTarget "Pods-SwiftSoup_Tests" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
8A8DB2D30A1E704BE9ECBAF9F67031A7 /* Debug */,
|
||||
3D8EED55FB8A9CA05383AE81EB1C5307 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
93611F58904508B082580ECF76CF61F2 /* Build configuration list for PBXNativeTarget "SwiftSoup" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
335273B9B316C9B70F274417280A0BAC /* Debug */,
|
||||
B24B8EFA360D695517E8E143943B3090 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
/* End XCConfigurationList section */
|
||||
};
|
||||
rootObject = D41D8CD98F00B204E9800998ECF8427E /* Project object */;
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>en</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>${EXECUTABLE_NAME}</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>${PRODUCT_BUNDLE_IDENTIFIER}</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>${PRODUCT_NAME}</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>FMWK</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0.0</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>${CURRENT_PROJECT_VERSION}</string>
|
||||
<key>NSPrincipalClass</key>
|
||||
<string></string>
|
||||
</dict>
|
||||
</plist>
|
|
@ -0,0 +1,28 @@
|
|||
# Acknowledgements
|
||||
This application makes use of the following third party libraries:
|
||||
|
||||
## SwiftSoup
|
||||
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2016 Nabil Chatbi
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
Generated by CocoaPods - https://cocoapods.org
|
|
@ -0,0 +1,60 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>PreferenceSpecifiers</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>FooterText</key>
|
||||
<string>This application makes use of the following third party libraries:</string>
|
||||
<key>Title</key>
|
||||
<string>Acknowledgements</string>
|
||||
<key>Type</key>
|
||||
<string>PSGroupSpecifier</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>FooterText</key>
|
||||
<string>MIT License
|
||||
|
||||
Copyright (c) 2016 Nabil Chatbi
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
</string>
|
||||
<key>License</key>
|
||||
<string>MIT</string>
|
||||
<key>Title</key>
|
||||
<string>SwiftSoup</string>
|
||||
<key>Type</key>
|
||||
<string>PSGroupSpecifier</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>FooterText</key>
|
||||
<string>Generated by CocoaPods - https://cocoapods.org</string>
|
||||
<key>Title</key>
|
||||
<string></string>
|
||||
<key>Type</key>
|
||||
<string>PSGroupSpecifier</string>
|
||||
</dict>
|
||||
</array>
|
||||
<key>StringsTable</key>
|
||||
<string>Acknowledgements</string>
|
||||
<key>Title</key>
|
||||
<string>Acknowledgements</string>
|
||||
</dict>
|
||||
</plist>
|
5
Example/Pods/Target Support Files/Pods-SwiftSoup_Example/Pods-SwiftSoup_Example-dummy.m
generated
Normal file
5
Example/Pods/Target Support Files/Pods-SwiftSoup_Example/Pods-SwiftSoup_Example-dummy.m
generated
Normal file
|
@ -0,0 +1,5 @@
|
|||
#import <Foundation/Foundation.h>
|
||||
@interface PodsDummy_Pods_SwiftSoup_Example : NSObject
|
||||
@end
|
||||
@implementation PodsDummy_Pods_SwiftSoup_Example
|
||||
@end
|
91
Example/Pods/Target Support Files/Pods-SwiftSoup_Example/Pods-SwiftSoup_Example-frameworks.sh
generated
Executable file
91
Example/Pods/Target Support Files/Pods-SwiftSoup_Example/Pods-SwiftSoup_Example-frameworks.sh
generated
Executable file
|
@ -0,0 +1,91 @@
|
|||
#!/bin/sh
|
||||
set -e
|
||||
|
||||
echo "mkdir -p ${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}"
|
||||
mkdir -p "${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}"
|
||||
|
||||
SWIFT_STDLIB_PATH="${DT_TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}"
|
||||
|
||||
install_framework()
|
||||
{
|
||||
if [ -r "${BUILT_PRODUCTS_DIR}/$1" ]; then
|
||||
local source="${BUILT_PRODUCTS_DIR}/$1"
|
||||
elif [ -r "${BUILT_PRODUCTS_DIR}/$(basename "$1")" ]; then
|
||||
local source="${BUILT_PRODUCTS_DIR}/$(basename "$1")"
|
||||
elif [ -r "$1" ]; then
|
||||
local source="$1"
|
||||
fi
|
||||
|
||||
local destination="${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}"
|
||||
|
||||
if [ -L "${source}" ]; then
|
||||
echo "Symlinked..."
|
||||
source="$(readlink "${source}")"
|
||||
fi
|
||||
|
||||
# use filter instead of exclude so missing patterns dont' throw errors
|
||||
echo "rsync -av --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${source}\" \"${destination}\""
|
||||
rsync -av --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${source}" "${destination}"
|
||||
|
||||
local basename
|
||||
basename="$(basename -s .framework "$1")"
|
||||
binary="${destination}/${basename}.framework/${basename}"
|
||||
if ! [ -r "$binary" ]; then
|
||||
binary="${destination}/${basename}"
|
||||
fi
|
||||
|
||||
# Strip invalid architectures so "fat" simulator / device frameworks work on device
|
||||
if [[ "$(file "$binary")" == *"dynamically linked shared library"* ]]; then
|
||||
strip_invalid_archs "$binary"
|
||||
fi
|
||||
|
||||
# Resign the code if required by the build settings to avoid unstable apps
|
||||
code_sign_if_enabled "${destination}/$(basename "$1")"
|
||||
|
||||
# Embed linked Swift runtime libraries. No longer necessary as of Xcode 7.
|
||||
if [ "${XCODE_VERSION_MAJOR}" -lt 7 ]; then
|
||||
local swift_runtime_libs
|
||||
swift_runtime_libs=$(xcrun otool -LX "$binary" | grep --color=never @rpath/libswift | sed -E s/@rpath\\/\(.+dylib\).*/\\1/g | uniq -u && exit ${PIPESTATUS[0]})
|
||||
for lib in $swift_runtime_libs; do
|
||||
echo "rsync -auv \"${SWIFT_STDLIB_PATH}/${lib}\" \"${destination}\""
|
||||
rsync -auv "${SWIFT_STDLIB_PATH}/${lib}" "${destination}"
|
||||
code_sign_if_enabled "${destination}/${lib}"
|
||||
done
|
||||
fi
|
||||
}
|
||||
|
||||
# Signs a framework with the provided identity
|
||||
code_sign_if_enabled() {
|
||||
if [ -n "${EXPANDED_CODE_SIGN_IDENTITY}" -a "${CODE_SIGNING_REQUIRED}" != "NO" -a "${CODE_SIGNING_ALLOWED}" != "NO" ]; then
|
||||
# Use the current code_sign_identitiy
|
||||
echo "Code Signing $1 with Identity ${EXPANDED_CODE_SIGN_IDENTITY_NAME}"
|
||||
echo "/usr/bin/codesign --force --sign ${EXPANDED_CODE_SIGN_IDENTITY} ${OTHER_CODE_SIGN_FLAGS} --preserve-metadata=identifier,entitlements \"$1\""
|
||||
/usr/bin/codesign --force --sign ${EXPANDED_CODE_SIGN_IDENTITY} ${OTHER_CODE_SIGN_FLAGS} --preserve-metadata=identifier,entitlements "$1"
|
||||
fi
|
||||
}
|
||||
|
||||
# Strip invalid architectures
|
||||
strip_invalid_archs() {
|
||||
binary="$1"
|
||||
# Get architectures for current file
|
||||
archs="$(lipo -info "$binary" | rev | cut -d ':' -f1 | rev)"
|
||||
stripped=""
|
||||
for arch in $archs; do
|
||||
if ! [[ "${VALID_ARCHS}" == *"$arch"* ]]; then
|
||||
# Strip non-valid architectures in-place
|
||||
lipo -remove "$arch" -output "$binary" "$binary" || exit 1
|
||||
stripped="$stripped $arch"
|
||||
fi
|
||||
done
|
||||
if [[ "$stripped" ]]; then
|
||||
echo "Stripped $binary of architectures:$stripped"
|
||||
fi
|
||||
}
|
||||
|
||||
|
||||
if [[ "$CONFIGURATION" == "Debug" ]]; then
|
||||
install_framework "$BUILT_PRODUCTS_DIR/SwiftSoup/SwiftSoup.framework"
|
||||
fi
|
||||
if [[ "$CONFIGURATION" == "Release" ]]; then
|
||||
install_framework "$BUILT_PRODUCTS_DIR/SwiftSoup/SwiftSoup.framework"
|
||||
fi
|
96
Example/Pods/Target Support Files/Pods-SwiftSoup_Example/Pods-SwiftSoup_Example-resources.sh
generated
Executable file
96
Example/Pods/Target Support Files/Pods-SwiftSoup_Example/Pods-SwiftSoup_Example-resources.sh
generated
Executable file
|
@ -0,0 +1,96 @@
|
|||
#!/bin/sh
|
||||
set -e
|
||||
|
||||
mkdir -p "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}"
|
||||
|
||||
RESOURCES_TO_COPY=${PODS_ROOT}/resources-to-copy-${TARGETNAME}.txt
|
||||
> "$RESOURCES_TO_COPY"
|
||||
|
||||
XCASSET_FILES=()
|
||||
|
||||
case "${TARGETED_DEVICE_FAMILY}" in
|
||||
1,2)
|
||||
TARGET_DEVICE_ARGS="--target-device ipad --target-device iphone"
|
||||
;;
|
||||
1)
|
||||
TARGET_DEVICE_ARGS="--target-device iphone"
|
||||
;;
|
||||
2)
|
||||
TARGET_DEVICE_ARGS="--target-device ipad"
|
||||
;;
|
||||
*)
|
||||
TARGET_DEVICE_ARGS="--target-device mac"
|
||||
;;
|
||||
esac
|
||||
|
||||
install_resource()
|
||||
{
|
||||
if [[ "$1" = /* ]] ; then
|
||||
RESOURCE_PATH="$1"
|
||||
else
|
||||
RESOURCE_PATH="${PODS_ROOT}/$1"
|
||||
fi
|
||||
if [[ ! -e "$RESOURCE_PATH" ]] ; then
|
||||
cat << EOM
|
||||
error: Resource "$RESOURCE_PATH" not found. Run 'pod install' to update the copy resources script.
|
||||
EOM
|
||||
exit 1
|
||||
fi
|
||||
case $RESOURCE_PATH in
|
||||
*.storyboard)
|
||||
echo "ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile ${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .storyboard`.storyboardc $RESOURCE_PATH --sdk ${SDKROOT} ${TARGET_DEVICE_ARGS}"
|
||||
ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .storyboard`.storyboardc" "$RESOURCE_PATH" --sdk "${SDKROOT}" ${TARGET_DEVICE_ARGS}
|
||||
;;
|
||||
*.xib)
|
||||
echo "ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile ${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .xib`.nib $RESOURCE_PATH --sdk ${SDKROOT} ${TARGET_DEVICE_ARGS}"
|
||||
ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .xib`.nib" "$RESOURCE_PATH" --sdk "${SDKROOT}" ${TARGET_DEVICE_ARGS}
|
||||
;;
|
||||
*.framework)
|
||||
echo "mkdir -p ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}"
|
||||
mkdir -p "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}"
|
||||
echo "rsync -av $RESOURCE_PATH ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}"
|
||||
rsync -av "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}"
|
||||
;;
|
||||
*.xcdatamodel)
|
||||
echo "xcrun momc \"$RESOURCE_PATH\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH"`.mom\""
|
||||
xcrun momc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcdatamodel`.mom"
|
||||
;;
|
||||
*.xcdatamodeld)
|
||||
echo "xcrun momc \"$RESOURCE_PATH\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcdatamodeld`.momd\""
|
||||
xcrun momc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcdatamodeld`.momd"
|
||||
;;
|
||||
*.xcmappingmodel)
|
||||
echo "xcrun mapc \"$RESOURCE_PATH\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcmappingmodel`.cdm\""
|
||||
xcrun mapc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcmappingmodel`.cdm"
|
||||
;;
|
||||
*.xcassets)
|
||||
ABSOLUTE_XCASSET_FILE="$RESOURCE_PATH"
|
||||
XCASSET_FILES+=("$ABSOLUTE_XCASSET_FILE")
|
||||
;;
|
||||
*)
|
||||
echo "$RESOURCE_PATH"
|
||||
echo "$RESOURCE_PATH" >> "$RESOURCES_TO_COPY"
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
mkdir -p "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}"
|
||||
rsync -avr --copy-links --no-relative --exclude '*/.svn/*' --files-from="$RESOURCES_TO_COPY" / "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}"
|
||||
if [[ "${ACTION}" == "install" ]] && [[ "${SKIP_INSTALL}" == "NO" ]]; then
|
||||
mkdir -p "${INSTALL_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}"
|
||||
rsync -avr --copy-links --no-relative --exclude '*/.svn/*' --files-from="$RESOURCES_TO_COPY" / "${INSTALL_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}"
|
||||
fi
|
||||
rm -f "$RESOURCES_TO_COPY"
|
||||
|
||||
if [[ -n "${WRAPPER_EXTENSION}" ]] && [ "`xcrun --find actool`" ] && [ -n "$XCASSET_FILES" ]
|
||||
then
|
||||
# Find all other xcassets (this unfortunately includes those of path pods and other targets).
|
||||
OTHER_XCASSETS=$(find "$PWD" -iname "*.xcassets" -type d)
|
||||
while read line; do
|
||||
if [[ $line != "${PODS_ROOT}*" ]]; then
|
||||
XCASSET_FILES+=("$line")
|
||||
fi
|
||||
done <<<"$OTHER_XCASSETS"
|
||||
|
||||
printf "%s\0" "${XCASSET_FILES[@]}" | xargs -0 xcrun actool --output-format human-readable-text --notices --warnings --platform "${PLATFORM_NAME}" --minimum-deployment-target "${!DEPLOYMENT_TARGET_SETTING_NAME}" ${TARGET_DEVICE_ARGS} --compress-pngs --compile "${BUILT_PRODUCTS_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}"
|
||||
fi
|
8
Example/Pods/Target Support Files/Pods-SwiftSoup_Example/Pods-SwiftSoup_Example-umbrella.h
generated
Normal file
8
Example/Pods/Target Support Files/Pods-SwiftSoup_Example/Pods-SwiftSoup_Example-umbrella.h
generated
Normal file
|
@ -0,0 +1,8 @@
|
|||
#ifdef __OBJC__
|
||||
#import <UIKit/UIKit.h>
|
||||
#endif
|
||||
|
||||
|
||||
FOUNDATION_EXPORT double Pods_SwiftSoup_ExampleVersionNumber;
|
||||
FOUNDATION_EXPORT const unsigned char Pods_SwiftSoup_ExampleVersionString[];
|
||||
|
11
Example/Pods/Target Support Files/Pods-SwiftSoup_Example/Pods-SwiftSoup_Example.debug.xcconfig
generated
Normal file
11
Example/Pods/Target Support Files/Pods-SwiftSoup_Example/Pods-SwiftSoup_Example.debug.xcconfig
generated
Normal file
|
@ -0,0 +1,11 @@
|
|||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES
|
||||
EMBEDDED_CONTENT_CONTAINS_SWIFT = YES
|
||||
FRAMEWORK_SEARCH_PATHS = $(inherited) "$PODS_CONFIGURATION_BUILD_DIR/SwiftSoup"
|
||||
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
|
||||
LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks'
|
||||
OTHER_CFLAGS = $(inherited) -iquote "$PODS_CONFIGURATION_BUILD_DIR/SwiftSoup/SwiftSoup.framework/Headers"
|
||||
OTHER_LDFLAGS = $(inherited) -framework "SwiftSoup"
|
||||
OTHER_SWIFT_FLAGS = $(inherited) "-D" "COCOAPODS"
|
||||
PODS_BUILD_DIR = $BUILD_DIR
|
||||
PODS_CONFIGURATION_BUILD_DIR = $PODS_BUILD_DIR/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
|
||||
PODS_ROOT = ${SRCROOT}/Pods
|
6
Example/Pods/Target Support Files/Pods-SwiftSoup_Example/Pods-SwiftSoup_Example.modulemap
generated
Normal file
6
Example/Pods/Target Support Files/Pods-SwiftSoup_Example/Pods-SwiftSoup_Example.modulemap
generated
Normal file
|
@ -0,0 +1,6 @@
|
|||
framework module Pods_SwiftSoup_Example {
|
||||
umbrella header "Pods-SwiftSoup_Example-umbrella.h"
|
||||
|
||||
export *
|
||||
module * { export * }
|
||||
}
|
11
Example/Pods/Target Support Files/Pods-SwiftSoup_Example/Pods-SwiftSoup_Example.release.xcconfig
generated
Normal file
11
Example/Pods/Target Support Files/Pods-SwiftSoup_Example/Pods-SwiftSoup_Example.release.xcconfig
generated
Normal file
|
@ -0,0 +1,11 @@
|
|||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES
|
||||
EMBEDDED_CONTENT_CONTAINS_SWIFT = YES
|
||||
FRAMEWORK_SEARCH_PATHS = $(inherited) "$PODS_CONFIGURATION_BUILD_DIR/SwiftSoup"
|
||||
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
|
||||
LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks'
|
||||
OTHER_CFLAGS = $(inherited) -iquote "$PODS_CONFIGURATION_BUILD_DIR/SwiftSoup/SwiftSoup.framework/Headers"
|
||||
OTHER_LDFLAGS = $(inherited) -framework "SwiftSoup"
|
||||
OTHER_SWIFT_FLAGS = $(inherited) "-D" "COCOAPODS"
|
||||
PODS_BUILD_DIR = $BUILD_DIR
|
||||
PODS_CONFIGURATION_BUILD_DIR = $PODS_BUILD_DIR/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
|
||||
PODS_ROOT = ${SRCROOT}/Pods
|
|
@ -0,0 +1,26 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>en</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>${EXECUTABLE_NAME}</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>${PRODUCT_BUNDLE_IDENTIFIER}</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>${PRODUCT_NAME}</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>FMWK</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0.0</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>${CURRENT_PROJECT_VERSION}</string>
|
||||
<key>NSPrincipalClass</key>
|
||||
<string></string>
|
||||
</dict>
|
||||
</plist>
|
|
@ -0,0 +1,3 @@
|
|||
# Acknowledgements
|
||||
This application makes use of the following third party libraries:
|
||||
Generated by CocoaPods - https://cocoapods.org
|
29
Example/Pods/Target Support Files/Pods-SwiftSoup_Tests/Pods-SwiftSoup_Tests-acknowledgements.plist
generated
Normal file
29
Example/Pods/Target Support Files/Pods-SwiftSoup_Tests/Pods-SwiftSoup_Tests-acknowledgements.plist
generated
Normal file
|
@ -0,0 +1,29 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>PreferenceSpecifiers</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>FooterText</key>
|
||||
<string>This application makes use of the following third party libraries:</string>
|
||||
<key>Title</key>
|
||||
<string>Acknowledgements</string>
|
||||
<key>Type</key>
|
||||
<string>PSGroupSpecifier</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>FooterText</key>
|
||||
<string>Generated by CocoaPods - https://cocoapods.org</string>
|
||||
<key>Title</key>
|
||||
<string></string>
|
||||
<key>Type</key>
|
||||
<string>PSGroupSpecifier</string>
|
||||
</dict>
|
||||
</array>
|
||||
<key>StringsTable</key>
|
||||
<string>Acknowledgements</string>
|
||||
<key>Title</key>
|
||||
<string>Acknowledgements</string>
|
||||
</dict>
|
||||
</plist>
|
5
Example/Pods/Target Support Files/Pods-SwiftSoup_Tests/Pods-SwiftSoup_Tests-dummy.m
generated
Normal file
5
Example/Pods/Target Support Files/Pods-SwiftSoup_Tests/Pods-SwiftSoup_Tests-dummy.m
generated
Normal file
|
@ -0,0 +1,5 @@
|
|||
#import <Foundation/Foundation.h>
|
||||
@interface PodsDummy_Pods_SwiftSoup_Tests : NSObject
|
||||
@end
|
||||
@implementation PodsDummy_Pods_SwiftSoup_Tests
|
||||
@end
|
84
Example/Pods/Target Support Files/Pods-SwiftSoup_Tests/Pods-SwiftSoup_Tests-frameworks.sh
generated
Executable file
84
Example/Pods/Target Support Files/Pods-SwiftSoup_Tests/Pods-SwiftSoup_Tests-frameworks.sh
generated
Executable file
|
@ -0,0 +1,84 @@
|
|||
#!/bin/sh
|
||||
set -e
|
||||
|
||||
echo "mkdir -p ${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}"
|
||||
mkdir -p "${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}"
|
||||
|
||||
SWIFT_STDLIB_PATH="${DT_TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}"
|
||||
|
||||
install_framework()
|
||||
{
|
||||
if [ -r "${BUILT_PRODUCTS_DIR}/$1" ]; then
|
||||
local source="${BUILT_PRODUCTS_DIR}/$1"
|
||||
elif [ -r "${BUILT_PRODUCTS_DIR}/$(basename "$1")" ]; then
|
||||
local source="${BUILT_PRODUCTS_DIR}/$(basename "$1")"
|
||||
elif [ -r "$1" ]; then
|
||||
local source="$1"
|
||||
fi
|
||||
|
||||
local destination="${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}"
|
||||
|
||||
if [ -L "${source}" ]; then
|
||||
echo "Symlinked..."
|
||||
source="$(readlink "${source}")"
|
||||
fi
|
||||
|
||||
# use filter instead of exclude so missing patterns dont' throw errors
|
||||
echo "rsync -av --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${source}\" \"${destination}\""
|
||||
rsync -av --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${source}" "${destination}"
|
||||
|
||||
local basename
|
||||
basename="$(basename -s .framework "$1")"
|
||||
binary="${destination}/${basename}.framework/${basename}"
|
||||
if ! [ -r "$binary" ]; then
|
||||
binary="${destination}/${basename}"
|
||||
fi
|
||||
|
||||
# Strip invalid architectures so "fat" simulator / device frameworks work on device
|
||||
if [[ "$(file "$binary")" == *"dynamically linked shared library"* ]]; then
|
||||
strip_invalid_archs "$binary"
|
||||
fi
|
||||
|
||||
# Resign the code if required by the build settings to avoid unstable apps
|
||||
code_sign_if_enabled "${destination}/$(basename "$1")"
|
||||
|
||||
# Embed linked Swift runtime libraries. No longer necessary as of Xcode 7.
|
||||
if [ "${XCODE_VERSION_MAJOR}" -lt 7 ]; then
|
||||
local swift_runtime_libs
|
||||
swift_runtime_libs=$(xcrun otool -LX "$binary" | grep --color=never @rpath/libswift | sed -E s/@rpath\\/\(.+dylib\).*/\\1/g | uniq -u && exit ${PIPESTATUS[0]})
|
||||
for lib in $swift_runtime_libs; do
|
||||
echo "rsync -auv \"${SWIFT_STDLIB_PATH}/${lib}\" \"${destination}\""
|
||||
rsync -auv "${SWIFT_STDLIB_PATH}/${lib}" "${destination}"
|
||||
code_sign_if_enabled "${destination}/${lib}"
|
||||
done
|
||||
fi
|
||||
}
|
||||
|
||||
# Signs a framework with the provided identity
|
||||
code_sign_if_enabled() {
|
||||
if [ -n "${EXPANDED_CODE_SIGN_IDENTITY}" -a "${CODE_SIGNING_REQUIRED}" != "NO" -a "${CODE_SIGNING_ALLOWED}" != "NO" ]; then
|
||||
# Use the current code_sign_identitiy
|
||||
echo "Code Signing $1 with Identity ${EXPANDED_CODE_SIGN_IDENTITY_NAME}"
|
||||
echo "/usr/bin/codesign --force --sign ${EXPANDED_CODE_SIGN_IDENTITY} ${OTHER_CODE_SIGN_FLAGS} --preserve-metadata=identifier,entitlements \"$1\""
|
||||
/usr/bin/codesign --force --sign ${EXPANDED_CODE_SIGN_IDENTITY} ${OTHER_CODE_SIGN_FLAGS} --preserve-metadata=identifier,entitlements "$1"
|
||||
fi
|
||||
}
|
||||
|
||||
# Strip invalid architectures
|
||||
strip_invalid_archs() {
|
||||
binary="$1"
|
||||
# Get architectures for current file
|
||||
archs="$(lipo -info "$binary" | rev | cut -d ':' -f1 | rev)"
|
||||
stripped=""
|
||||
for arch in $archs; do
|
||||
if ! [[ "${VALID_ARCHS}" == *"$arch"* ]]; then
|
||||
# Strip non-valid architectures in-place
|
||||
lipo -remove "$arch" -output "$binary" "$binary" || exit 1
|
||||
stripped="$stripped $arch"
|
||||
fi
|
||||
done
|
||||
if [[ "$stripped" ]]; then
|
||||
echo "Stripped $binary of architectures:$stripped"
|
||||
fi
|
||||
}
|
||||
|
96
Example/Pods/Target Support Files/Pods-SwiftSoup_Tests/Pods-SwiftSoup_Tests-resources.sh
generated
Executable file
96
Example/Pods/Target Support Files/Pods-SwiftSoup_Tests/Pods-SwiftSoup_Tests-resources.sh
generated
Executable file
|
@ -0,0 +1,96 @@
|
|||
#!/bin/sh
|
||||
set -e
|
||||
|
||||
mkdir -p "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}"
|
||||
|
||||
RESOURCES_TO_COPY=${PODS_ROOT}/resources-to-copy-${TARGETNAME}.txt
|
||||
> "$RESOURCES_TO_COPY"
|
||||
|
||||
XCASSET_FILES=()
|
||||
|
||||
case "${TARGETED_DEVICE_FAMILY}" in
|
||||
1,2)
|
||||
TARGET_DEVICE_ARGS="--target-device ipad --target-device iphone"
|
||||
;;
|
||||
1)
|
||||
TARGET_DEVICE_ARGS="--target-device iphone"
|
||||
;;
|
||||
2)
|
||||
TARGET_DEVICE_ARGS="--target-device ipad"
|
||||
;;
|
||||
*)
|
||||
TARGET_DEVICE_ARGS="--target-device mac"
|
||||
;;
|
||||
esac
|
||||
|
||||
install_resource()
|
||||
{
|
||||
if [[ "$1" = /* ]] ; then
|
||||
RESOURCE_PATH="$1"
|
||||
else
|
||||
RESOURCE_PATH="${PODS_ROOT}/$1"
|
||||
fi
|
||||
if [[ ! -e "$RESOURCE_PATH" ]] ; then
|
||||
cat << EOM
|
||||
error: Resource "$RESOURCE_PATH" not found. Run 'pod install' to update the copy resources script.
|
||||
EOM
|
||||
exit 1
|
||||
fi
|
||||
case $RESOURCE_PATH in
|
||||
*.storyboard)
|
||||
echo "ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile ${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .storyboard`.storyboardc $RESOURCE_PATH --sdk ${SDKROOT} ${TARGET_DEVICE_ARGS}"
|
||||
ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .storyboard`.storyboardc" "$RESOURCE_PATH" --sdk "${SDKROOT}" ${TARGET_DEVICE_ARGS}
|
||||
;;
|
||||
*.xib)
|
||||
echo "ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile ${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .xib`.nib $RESOURCE_PATH --sdk ${SDKROOT} ${TARGET_DEVICE_ARGS}"
|
||||
ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .xib`.nib" "$RESOURCE_PATH" --sdk "${SDKROOT}" ${TARGET_DEVICE_ARGS}
|
||||
;;
|
||||
*.framework)
|
||||
echo "mkdir -p ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}"
|
||||
mkdir -p "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}"
|
||||
echo "rsync -av $RESOURCE_PATH ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}"
|
||||
rsync -av "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}"
|
||||
;;
|
||||
*.xcdatamodel)
|
||||
echo "xcrun momc \"$RESOURCE_PATH\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH"`.mom\""
|
||||
xcrun momc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcdatamodel`.mom"
|
||||
;;
|
||||
*.xcdatamodeld)
|
||||
echo "xcrun momc \"$RESOURCE_PATH\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcdatamodeld`.momd\""
|
||||
xcrun momc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcdatamodeld`.momd"
|
||||
;;
|
||||
*.xcmappingmodel)
|
||||
echo "xcrun mapc \"$RESOURCE_PATH\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcmappingmodel`.cdm\""
|
||||
xcrun mapc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcmappingmodel`.cdm"
|
||||
;;
|
||||
*.xcassets)
|
||||
ABSOLUTE_XCASSET_FILE="$RESOURCE_PATH"
|
||||
XCASSET_FILES+=("$ABSOLUTE_XCASSET_FILE")
|
||||
;;
|
||||
*)
|
||||
echo "$RESOURCE_PATH"
|
||||
echo "$RESOURCE_PATH" >> "$RESOURCES_TO_COPY"
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
mkdir -p "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}"
|
||||
rsync -avr --copy-links --no-relative --exclude '*/.svn/*' --files-from="$RESOURCES_TO_COPY" / "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}"
|
||||
if [[ "${ACTION}" == "install" ]] && [[ "${SKIP_INSTALL}" == "NO" ]]; then
|
||||
mkdir -p "${INSTALL_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}"
|
||||
rsync -avr --copy-links --no-relative --exclude '*/.svn/*' --files-from="$RESOURCES_TO_COPY" / "${INSTALL_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}"
|
||||
fi
|
||||
rm -f "$RESOURCES_TO_COPY"
|
||||
|
||||
if [[ -n "${WRAPPER_EXTENSION}" ]] && [ "`xcrun --find actool`" ] && [ -n "$XCASSET_FILES" ]
|
||||
then
|
||||
# Find all other xcassets (this unfortunately includes those of path pods and other targets).
|
||||
OTHER_XCASSETS=$(find "$PWD" -iname "*.xcassets" -type d)
|
||||
while read line; do
|
||||
if [[ $line != "${PODS_ROOT}*" ]]; then
|
||||
XCASSET_FILES+=("$line")
|
||||
fi
|
||||
done <<<"$OTHER_XCASSETS"
|
||||
|
||||
printf "%s\0" "${XCASSET_FILES[@]}" | xargs -0 xcrun actool --output-format human-readable-text --notices --warnings --platform "${PLATFORM_NAME}" --minimum-deployment-target "${!DEPLOYMENT_TARGET_SETTING_NAME}" ${TARGET_DEVICE_ARGS} --compress-pngs --compile "${BUILT_PRODUCTS_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}"
|
||||
fi
|
8
Example/Pods/Target Support Files/Pods-SwiftSoup_Tests/Pods-SwiftSoup_Tests-umbrella.h
generated
Normal file
8
Example/Pods/Target Support Files/Pods-SwiftSoup_Tests/Pods-SwiftSoup_Tests-umbrella.h
generated
Normal file
|
@ -0,0 +1,8 @@
|
|||
#ifdef __OBJC__
|
||||
#import <UIKit/UIKit.h>
|
||||
#endif
|
||||
|
||||
|
||||
FOUNDATION_EXPORT double Pods_SwiftSoup_TestsVersionNumber;
|
||||
FOUNDATION_EXPORT const unsigned char Pods_SwiftSoup_TestsVersionString[];
|
||||
|
8
Example/Pods/Target Support Files/Pods-SwiftSoup_Tests/Pods-SwiftSoup_Tests.debug.xcconfig
generated
Normal file
8
Example/Pods/Target Support Files/Pods-SwiftSoup_Tests/Pods-SwiftSoup_Tests.debug.xcconfig
generated
Normal file
|
@ -0,0 +1,8 @@
|
|||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = NO
|
||||
FRAMEWORK_SEARCH_PATHS = $(inherited) "$PODS_CONFIGURATION_BUILD_DIR/SwiftSoup"
|
||||
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
|
||||
LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks'
|
||||
OTHER_CFLAGS = $(inherited) -iquote "$PODS_CONFIGURATION_BUILD_DIR/SwiftSoup/SwiftSoup.framework/Headers"
|
||||
PODS_BUILD_DIR = $BUILD_DIR
|
||||
PODS_CONFIGURATION_BUILD_DIR = $PODS_BUILD_DIR/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
|
||||
PODS_ROOT = ${SRCROOT}/Pods
|
6
Example/Pods/Target Support Files/Pods-SwiftSoup_Tests/Pods-SwiftSoup_Tests.modulemap
generated
Normal file
6
Example/Pods/Target Support Files/Pods-SwiftSoup_Tests/Pods-SwiftSoup_Tests.modulemap
generated
Normal file
|
@ -0,0 +1,6 @@
|
|||
framework module Pods_SwiftSoup_Tests {
|
||||
umbrella header "Pods-SwiftSoup_Tests-umbrella.h"
|
||||
|
||||
export *
|
||||
module * { export * }
|
||||
}
|
8
Example/Pods/Target Support Files/Pods-SwiftSoup_Tests/Pods-SwiftSoup_Tests.release.xcconfig
generated
Normal file
8
Example/Pods/Target Support Files/Pods-SwiftSoup_Tests/Pods-SwiftSoup_Tests.release.xcconfig
generated
Normal file
|
@ -0,0 +1,8 @@
|
|||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = NO
|
||||
FRAMEWORK_SEARCH_PATHS = $(inherited) "$PODS_CONFIGURATION_BUILD_DIR/SwiftSoup"
|
||||
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
|
||||
LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks'
|
||||
OTHER_CFLAGS = $(inherited) -iquote "$PODS_CONFIGURATION_BUILD_DIR/SwiftSoup/SwiftSoup.framework/Headers"
|
||||
PODS_BUILD_DIR = $BUILD_DIR
|
||||
PODS_CONFIGURATION_BUILD_DIR = $PODS_BUILD_DIR/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
|
||||
PODS_ROOT = ${SRCROOT}/Pods
|
|
@ -0,0 +1,26 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>en</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>${EXECUTABLE_NAME}</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>${PRODUCT_BUNDLE_IDENTIFIER}</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>${PRODUCT_NAME}</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>FMWK</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>0.1.0</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>${CURRENT_PROJECT_VERSION}</string>
|
||||
<key>NSPrincipalClass</key>
|
||||
<string></string>
|
||||
</dict>
|
||||
</plist>
|
|
@ -0,0 +1,5 @@
|
|||
#import <Foundation/Foundation.h>
|
||||
@interface PodsDummy_SwiftSoup : NSObject
|
||||
@end
|
||||
@implementation PodsDummy_SwiftSoup
|
||||
@end
|
|
@ -0,0 +1,4 @@
|
|||
#ifdef __OBJC__
|
||||
#import <UIKit/UIKit.h>
|
||||
#endif
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
#ifdef __OBJC__
|
||||
#import <UIKit/UIKit.h>
|
||||
#endif
|
||||
|
||||
#import "SwiftSoup.h"
|
||||
|
||||
FOUNDATION_EXPORT double SwiftSoupVersionNumber;
|
||||
FOUNDATION_EXPORT const unsigned char SwiftSoupVersionString[];
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
framework module SwiftSoup {
|
||||
umbrella header "SwiftSoup-umbrella.h"
|
||||
|
||||
export *
|
||||
module * { export * }
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
CONFIGURATION_BUILD_DIR = $PODS_CONFIGURATION_BUILD_DIR/SwiftSoup
|
||||
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
|
||||
HEADER_SEARCH_PATHS = "${PODS_ROOT}/Headers/Private" "${PODS_ROOT}/Headers/Public"
|
||||
OTHER_SWIFT_FLAGS = $(inherited) "-D" "COCOAPODS"
|
||||
PODS_BUILD_DIR = $BUILD_DIR
|
||||
PODS_CONFIGURATION_BUILD_DIR = $PODS_BUILD_DIR/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
|
||||
PODS_ROOT = ${SRCROOT}
|
||||
PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier}
|
||||
SKIP_INSTALL = YES
|
|
@ -0,0 +1,593 @@
|
|||
// !$*UTF8*$!
|
||||
{
|
||||
archiveVersion = 1;
|
||||
classes = {
|
||||
};
|
||||
objectVersion = 46;
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
3BEB5E7C92EAD422B8D3900B /* Pods_SwiftSoup_Tests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C9492EBFCE48537FB0DFA7C4 /* Pods_SwiftSoup_Tests.framework */; };
|
||||
468CE1131597E3F228F2411F /* Pods_SwiftSoup_Example.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5E8D0E6394AD70A96FD2231F /* Pods_SwiftSoup_Example.framework */; };
|
||||
607FACD61AFB9204008FA782 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 607FACD51AFB9204008FA782 /* AppDelegate.swift */; };
|
||||
607FACD81AFB9204008FA782 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 607FACD71AFB9204008FA782 /* ViewController.swift */; };
|
||||
607FACDB1AFB9204008FA782 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 607FACD91AFB9204008FA782 /* Main.storyboard */; };
|
||||
607FACDD1AFB9204008FA782 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 607FACDC1AFB9204008FA782 /* Images.xcassets */; };
|
||||
607FACE01AFB9204008FA782 /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 607FACDE1AFB9204008FA782 /* LaunchScreen.xib */; };
|
||||
607FACEC1AFB9204008FA782 /* Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 607FACEB1AFB9204008FA782 /* Tests.swift */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
607FACE61AFB9204008FA782 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 607FACC81AFB9204008FA782 /* Project object */;
|
||||
proxyType = 1;
|
||||
remoteGlobalIDString = 607FACCF1AFB9204008FA782;
|
||||
remoteInfo = SwiftSoup;
|
||||
};
|
||||
/* End PBXContainerItemProxy section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
164E36D1491528A4BE6103E6 /* Pods-SwiftSoup_Example.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SwiftSoup_Example.debug.xcconfig"; path = "Pods/Target Support Files/Pods-SwiftSoup_Example/Pods-SwiftSoup_Example.debug.xcconfig"; sourceTree = "<group>"; };
|
||||
314E985F9DA2FFD91E54F5CA /* README.md */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = net.daringfireball.markdown; name = README.md; path = ../README.md; sourceTree = "<group>"; };
|
||||
3E29D6B2C06B70F9343156AB /* Pods-SwiftSoup_Example.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SwiftSoup_Example.release.xcconfig"; path = "Pods/Target Support Files/Pods-SwiftSoup_Example/Pods-SwiftSoup_Example.release.xcconfig"; sourceTree = "<group>"; };
|
||||
459BB90FB1836BA98A7A3928 /* Pods-SwiftSoup_Tests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SwiftSoup_Tests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-SwiftSoup_Tests/Pods-SwiftSoup_Tests.debug.xcconfig"; sourceTree = "<group>"; };
|
||||
5E8D0E6394AD70A96FD2231F /* Pods_SwiftSoup_Example.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_SwiftSoup_Example.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
607FACD01AFB9204008FA782 /* SwiftSoup_Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SwiftSoup_Example.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
607FACD41AFB9204008FA782 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
607FACD51AFB9204008FA782 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
||||
607FACD71AFB9204008FA782 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = "<group>"; };
|
||||
607FACDA1AFB9204008FA782 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
|
||||
607FACDC1AFB9204008FA782 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = "<group>"; };
|
||||
607FACDF1AFB9204008FA782 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/LaunchScreen.xib; sourceTree = "<group>"; };
|
||||
607FACE51AFB9204008FA782 /* SwiftSoup_Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SwiftSoup_Tests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
607FACEA1AFB9204008FA782 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
607FACEB1AFB9204008FA782 /* Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tests.swift; sourceTree = "<group>"; };
|
||||
70D63D355EA1B8EC6DAD8A06 /* SwiftSoup.podspec */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; name = SwiftSoup.podspec; path = ../SwiftSoup.podspec; sourceTree = "<group>"; };
|
||||
C9492EBFCE48537FB0DFA7C4 /* Pods_SwiftSoup_Tests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_SwiftSoup_Tests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
DD5C07633B805A60D086B9BF /* LICENSE */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; name = LICENSE; path = ../LICENSE; sourceTree = "<group>"; };
|
||||
FBE53E306FAF4C428827FFD9 /* Pods-SwiftSoup_Tests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SwiftSoup_Tests.release.xcconfig"; path = "Pods/Target Support Files/Pods-SwiftSoup_Tests/Pods-SwiftSoup_Tests.release.xcconfig"; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
607FACCD1AFB9204008FA782 /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
468CE1131597E3F228F2411F /* Pods_SwiftSoup_Example.framework in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
607FACE21AFB9204008FA782 /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
3BEB5E7C92EAD422B8D3900B /* Pods_SwiftSoup_Tests.framework in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXFrameworksBuildPhase section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
607FACC71AFB9204008FA782 = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
607FACF51AFB993E008FA782 /* Podspec Metadata */,
|
||||
607FACD21AFB9204008FA782 /* Example for SwiftSoup */,
|
||||
607FACE81AFB9204008FA782 /* Tests */,
|
||||
607FACD11AFB9204008FA782 /* Products */,
|
||||
63E804EECBB4A8465A2EB234 /* Pods */,
|
||||
6E66337843F9E3A4A1618B4D /* Frameworks */,
|
||||
);
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
607FACD11AFB9204008FA782 /* Products */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
607FACD01AFB9204008FA782 /* SwiftSoup_Example.app */,
|
||||
607FACE51AFB9204008FA782 /* SwiftSoup_Tests.xctest */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
607FACD21AFB9204008FA782 /* Example for SwiftSoup */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
607FACD51AFB9204008FA782 /* AppDelegate.swift */,
|
||||
607FACD71AFB9204008FA782 /* ViewController.swift */,
|
||||
607FACD91AFB9204008FA782 /* Main.storyboard */,
|
||||
607FACDC1AFB9204008FA782 /* Images.xcassets */,
|
||||
607FACDE1AFB9204008FA782 /* LaunchScreen.xib */,
|
||||
607FACD31AFB9204008FA782 /* Supporting Files */,
|
||||
);
|
||||
name = "Example for SwiftSoup";
|
||||
path = SwiftSoup;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
607FACD31AFB9204008FA782 /* Supporting Files */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
607FACD41AFB9204008FA782 /* Info.plist */,
|
||||
);
|
||||
name = "Supporting Files";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
607FACE81AFB9204008FA782 /* Tests */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
607FACEB1AFB9204008FA782 /* Tests.swift */,
|
||||
607FACE91AFB9204008FA782 /* Supporting Files */,
|
||||
);
|
||||
path = Tests;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
607FACE91AFB9204008FA782 /* Supporting Files */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
607FACEA1AFB9204008FA782 /* Info.plist */,
|
||||
);
|
||||
name = "Supporting Files";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
607FACF51AFB993E008FA782 /* Podspec Metadata */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
70D63D355EA1B8EC6DAD8A06 /* SwiftSoup.podspec */,
|
||||
314E985F9DA2FFD91E54F5CA /* README.md */,
|
||||
DD5C07633B805A60D086B9BF /* LICENSE */,
|
||||
);
|
||||
name = "Podspec Metadata";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
63E804EECBB4A8465A2EB234 /* Pods */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
164E36D1491528A4BE6103E6 /* Pods-SwiftSoup_Example.debug.xcconfig */,
|
||||
3E29D6B2C06B70F9343156AB /* Pods-SwiftSoup_Example.release.xcconfig */,
|
||||
459BB90FB1836BA98A7A3928 /* Pods-SwiftSoup_Tests.debug.xcconfig */,
|
||||
FBE53E306FAF4C428827FFD9 /* Pods-SwiftSoup_Tests.release.xcconfig */,
|
||||
);
|
||||
name = Pods;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
6E66337843F9E3A4A1618B4D /* Frameworks */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
5E8D0E6394AD70A96FD2231F /* Pods_SwiftSoup_Example.framework */,
|
||||
C9492EBFCE48537FB0DFA7C4 /* Pods_SwiftSoup_Tests.framework */,
|
||||
);
|
||||
name = Frameworks;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
607FACCF1AFB9204008FA782 /* SwiftSoup_Example */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 607FACEF1AFB9204008FA782 /* Build configuration list for PBXNativeTarget "SwiftSoup_Example" */;
|
||||
buildPhases = (
|
||||
A0119AC35661ED0A1BD7913B /* [CP] Check Pods Manifest.lock */,
|
||||
607FACCC1AFB9204008FA782 /* Sources */,
|
||||
607FACCD1AFB9204008FA782 /* Frameworks */,
|
||||
607FACCE1AFB9204008FA782 /* Resources */,
|
||||
D9F7845A52211017AECDD30F /* [CP] Embed Pods Frameworks */,
|
||||
4680F813A9BB67329666E939 /* [CP] Copy Pods Resources */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
name = SwiftSoup_Example;
|
||||
productName = SwiftSoup;
|
||||
productReference = 607FACD01AFB9204008FA782 /* SwiftSoup_Example.app */;
|
||||
productType = "com.apple.product-type.application";
|
||||
};
|
||||
607FACE41AFB9204008FA782 /* SwiftSoup_Tests */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 607FACF21AFB9204008FA782 /* Build configuration list for PBXNativeTarget "SwiftSoup_Tests" */;
|
||||
buildPhases = (
|
||||
2E0348C7E1745F405E4517D3 /* [CP] Check Pods Manifest.lock */,
|
||||
607FACE11AFB9204008FA782 /* Sources */,
|
||||
607FACE21AFB9204008FA782 /* Frameworks */,
|
||||
607FACE31AFB9204008FA782 /* Resources */,
|
||||
58E76CD2ECC9108A3405EC58 /* [CP] Embed Pods Frameworks */,
|
||||
8B4EF740BFDB6721B4EE4F7E /* [CP] Copy Pods Resources */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
607FACE71AFB9204008FA782 /* PBXTargetDependency */,
|
||||
);
|
||||
name = SwiftSoup_Tests;
|
||||
productName = Tests;
|
||||
productReference = 607FACE51AFB9204008FA782 /* SwiftSoup_Tests.xctest */;
|
||||
productType = "com.apple.product-type.bundle.unit-test";
|
||||
};
|
||||
/* End PBXNativeTarget section */
|
||||
|
||||
/* Begin PBXProject section */
|
||||
607FACC81AFB9204008FA782 /* Project object */ = {
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
LastSwiftUpdateCheck = 0720;
|
||||
LastUpgradeCheck = 0810;
|
||||
ORGANIZATIONNAME = CocoaPods;
|
||||
TargetAttributes = {
|
||||
607FACCF1AFB9204008FA782 = {
|
||||
CreatedOnToolsVersion = 6.3.1;
|
||||
LastSwiftMigration = 0810;
|
||||
};
|
||||
607FACE41AFB9204008FA782 = {
|
||||
CreatedOnToolsVersion = 6.3.1;
|
||||
LastSwiftMigration = 0810;
|
||||
TestTargetID = 607FACCF1AFB9204008FA782;
|
||||
};
|
||||
};
|
||||
};
|
||||
buildConfigurationList = 607FACCB1AFB9204008FA782 /* Build configuration list for PBXProject "SwiftSoup" */;
|
||||
compatibilityVersion = "Xcode 3.2";
|
||||
developmentRegion = English;
|
||||
hasScannedForEncodings = 0;
|
||||
knownRegions = (
|
||||
en,
|
||||
Base,
|
||||
);
|
||||
mainGroup = 607FACC71AFB9204008FA782;
|
||||
productRefGroup = 607FACD11AFB9204008FA782 /* Products */;
|
||||
projectDirPath = "";
|
||||
projectRoot = "";
|
||||
targets = (
|
||||
607FACCF1AFB9204008FA782 /* SwiftSoup_Example */,
|
||||
607FACE41AFB9204008FA782 /* SwiftSoup_Tests */,
|
||||
);
|
||||
};
|
||||
/* End PBXProject section */
|
||||
|
||||
/* Begin PBXResourcesBuildPhase section */
|
||||
607FACCE1AFB9204008FA782 /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
607FACDB1AFB9204008FA782 /* Main.storyboard in Resources */,
|
||||
607FACE01AFB9204008FA782 /* LaunchScreen.xib in Resources */,
|
||||
607FACDD1AFB9204008FA782 /* Images.xcassets in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
607FACE31AFB9204008FA782 /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXResourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXShellScriptBuildPhase section */
|
||||
2E0348C7E1745F405E4517D3 /* [CP] Check Pods Manifest.lock */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
name = "[CP] Check Pods Manifest.lock";
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
4680F813A9BB67329666E939 /* [CP] Copy Pods Resources */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
name = "[CP] Copy Pods Resources";
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-SwiftSoup_Example/Pods-SwiftSoup_Example-resources.sh\"\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
58E76CD2ECC9108A3405EC58 /* [CP] Embed Pods Frameworks */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
name = "[CP] Embed Pods Frameworks";
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-SwiftSoup_Tests/Pods-SwiftSoup_Tests-frameworks.sh\"\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
8B4EF740BFDB6721B4EE4F7E /* [CP] Copy Pods Resources */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
name = "[CP] Copy Pods Resources";
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-SwiftSoup_Tests/Pods-SwiftSoup_Tests-resources.sh\"\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
A0119AC35661ED0A1BD7913B /* [CP] Check Pods Manifest.lock */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
name = "[CP] Check Pods Manifest.lock";
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
D9F7845A52211017AECDD30F /* [CP] Embed Pods Frameworks */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
name = "[CP] Embed Pods Frameworks";
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-SwiftSoup_Example/Pods-SwiftSoup_Example-frameworks.sh\"\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
/* End PBXShellScriptBuildPhase section */
|
||||
|
||||
/* Begin PBXSourcesBuildPhase section */
|
||||
607FACCC1AFB9204008FA782 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
607FACD81AFB9204008FA782 /* ViewController.swift in Sources */,
|
||||
607FACD61AFB9204008FA782 /* AppDelegate.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
607FACE11AFB9204008FA782 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
607FACEC1AFB9204008FA782 /* Tests.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXSourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXTargetDependency section */
|
||||
607FACE71AFB9204008FA782 /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
target = 607FACCF1AFB9204008FA782 /* SwiftSoup_Example */;
|
||||
targetProxy = 607FACE61AFB9204008FA782 /* PBXContainerItemProxy */;
|
||||
};
|
||||
/* End PBXTargetDependency section */
|
||||
|
||||
/* Begin PBXVariantGroup section */
|
||||
607FACD91AFB9204008FA782 /* Main.storyboard */ = {
|
||||
isa = PBXVariantGroup;
|
||||
children = (
|
||||
607FACDA1AFB9204008FA782 /* Base */,
|
||||
);
|
||||
name = Main.storyboard;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
607FACDE1AFB9204008FA782 /* LaunchScreen.xib */ = {
|
||||
isa = PBXVariantGroup;
|
||||
children = (
|
||||
607FACDF1AFB9204008FA782 /* Base */,
|
||||
);
|
||||
name = LaunchScreen.xib;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXVariantGroup section */
|
||||
|
||||
/* Begin XCBuildConfiguration section */
|
||||
607FACED1AFB9204008FA782 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
ENABLE_TESTABILITY = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||
GCC_DYNAMIC_NO_PIC = NO;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_OPTIMIZATION_LEVEL = 0;
|
||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||
"DEBUG=1",
|
||||
"$(inherited)",
|
||||
);
|
||||
GCC_SYMBOLS_PRIVATE_EXTERN = NO;
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 8.3;
|
||||
MTL_ENABLE_DEBUG_INFO = YES;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
SDKROOT = iphoneos;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
607FACEE1AFB9204008FA782 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
ENABLE_NS_ASSERTIONS = NO;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 8.3;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
SDKROOT = iphoneos;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
|
||||
VALIDATE_PRODUCT = YES;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
607FACF01AFB9204008FA782 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 164E36D1491528A4BE6103E6 /* Pods-SwiftSoup_Example.debug.xcconfig */;
|
||||
buildSettings = {
|
||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
INFOPLIST_FILE = SwiftSoup/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
||||
MODULE_NAME = ExampleApp;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.demo.$(PRODUCT_NAME:rfc1034identifier)";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_VERSION = 3.0;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
607FACF11AFB9204008FA782 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 3E29D6B2C06B70F9343156AB /* Pods-SwiftSoup_Example.release.xcconfig */;
|
||||
buildSettings = {
|
||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
INFOPLIST_FILE = SwiftSoup/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
||||
MODULE_NAME = ExampleApp;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.demo.$(PRODUCT_NAME:rfc1034identifier)";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_VERSION = 3.0;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
607FACF31AFB9204008FA782 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 459BB90FB1836BA98A7A3928 /* Pods-SwiftSoup_Tests.debug.xcconfig */;
|
||||
buildSettings = {
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
"$(SDKROOT)/Developer/Library/Frameworks",
|
||||
"$(inherited)",
|
||||
);
|
||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||
"DEBUG=1",
|
||||
"$(inherited)",
|
||||
);
|
||||
INFOPLIST_FILE = Tests/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.$(PRODUCT_NAME:rfc1034identifier)";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_VERSION = 3.0;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
607FACF41AFB9204008FA782 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = FBE53E306FAF4C428827FFD9 /* Pods-SwiftSoup_Tests.release.xcconfig */;
|
||||
buildSettings = {
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
"$(SDKROOT)/Developer/Library/Frameworks",
|
||||
"$(inherited)",
|
||||
);
|
||||
INFOPLIST_FILE = Tests/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.$(PRODUCT_NAME:rfc1034identifier)";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_VERSION = 3.0;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
/* End XCBuildConfiguration section */
|
||||
|
||||
/* Begin XCConfigurationList section */
|
||||
607FACCB1AFB9204008FA782 /* Build configuration list for PBXProject "SwiftSoup" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
607FACED1AFB9204008FA782 /* Debug */,
|
||||
607FACEE1AFB9204008FA782 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
607FACEF1AFB9204008FA782 /* Build configuration list for PBXNativeTarget "SwiftSoup_Example" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
607FACF01AFB9204008FA782 /* Debug */,
|
||||
607FACF11AFB9204008FA782 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
607FACF21AFB9204008FA782 /* Build configuration list for PBXNativeTarget "SwiftSoup_Tests" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
607FACF31AFB9204008FA782 /* Debug */,
|
||||
607FACF41AFB9204008FA782 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
/* End XCConfigurationList section */
|
||||
};
|
||||
rootObject = 607FACC81AFB9204008FA782 /* Project object */;
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Workspace
|
||||
version = "1.0">
|
||||
<FileRef
|
||||
location = "self:SwiftSoup.xcodeproj">
|
||||
</FileRef>
|
||||
</Workspace>
|
|
@ -0,0 +1,115 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "0810"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "607FACCF1AFB9204008FA782"
|
||||
BuildableName = "SwiftSoup_Example.app"
|
||||
BlueprintName = "SwiftSoup_Example"
|
||||
ReferencedContainer = "container:SwiftSoup.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "NO"
|
||||
buildForArchiving = "NO"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "607FACE41AFB9204008FA782"
|
||||
BuildableName = "SwiftSoup_Tests.xctest"
|
||||
BlueprintName = "SwiftSoup_Tests"
|
||||
ReferencedContainer = "container:SwiftSoup.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<Testables>
|
||||
<TestableReference
|
||||
skipped = "NO">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "607FACE41AFB9204008FA782"
|
||||
BuildableName = "SwiftSoup_Tests.xctest"
|
||||
BlueprintName = "SwiftSoup_Tests"
|
||||
ReferencedContainer = "container:SwiftSoup.xcodeproj">
|
||||
</BuildableReference>
|
||||
</TestableReference>
|
||||
</Testables>
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "607FACCF1AFB9204008FA782"
|
||||
BuildableName = "SwiftSoup_Example.app"
|
||||
BlueprintName = "SwiftSoup_Example"
|
||||
ReferencedContainer = "container:SwiftSoup.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "607FACCF1AFB9204008FA782"
|
||||
BuildableName = "SwiftSoup_Example.app"
|
||||
BlueprintName = "SwiftSoup_Example"
|
||||
ReferencedContainer = "container:SwiftSoup.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
debugDocumentVersioning = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "607FACCF1AFB9204008FA782"
|
||||
BuildableName = "SwiftSoup_Example.app"
|
||||
BlueprintName = "SwiftSoup_Example"
|
||||
ReferencedContainer = "container:SwiftSoup.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
|
@ -0,0 +1,10 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Workspace
|
||||
version = "1.0">
|
||||
<FileRef
|
||||
location = "group:SwiftSoup.xcodeproj">
|
||||
</FileRef>
|
||||
<FileRef
|
||||
location = "group:Pods/Pods.xcodeproj">
|
||||
</FileRef>
|
||||
</Workspace>
|
|
@ -0,0 +1,46 @@
|
|||
//
|
||||
// AppDelegate.swift
|
||||
// SwiftSoup
|
||||
//
|
||||
// Created by Nabil Chatbi on 11/19/2016.
|
||||
// Copyright (c) 2016 Nabil Chatbi. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
@UIApplicationMain
|
||||
class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||
|
||||
var window: UIWindow?
|
||||
|
||||
|
||||
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
|
||||
// Override point for customization after application launch.
|
||||
return true
|
||||
}
|
||||
|
||||
func applicationWillResignActive(_ application: UIApplication) {
|
||||
// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
|
||||
// Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
|
||||
}
|
||||
|
||||
func applicationDidEnterBackground(_ application: UIApplication) {
|
||||
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
|
||||
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
|
||||
}
|
||||
|
||||
func applicationWillEnterForeground(_ application: UIApplication) {
|
||||
// Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background.
|
||||
}
|
||||
|
||||
func applicationDidBecomeActive(_ application: UIApplication) {
|
||||
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
|
||||
}
|
||||
|
||||
func applicationWillTerminate(_ application: UIApplication) {
|
||||
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="6214" systemVersion="14A314h" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES">
|
||||
<dependencies>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="6207"/>
|
||||
<capability name="Constraints with non-1.0 multipliers" minToolsVersion="5.1"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
|
||||
<view contentMode="scaleToFill" id="iN0-l3-epB">
|
||||
<rect key="frame" x="0.0" y="0.0" width="480" height="480"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text=" Copyright (c) 2015 CocoaPods. All rights reserved." textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumFontSize="9" translatesAutoresizingMaskIntoConstraints="NO" id="8ie-xW-0ye">
|
||||
<rect key="frame" x="20" y="439" width="441" height="21"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<color key="textColor" cocoaTouchSystemColor="darkTextColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="SwiftSoup" textAlignment="center" lineBreakMode="middleTruncation" baselineAdjustment="alignBaselines" minimumFontSize="18" translatesAutoresizingMaskIntoConstraints="NO" id="kId-c2-rCX">
|
||||
<rect key="frame" x="20" y="140" width="441" height="43"/>
|
||||
<fontDescription key="fontDescription" type="boldSystem" pointSize="36"/>
|
||||
<color key="textColor" cocoaTouchSystemColor="darkTextColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
|
||||
<constraints>
|
||||
<constraint firstItem="kId-c2-rCX" firstAttribute="centerY" secondItem="iN0-l3-epB" secondAttribute="bottom" multiplier="1/3" constant="1" id="5cJ-9S-tgC"/>
|
||||
<constraint firstAttribute="centerX" secondItem="kId-c2-rCX" secondAttribute="centerX" id="Koa-jz-hwk"/>
|
||||
<constraint firstAttribute="bottom" secondItem="8ie-xW-0ye" secondAttribute="bottom" constant="20" id="Kzo-t9-V3l"/>
|
||||
<constraint firstItem="8ie-xW-0ye" firstAttribute="leading" secondItem="iN0-l3-epB" secondAttribute="leading" constant="20" symbolic="YES" id="MfP-vx-nX0"/>
|
||||
<constraint firstAttribute="centerX" secondItem="8ie-xW-0ye" secondAttribute="centerX" id="ZEH-qu-HZ9"/>
|
||||
<constraint firstItem="kId-c2-rCX" firstAttribute="leading" secondItem="iN0-l3-epB" secondAttribute="leading" constant="20" symbolic="YES" id="fvb-Df-36g"/>
|
||||
</constraints>
|
||||
<nil key="simulatedStatusBarMetrics"/>
|
||||
<freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
|
||||
<point key="canvasLocation" x="548" y="455"/>
|
||||
</view>
|
||||
</objects>
|
||||
</document>
|
|
@ -0,0 +1,25 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="6211" systemVersion="14A298i" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="vXZ-lx-hvc">
|
||||
<dependencies>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="6204"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
<!--View Controller-->
|
||||
<scene sceneID="ufC-wZ-h7g">
|
||||
<objects>
|
||||
<viewController id="vXZ-lx-hvc" customClass="ViewController" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<layoutGuides>
|
||||
<viewControllerLayoutGuide type="top" id="jyV-Pf-zRb"/>
|
||||
<viewControllerLayoutGuide type="bottom" id="2fi-mo-0CV"/>
|
||||
</layoutGuides>
|
||||
<view key="view" contentMode="scaleToFill" id="kh9-bI-dsS">
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
|
||||
</view>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="x5A-6p-PRh" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
</scene>
|
||||
</scenes>
|
||||
</document>
|
|
@ -0,0 +1,48 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"size" : "20x20",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"size" : "20x20",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"size" : "29x29",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"size" : "29x29",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"size" : "40x40",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"size" : "40x40",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"size" : "60x60",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"size" : "60x60",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>en</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>$(PRODUCT_NAME)</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1</string>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
<key>UILaunchStoryboardName</key>
|
||||
<string>LaunchScreen</string>
|
||||
<key>UIMainStoryboardFile</key>
|
||||
<string>Main</string>
|
||||
<key>UIRequiredDeviceCapabilities</key>
|
||||
<array>
|
||||
<string>armv7</string>
|
||||
</array>
|
||||
<key>UISupportedInterfaceOrientations</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
|
@ -0,0 +1,25 @@
|
|||
//
|
||||
// ViewController.swift
|
||||
// SwiftSoup
|
||||
//
|
||||
// Created by Nabil Chatbi on 11/19/2016.
|
||||
// Copyright (c) 2016 Nabil Chatbi. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import SwiftSoup
|
||||
|
||||
class ViewController: UIViewController {
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
// Do any additional setup after loading the view, typically from a nib.
|
||||
}
|
||||
|
||||
override func didReceiveMemoryWarning() {
|
||||
super.didReceiveMemoryWarning()
|
||||
// Dispose of any resources that can be recreated.
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>en</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>$(PRODUCT_NAME)</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>BNDL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1</string>
|
||||
</dict>
|
||||
</plist>
|
|
@ -0,0 +1,29 @@
|
|||
import UIKit
|
||||
import XCTest
|
||||
import SwiftSoup
|
||||
|
||||
class Tests: XCTestCase {
|
||||
|
||||
override func setUp() {
|
||||
super.setUp()
|
||||
// Put setup code here. This method is called before the invocation of each test method in the class.
|
||||
}
|
||||
|
||||
override func tearDown() {
|
||||
// Put teardown code here. This method is called after the invocation of each test method in the class.
|
||||
super.tearDown()
|
||||
}
|
||||
|
||||
func testExample() {
|
||||
// This is an example of a functional test case.
|
||||
XCTAssert(true, "Pass")
|
||||
}
|
||||
|
||||
func testPerformanceExample() {
|
||||
// This is an example of a performance test case.
|
||||
self.measure() {
|
||||
// Put the code you want to measure the time of here.
|
||||
}
|
||||
}
|
||||
|
||||
}
|
30
README.md
30
README.md
|
@ -1 +1,29 @@
|
|||
# SwiftSoup
|
||||
# SwiftSoup
|
||||
|
||||
[](https://travis-ci.org/Nabil Chatbi/SwiftSoup)
|
||||
[](http://cocoapods.org/pods/SwiftSoup)
|
||||
[](http://cocoapods.org/pods/SwiftSoup)
|
||||
[](http://cocoapods.org/pods/SwiftSoup)
|
||||
|
||||
## Example
|
||||
|
||||
To run the example project, clone the repo, and run `pod install` from the Example directory first.
|
||||
|
||||
## Requirements
|
||||
|
||||
## Installation
|
||||
|
||||
SwiftSoup is available through [CocoaPods](http://cocoapods.org). To install
|
||||
it, simply add the following line to your Podfile:
|
||||
|
||||
```ruby
|
||||
pod "SwiftSoup"
|
||||
```
|
||||
|
||||
## Author
|
||||
|
||||
Nabil Chatbi, scinfu@gmail.com
|
||||
|
||||
## License
|
||||
|
||||
SwiftSoup is available under the MIT license. See the LICENSE file for more info.
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
//
|
||||
// Connection.swift
|
||||
// SwifSoup
|
||||
//
|
||||
// Created by Nabil Chatbi on 29/09/16.
|
||||
// Copyright © 2016 Nabil Chatbi.. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
//TODO:
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
//
|
||||
// HttpStatusException.swift
|
||||
// SwifSoup
|
||||
//
|
||||
// Created by Nabil Chatbi on 29/09/16.
|
||||
// Copyright © 2016 Nabil Chatbi.. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
//TODO:
|
|
@ -0,0 +1,24 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>en</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>$(PRODUCT_NAME)</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>FMWK</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>$(CURRENT_PROJECT_VERSION)</string>
|
||||
<key>NSPrincipalClass</key>
|
||||
<string></string>
|
||||
</dict>
|
||||
</plist>
|
|
@ -0,0 +1,10 @@
|
|||
//
|
||||
// SerializationException.swift
|
||||
// SwifSoup
|
||||
//
|
||||
// Created by Nabil Chatbi on 29/09/16.
|
||||
// Copyright © 2016 Nabil Chatbi.. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
//TODO:
|
|
@ -0,0 +1,19 @@
|
|||
//
|
||||
// SwiftSoup.h
|
||||
// SwiftSoup
|
||||
//
|
||||
// Created by Nabil Chatbi on 09/10/16.
|
||||
// Copyright © 2016 Nabil Chatbi.. All rights reserved.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
//! Project version number for SwiftSoup.
|
||||
FOUNDATION_EXPORT double SwiftSoupVersionNumber;
|
||||
|
||||
//! Project version string for SwiftSoup.
|
||||
FOUNDATION_EXPORT const unsigned char SwiftSoupVersionString[];
|
||||
|
||||
// In this header, you should import all the public headers of your framework using statements like #import <SwiftSoup/PublicHeader.h>
|
||||
|
||||
|
|
@ -0,0 +1,238 @@
|
|||
//
|
||||
// SwiftSoup.swift
|
||||
// Jsoup
|
||||
//
|
||||
// Created by Nabil Chatbi on 29/09/16.
|
||||
// Copyright © 2016 Nabil Chatbi.. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
|
||||
/**
|
||||
The core public access point to the jsoup functionality.
|
||||
*/
|
||||
open class SwiftSoup {
|
||||
private init(){}
|
||||
|
||||
/**
|
||||
Parse HTML into a Document. The parser will make a sensible, balanced document tree out of any HTML.
|
||||
|
||||
@param html HTML to parse
|
||||
@param baseUri The URL where the HTML was retrieved from. Used to resolve relative URLs to absolute URLs, that occur
|
||||
before the HTML declares a {@code <base href>} tag.
|
||||
@return sane HTML
|
||||
*/
|
||||
public static func parse(_ html: String, _ baseUri: String)throws->Document {
|
||||
return try Parser.parse(html, baseUri);
|
||||
}
|
||||
|
||||
/**
|
||||
Parse HTML into a Document, using the provided Parser. You can provide an alternate parser, such as a simple XML
|
||||
(non-HTML) parser.
|
||||
|
||||
@param html HTML to parse
|
||||
@param baseUri The URL where the HTML was retrieved from. Used to resolve relative URLs to absolute URLs, that occur
|
||||
before the HTML declares a {@code <base href>} tag.
|
||||
@param parser alternate {@link Parser#xmlParser() parser} to use.
|
||||
@return sane HTML
|
||||
*/
|
||||
public static func parse(_ html: String, _ baseUri: String, _ parser: Parser)throws->Document {
|
||||
return try parser.parseInput(html, baseUri);
|
||||
}
|
||||
|
||||
/**
|
||||
Parse HTML into a Document. As no base URI is specified, absolute URL detection relies on the HTML including a
|
||||
{@code <base href>} tag.
|
||||
|
||||
@param html HTML to parse
|
||||
@return sane HTML
|
||||
|
||||
@see #parse(String, String)
|
||||
*/
|
||||
public static func parse(_ html: String)throws->Document {
|
||||
return try Parser.parse(html, "");
|
||||
}
|
||||
|
||||
//todo:
|
||||
// /**
|
||||
// * Creates a new {@link Connection} to a URL. Use to fetch and parse a HTML page.
|
||||
// * <p>
|
||||
// * Use examples:
|
||||
// * <ul>
|
||||
// * <li><code>Document doc = Jsoup.connect("http://example.com").userAgent("Mozilla").data("name", "jsoup").get();</code></li>
|
||||
// * <li><code>Document doc = Jsoup.connect("http://example.com").cookie("auth", "token").post();</code></li>
|
||||
// * </ul>
|
||||
// * @param url URL to connect to. The protocol must be {@code http} or {@code https}.
|
||||
// * @return the connection. You can add data, cookies, and headers; set the user-agent, referrer, method; and then execute.
|
||||
// */
|
||||
// public static Connection connect(String url) {
|
||||
// return HttpConnection.connect(url);
|
||||
// }
|
||||
|
||||
//todo:
|
||||
// /**
|
||||
// Parse the contents of a file as HTML.
|
||||
//
|
||||
// @param in file to load HTML from
|
||||
// @param charsetName (optional) character set of file contents. Set to {@code null} to determine from {@code http-equiv} meta tag, if
|
||||
// present, or fall back to {@code UTF-8} (which is often safe to do).
|
||||
// @param baseUri The URL where the HTML was retrieved from, to resolve relative links against.
|
||||
// @return sane HTML
|
||||
//
|
||||
// @throws IOException if the file could not be found, or read, or if the charsetName is invalid.
|
||||
// */
|
||||
// public static Document parse(File in, String charsetName, String baseUri) throws IOException {
|
||||
// return DataUtil.load(in, charsetName, baseUri);
|
||||
// }
|
||||
|
||||
//todo:
|
||||
// /**
|
||||
// Parse the contents of a file as HTML. The location of the file is used as the base URI to qualify relative URLs.
|
||||
//
|
||||
// @param in file to load HTML from
|
||||
// @param charsetName (optional) character set of file contents. Set to {@code null} to determine from {@code http-equiv} meta tag, if
|
||||
// present, or fall back to {@code UTF-8} (which is often safe to do).
|
||||
// @return sane HTML
|
||||
//
|
||||
// @throws IOException if the file could not be found, or read, or if the charsetName is invalid.
|
||||
// @see #parse(File, String, String)
|
||||
// */
|
||||
// public static Document parse(File in, String charsetName) throws IOException {
|
||||
// return DataUtil.load(in, charsetName, in.getAbsolutePath());
|
||||
// }
|
||||
|
||||
// /**
|
||||
// Read an input stream, and parse it to a Document.
|
||||
//
|
||||
// @param in input stream to read. Make sure to close it after parsing.
|
||||
// @param charsetName (optional) character set of file contents. Set to {@code null} to determine from {@code http-equiv} meta tag, if
|
||||
// present, or fall back to {@code UTF-8} (which is often safe to do).
|
||||
// @param baseUri The URL where the HTML was retrieved from, to resolve relative links against.
|
||||
// @return sane HTML
|
||||
//
|
||||
// @throws IOException if the file could not be found, or read, or if the charsetName is invalid.
|
||||
// */
|
||||
// public static Document parse(InputStream in, String charsetName, String baseUri) throws IOException {
|
||||
// return DataUtil.load(in, charsetName, baseUri);
|
||||
// }
|
||||
|
||||
// /**
|
||||
// Read an input stream, and parse it to a Document. You can provide an alternate parser, such as a simple XML
|
||||
// (non-HTML) parser.
|
||||
//
|
||||
// @param in input stream to read. Make sure to close it after parsing.
|
||||
// @param charsetName (optional) character set of file contents. Set to {@code null} to determine from {@code http-equiv} meta tag, if
|
||||
// present, or fall back to {@code UTF-8} (which is often safe to do).
|
||||
// @param baseUri The URL where the HTML was retrieved from, to resolve relative links against.
|
||||
// @param parser alternate {@link Parser#xmlParser() parser} to use.
|
||||
// @return sane HTML
|
||||
//
|
||||
// @throws IOException if the file could not be found, or read, or if the charsetName is invalid.
|
||||
// */
|
||||
// public static Document parse(InputStream in, String charsetName, String baseUri, Parser parser) throws IOException {
|
||||
// return DataUtil.load(in, charsetName, baseUri, parser);
|
||||
// }
|
||||
|
||||
/**
|
||||
Parse a fragment of HTML, with the assumption that it forms the {@code body} of the HTML.
|
||||
|
||||
@param bodyHtml body HTML fragment
|
||||
@param baseUri URL to resolve relative URLs against.
|
||||
@return sane HTML document
|
||||
|
||||
@see Document#body()
|
||||
*/
|
||||
public static func parseBodyFragment(_ bodyHtml: String, _ baseUri: String)throws->Document {
|
||||
return try Parser.parseBodyFragment(bodyHtml, baseUri);
|
||||
}
|
||||
|
||||
/**
|
||||
Parse a fragment of HTML, with the assumption that it forms the {@code body} of the HTML.
|
||||
|
||||
@param bodyHtml body HTML fragment
|
||||
@return sane HTML document
|
||||
|
||||
@see Document#body()
|
||||
*/
|
||||
public static func parseBodyFragment(_ bodyHtml: String)throws->Document {
|
||||
return try Parser.parseBodyFragment(bodyHtml, "");
|
||||
}
|
||||
|
||||
// /**
|
||||
// Fetch a URL, and parse it as HTML. Provided for compatibility; in most cases use {@link #connect(String)} instead.
|
||||
// <p>
|
||||
// The encoding character set is determined by the content-type header or http-equiv meta tag, or falls back to {@code UTF-8}.
|
||||
//
|
||||
// @param url URL to fetch (with a GET). The protocol must be {@code http} or {@code https}.
|
||||
// @param timeoutMillis Connection and read timeout, in milliseconds. If exceeded, IOException is thrown.
|
||||
// @return The parsed HTML.
|
||||
//
|
||||
// @throws java.net.MalformedURLException if the request URL is not a HTTP or HTTPS URL, or is otherwise malformed
|
||||
// @throws HttpStatusException if the response is not OK and HTTP response errors are not ignored
|
||||
// @throws UnsupportedMimeTypeException if the response mime type is not supported and those errors are not ignored
|
||||
// @throws java.net.SocketTimeoutException if the connection times out
|
||||
// @throws IOException if a connection or read error occurs
|
||||
//
|
||||
// @see #connect(String)
|
||||
// */
|
||||
// public static func parse(_ url: URL, _ timeoutMillis: Int)throws->Document {
|
||||
// Connection con = HttpConnection.connect(url);
|
||||
// con.timeout(timeoutMillis);
|
||||
// return con.get();
|
||||
// }
|
||||
|
||||
/**
|
||||
Get safe HTML from untrusted input HTML, by parsing input HTML and filtering it through a white-list of permitted
|
||||
tags and attributes.
|
||||
|
||||
@param bodyHtml input untrusted HTML (body fragment)
|
||||
@param baseUri URL to resolve relative URLs against
|
||||
@param whitelist white-list of permitted HTML elements
|
||||
@return safe HTML (body fragment)
|
||||
|
||||
@see Cleaner#clean(Document)
|
||||
*/
|
||||
public static func clean(_ bodyHtml: String, _ baseUri: String, _ whitelist: Whitelist)throws->String? {
|
||||
let dirty: Document = try parseBodyFragment(bodyHtml, baseUri);
|
||||
let cleaner: Cleaner = Cleaner(whitelist);
|
||||
let clean: Document = try cleaner.clean(dirty);
|
||||
return try clean.body()?.html();
|
||||
}
|
||||
|
||||
/**
|
||||
Get safe HTML from untrusted input HTML, by parsing input HTML and filtering it through a white-list of permitted
|
||||
tags and attributes.
|
||||
|
||||
@param bodyHtml input untrusted HTML (body fragment)
|
||||
@param whitelist white-list of permitted HTML elements
|
||||
@return safe HTML (body fragment)
|
||||
|
||||
@see Cleaner#clean(Document)
|
||||
*/
|
||||
public static func clean(_ bodyHtml: String, _ whitelist: Whitelist)throws->String? {
|
||||
return try SwiftSoup.clean(bodyHtml, "", whitelist);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get safe HTML from untrusted input HTML, by parsing input HTML and filtering it through a white-list of
|
||||
* permitted
|
||||
* tags and attributes.
|
||||
*
|
||||
* @param bodyHtml input untrusted HTML (body fragment)
|
||||
* @param baseUri URL to resolve relative URLs against
|
||||
* @param whitelist white-list of permitted HTML elements
|
||||
* @param outputSettings document output settings; use to control pretty-printing and entity escape modes
|
||||
* @return safe HTML (body fragment)
|
||||
* @see Cleaner#clean(Document)
|
||||
*/
|
||||
public static func clean(_ bodyHtml: String, _ baseUri: String, _ whitelist: Whitelist, _ outputSettings: OutputSettings)throws->String? {
|
||||
let dirty: Document = try SwiftSoup.parseBodyFragment(bodyHtml, baseUri);
|
||||
let cleaner: Cleaner = Cleaner(whitelist);
|
||||
let clean: Document = try cleaner.clean(dirty);
|
||||
clean.outputSettings(outputSettings);
|
||||
return try clean.body()?.html();
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
//
|
||||
// DataUtil.swift
|
||||
// SwifSoup
|
||||
//
|
||||
// Created by Nabil Chatbi on 02/10/16.
|
||||
// Copyright © 2016 Nabil Chatbi.. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
/**
|
||||
* Internal static utilities for handling data.
|
||||
*
|
||||
*/
|
||||
class DataUtil {
|
||||
|
||||
static let charsetPattern = "(?i)\\bcharset=\\s*(?:\"|')?([^\\s,;\"']*)"
|
||||
static let defaultCharset = "UTF-8"; // used if not found in header or meta charset
|
||||
static let bufferSize = 0x20000; // ~130K.
|
||||
static let UNICODE_BOM = 0xFEFF;
|
||||
static let mimeBoundaryChars = "-_1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ".characters
|
||||
static let boundaryLength = 32;
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
//
|
||||
// Exception.swift
|
||||
// SwifSoup
|
||||
//
|
||||
// Created by Nabil Chatbi on 02/10/16.
|
||||
// Copyright © 2016 Nabil Chatbi.. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
enum ExceptionType {
|
||||
case IllegalArgumentException
|
||||
case IOException
|
||||
case XmlDeclaration
|
||||
case MalformedURLException
|
||||
case CloneNotSupportedException
|
||||
case SelectorParseException
|
||||
}
|
||||
|
||||
enum Exception : Error {
|
||||
case Error(type:ExceptionType ,Message: String)
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,163 @@
|
|||
/**
|
||||
Supports creation of a String from pieces
|
||||
https://gist.github.com/kristopherjohnson/1fc55e811d944a430289
|
||||
*/
|
||||
open class StringBuilder {
|
||||
fileprivate var stringValue: String
|
||||
|
||||
/**
|
||||
Construct with initial String contents
|
||||
|
||||
:param: string Initial value; defaults to empty string
|
||||
*/
|
||||
public init(string: String = "") {
|
||||
self.stringValue = string
|
||||
}
|
||||
|
||||
public init(_ size: Int) {
|
||||
self.stringValue = ""
|
||||
}
|
||||
|
||||
/**
|
||||
Return the String object
|
||||
|
||||
:return: String
|
||||
*/
|
||||
open func toString() -> String {
|
||||
return stringValue
|
||||
}
|
||||
|
||||
/**
|
||||
Return the current length of the String object
|
||||
*/
|
||||
open var length: Int {
|
||||
return self.stringValue.characters.count;
|
||||
//return countElements(stringValue)
|
||||
}
|
||||
|
||||
/**
|
||||
Append a String to the object
|
||||
|
||||
:param: string String
|
||||
|
||||
:return: reference to this StringBuilder instance
|
||||
*/
|
||||
open func append(_ string: String) {
|
||||
stringValue += string
|
||||
}
|
||||
|
||||
|
||||
open func appendCodePoint(_ chr: Character) {
|
||||
stringValue = stringValue + String(chr)
|
||||
}
|
||||
|
||||
open func appendCodePoints(_ chr: [Character]) {
|
||||
for c in chr{
|
||||
stringValue = stringValue + String(c)
|
||||
}
|
||||
}
|
||||
|
||||
open func appendCodePoint(_ ch: Int) {
|
||||
stringValue = stringValue + String(Character(UnicodeScalar(ch)!))
|
||||
}
|
||||
|
||||
open func appendCodePoint(_ ch: UnicodeScalar) {
|
||||
stringValue = stringValue + String(ch)
|
||||
}
|
||||
|
||||
open func appendCodePoints(_ chr: [UnicodeScalar]) {
|
||||
for c in chr{
|
||||
stringValue = stringValue + String(c)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
Append a Printable to the object
|
||||
|
||||
:param: value a value supporting the Printable protocol
|
||||
|
||||
:return: reference to this StringBuilder instance
|
||||
*/
|
||||
@discardableResult
|
||||
open func append<T: CustomStringConvertible>(_ value: T) -> StringBuilder {
|
||||
stringValue += value.description
|
||||
return self
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
open func insert<T: CustomStringConvertible>(_ offset: Int ,_ value: T) -> StringBuilder {
|
||||
stringValue = stringValue.insert(string: value.description, ind: offset)
|
||||
return self
|
||||
}
|
||||
|
||||
/**
|
||||
Append a String and a newline to the object
|
||||
|
||||
:param: string String
|
||||
|
||||
:return: reference to this StringBuilder instance
|
||||
*/
|
||||
@discardableResult
|
||||
open func appendLine(_ string: String) -> StringBuilder {
|
||||
stringValue += string + "\n"
|
||||
return self
|
||||
}
|
||||
|
||||
/**
|
||||
Append a Printable and a newline to the object
|
||||
|
||||
:param: value a value supporting the Printable protocol
|
||||
|
||||
:return: reference to this StringBuilder instance
|
||||
*/
|
||||
@discardableResult
|
||||
open func appendLine<T: CustomStringConvertible>(_ value: T) -> StringBuilder {
|
||||
stringValue += value.description + "\n"
|
||||
return self
|
||||
}
|
||||
|
||||
/**
|
||||
Reset the object to an empty string
|
||||
|
||||
:return: reference to this StringBuilder instance
|
||||
*/
|
||||
@discardableResult
|
||||
open func clear() -> StringBuilder {
|
||||
stringValue = ""
|
||||
return self
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Append a String to a StringBuilder using operator syntax
|
||||
|
||||
:param: lhs StringBuilder
|
||||
:param: rhs String
|
||||
*/
|
||||
public func += (lhs: StringBuilder, rhs: String) {
|
||||
lhs.append(rhs)
|
||||
}
|
||||
|
||||
/**
|
||||
Append a Printable to a StringBuilder using operator syntax
|
||||
|
||||
:param: lhs Printable
|
||||
:param: rhs String
|
||||
*/
|
||||
public func += <T: CustomStringConvertible>(lhs: StringBuilder, rhs: T) {
|
||||
lhs.append(rhs.description)
|
||||
}
|
||||
|
||||
/**
|
||||
Create a StringBuilder by concatenating the values of two StringBuilders
|
||||
|
||||
:param: lhs first StringBuilder
|
||||
:param: rhs second StringBuilder
|
||||
|
||||
:result StringBuilder
|
||||
*/
|
||||
public func +(lhs: StringBuilder, rhs: StringBuilder) -> StringBuilder {
|
||||
return StringBuilder(string: lhs.toString() + rhs.toString())
|
||||
}
|
||||
|
|
@ -0,0 +1,291 @@
|
|||
//
|
||||
// StringUtil.swift
|
||||
// SwifSoup
|
||||
//
|
||||
// Created by Nabil Chatbi on 20/04/16.
|
||||
// Copyright © 2016 Nabil Chatbi.. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
/**
|
||||
* A minimal String utility class. Designed for internal jsoup use only.
|
||||
*/
|
||||
open class StringUtil
|
||||
{
|
||||
enum StringError: Error {
|
||||
case empty
|
||||
case short
|
||||
case error(String)
|
||||
}
|
||||
|
||||
// memoised padding up to 10
|
||||
fileprivate static var padding : [String] = ["", " ", " ", " ", " ", " ", " ", " ", " ", " ", " "];
|
||||
|
||||
/**
|
||||
* Join a collection of strings by a seperator
|
||||
* @param strings collection of string objects
|
||||
* @param sep string to place between strings
|
||||
* @return joined string
|
||||
*/
|
||||
open static func join(_ strings:[String], sep:String) -> String {
|
||||
return strings.joined(separator: sep);
|
||||
}
|
||||
open static func join(_ strings:Set<String>, sep:String) -> String {
|
||||
return strings.joined(separator: sep);
|
||||
}
|
||||
|
||||
open static func join(_ strings:OrderedSet<String>, sep:String) -> String {
|
||||
return strings.joined(separator: sep);
|
||||
}
|
||||
|
||||
|
||||
// /**
|
||||
// * Join a collection of strings by a seperator
|
||||
// * @param strings iterator of string objects
|
||||
// * @param sep string to place between strings
|
||||
// * @return joined string
|
||||
// */
|
||||
// public static String join(Iterator strings, String sep) {
|
||||
// if (!strings.hasNext())
|
||||
// return "";
|
||||
//
|
||||
// String start = strings.next().toString();
|
||||
// if (!strings.hasNext()) // only one, avoid builder
|
||||
// return start;
|
||||
//
|
||||
// StringBuilder sb = new StringBuilder(64).append(start);
|
||||
// while (strings.hasNext()) {
|
||||
// sb.append(sep);
|
||||
// sb.append(strings.next());
|
||||
// }
|
||||
// return sb.toString();
|
||||
// }
|
||||
/**
|
||||
* Returns space padding
|
||||
* @param width amount of padding desired
|
||||
* @return string of spaces * width
|
||||
*/
|
||||
open static func padding(_ width:Int) -> String{
|
||||
|
||||
if(width <= 0){
|
||||
return "";
|
||||
}
|
||||
|
||||
if (width < padding.count){
|
||||
return padding[width];
|
||||
}
|
||||
|
||||
var out: [Character] = [Character]()
|
||||
|
||||
for _ in 0..<width
|
||||
{
|
||||
out.append(" ")
|
||||
}
|
||||
return String(out);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests if a string is blank: null, emtpy, or only whitespace (" ", \r\n, \t, etc)
|
||||
* @param string string to test
|
||||
* @return if string is blank
|
||||
*/
|
||||
open static func isBlank(_ string:String) -> Bool {
|
||||
if (string.characters.count == 0){
|
||||
return true;
|
||||
}
|
||||
|
||||
for chr in string.characters
|
||||
{
|
||||
if (!StringUtil.isWhitespace(chr)){
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests if a string is numeric, i.e. contains only digit characters
|
||||
* @param string string to test
|
||||
* @return true if only digit chars, false if empty or null or contains non-digit chrs
|
||||
*/
|
||||
open static func isNumeric(_ string:String) -> Bool {
|
||||
if (string.characters.count == 0){
|
||||
return false;
|
||||
}
|
||||
|
||||
for chr in string.characters
|
||||
{
|
||||
if !("0"..."9" ~= chr)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests if a code point is "whitespace" as defined in the HTML spec.
|
||||
* @param c code point to test
|
||||
* @return true if code point is whitespace, false otherwise
|
||||
*/
|
||||
open static func isWhitespace(_ c:Character) -> Bool
|
||||
{
|
||||
//(c == " " || c == "\t" || c == "\n" || (c == "\f" ) || c == "\r")
|
||||
return c.isWhitespace;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalise the whitespace within this string; multiple spaces collapse to a single, and all whitespace characters
|
||||
* (e.g. newline, tab) convert to a simple space
|
||||
* @param string content to normalise
|
||||
* @return normalised string
|
||||
*/
|
||||
open static func normaliseWhitespace(_ string:String) -> String {
|
||||
let sb : StringBuilder = StringBuilder.init();
|
||||
appendNormalisedWhitespace(sb, string: string, stripLeading: false);
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* After normalizing the whitespace within a string, appends it to a string builder.
|
||||
* @param accum builder to append to
|
||||
* @param string string to normalize whitespace within
|
||||
* @param stripLeading set to true if you wish to remove any leading whitespace
|
||||
*/
|
||||
open static func appendNormalisedWhitespace(_ accum:StringBuilder, string: String , stripLeading:Bool ) {
|
||||
var lastWasWhite : Bool = false;
|
||||
var reachedNonWhite: Bool = false;
|
||||
|
||||
for c in string.characters
|
||||
{
|
||||
if (isWhitespace(c))
|
||||
{
|
||||
if ((stripLeading && !reachedNonWhite) || lastWasWhite){
|
||||
continue;
|
||||
}
|
||||
accum.append(" ");
|
||||
lastWasWhite = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
accum.appendCodePoint(c);
|
||||
lastWasWhite = false;
|
||||
reachedNonWhite = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
open static func inString(_ needle:String? , haystack:String...) -> Bool {
|
||||
return inString(needle,haystack)
|
||||
}
|
||||
open static func inString(_ needle:String? , _ haystack:[String?]) -> Bool {
|
||||
if(needle == nil){return false}
|
||||
for hay in haystack
|
||||
{
|
||||
if(hay != nil && hay!.compare(needle!) == ComparisonResult.orderedSame){
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
open static func inSorted(_ needle:String, haystack:[String]) -> Bool {
|
||||
return binarySearch(haystack, searchItem: needle) >= 0;
|
||||
}
|
||||
|
||||
open static func binarySearch<T:Comparable>(_ inputArr:Array<T>, searchItem: T)->Int{
|
||||
var lowerIndex = 0;
|
||||
var upperIndex = inputArr.count - 1
|
||||
|
||||
while (true) {
|
||||
let currentIndex = (lowerIndex + upperIndex)/2
|
||||
if(inputArr[currentIndex] == searchItem) {
|
||||
return currentIndex
|
||||
} else if (lowerIndex > upperIndex) {
|
||||
return -1
|
||||
} else {
|
||||
if (inputArr[currentIndex] > searchItem) {
|
||||
upperIndex = currentIndex - 1
|
||||
} else {
|
||||
lowerIndex = currentIndex + 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new absolute URL, from a provided existing absolute URL and a relative URL component.
|
||||
* @param base the existing absolulte base URL
|
||||
* @param relUrl the relative URL to resolve. (If it's already absolute, it will be returned)
|
||||
* @return the resolved absolute URL
|
||||
* @throws MalformedURLException if an error occurred generating the URL
|
||||
*/
|
||||
//NOTE: Not sure it work
|
||||
open static func resolve(_ base:URL, relUrl: String ) -> URL? {
|
||||
var base = base
|
||||
if(base.pathComponents.count == 0 && base.absoluteString.characters.last != "/" && !base.isFileURL)
|
||||
{
|
||||
base = base.appendingPathComponent("/", isDirectory: false)
|
||||
}
|
||||
let u = URL(string: relUrl, relativeTo : base);
|
||||
return u;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new absolute URL, from a provided existing absolute URL and a relative URL component.
|
||||
* @param baseUrl the existing absolute base URL
|
||||
* @param relUrl the relative URL to resolve. (If it's already absolute, it will be returned)
|
||||
* @return an absolute URL if one was able to be generated, or the empty string if not
|
||||
*/
|
||||
open static func resolve(_ baseUrl : String , relUrl : String ) -> String {
|
||||
|
||||
let base = URL(string: baseUrl);
|
||||
|
||||
if(base == nil || base?.scheme == nil)
|
||||
{
|
||||
let abs = URL(string: relUrl);
|
||||
return abs != nil && abs?.scheme != nil ? abs!.absoluteURL.absoluteString : ""
|
||||
}else{
|
||||
let url = resolve(base!, relUrl: relUrl);
|
||||
if(url != nil){
|
||||
let ext = url!.absoluteURL.absoluteString;
|
||||
return ext;
|
||||
}
|
||||
|
||||
if(base != nil && base?.scheme != nil){
|
||||
let ext = base!.absoluteString;
|
||||
return ext;
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
// try {
|
||||
// try {
|
||||
// base = new URL(baseUrl);
|
||||
// } catch (MalformedURLException e) {
|
||||
// // the base is unsuitable, but the attribute/rel may be abs on its own, so try that
|
||||
// URL abs = new URL(relUrl);
|
||||
// return abs.toExternalForm();
|
||||
// }
|
||||
// return resolve(base, relUrl).toExternalForm();
|
||||
// } catch (MalformedURLException e) {
|
||||
// return "";
|
||||
// }
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,136 @@
|
|||
//
|
||||
// Validate.swift
|
||||
// SwifSoup
|
||||
//
|
||||
// Created by Nabil Chatbi on 02/10/16.
|
||||
// Copyright © 2016 Nabil Chatbi.. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
struct Validate
|
||||
{
|
||||
|
||||
/**
|
||||
* Validates that the object is not null
|
||||
* @param obj object to test
|
||||
*/
|
||||
public static func notNull(obj:Any?) throws {
|
||||
if (obj == nil){
|
||||
throw Exception.Error(type: ExceptionType.IllegalArgumentException, Message: "Object must not be null")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates that the object is not null
|
||||
* @param obj object to test
|
||||
* @param msg message to output if validation fails
|
||||
*/
|
||||
public static func notNull(obj:AnyObject?, msg:String) throws {
|
||||
if (obj == nil){
|
||||
throw Exception.Error(type: ExceptionType.IllegalArgumentException, Message: msg)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates that the value is true
|
||||
* @param val object to test
|
||||
*/
|
||||
public static func isTrue(val:Bool) throws {
|
||||
if (!val){
|
||||
throw Exception.Error(type: ExceptionType.IllegalArgumentException, Message: "Must be true")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates that the value is true
|
||||
* @param val object to test
|
||||
* @param msg message to output if validation fails
|
||||
*/
|
||||
public static func isTrue(val: Bool ,msg: String) throws {
|
||||
if (!val){
|
||||
throw Exception.Error(type: ExceptionType.IllegalArgumentException, Message: msg)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates that the value is false
|
||||
* @param val object to test
|
||||
*/
|
||||
public static func isFalse(val: Bool) throws {
|
||||
if (val){
|
||||
throw Exception.Error(type: ExceptionType.IllegalArgumentException, Message: "Must be false")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates that the value is false
|
||||
* @param val object to test
|
||||
* @param msg message to output if validation fails
|
||||
*/
|
||||
public static func isFalse(val: Bool, msg: String) throws {
|
||||
if (val){
|
||||
throw Exception.Error(type: ExceptionType.IllegalArgumentException, Message: msg)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates that the array contains no null elements
|
||||
* @param objects the array to test
|
||||
*/
|
||||
public static func noNullElements(objects: [AnyObject?]) throws {
|
||||
try noNullElements(objects: objects, msg: "Array must not contain any null objects");
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates that the array contains no null elements
|
||||
* @param objects the array to test
|
||||
* @param msg message to output if validation fails
|
||||
*/
|
||||
public static func noNullElements(objects: [AnyObject?], msg: String) throws {
|
||||
for obj in objects {
|
||||
if (obj == nil){
|
||||
throw Exception.Error(type: ExceptionType.IllegalArgumentException, Message: msg)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates that the string is not empty
|
||||
* @param string the string to test
|
||||
*/
|
||||
public static func notEmpty(string: String?) throws
|
||||
{
|
||||
if (string == nil || string?.characters.count == 0){
|
||||
throw Exception.Error(type: ExceptionType.IllegalArgumentException, Message: "String must not be empty")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates that the string is not empty
|
||||
* @param string the string to test
|
||||
* @param msg message to output if validation fails
|
||||
*/
|
||||
public static func notEmpty(string: String?, msg: String ) throws {
|
||||
if (string == nil || string?.characters.count == 0){
|
||||
throw Exception.Error(type: ExceptionType.IllegalArgumentException, Message: msg)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Cause a failure.
|
||||
@param msg message to output.
|
||||
*/
|
||||
public static func fail(msg: String) throws {
|
||||
throw Exception.Error(type: ExceptionType.IllegalArgumentException, Message: msg)
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
Helper
|
||||
*/
|
||||
public static func exception(msg: String) throws {
|
||||
throw Exception.Error(type: ExceptionType.IllegalArgumentException, Message: msg)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,155 @@
|
|||
//
|
||||
// Attribute.swift
|
||||
// SwifSoup
|
||||
//
|
||||
// Created by Nabil Chatbi on 29/09/16.
|
||||
// Copyright © 2016 Nabil Chatbi.. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
open class Attribute {
|
||||
/// The element type of a dictionary: a tuple containing an individual
|
||||
/// key-value pair.
|
||||
|
||||
static let booleanAttributes: [String] = [
|
||||
"allowfullscreen", "async", "autofocus", "checked", "compact", "declare", "default", "defer", "disabled",
|
||||
"formnovalidate", "hidden", "inert", "ismap", "itemscope", "multiple", "muted", "nohref", "noresize",
|
||||
"noshade", "novalidate", "nowrap", "open", "readonly", "required", "reversed", "seamless", "selected",
|
||||
"sortable", "truespeed", "typemustmatch"
|
||||
];
|
||||
|
||||
var key: String;
|
||||
var value: String;
|
||||
|
||||
public init(key: String,value :String) throws {
|
||||
try Validate.notEmpty(string: key);
|
||||
self.key = key.trim();
|
||||
self.value = value;
|
||||
}
|
||||
|
||||
/**
|
||||
Get the attribute key.
|
||||
@return the attribute key
|
||||
*/
|
||||
open func getKey() -> String{
|
||||
return key;
|
||||
}
|
||||
|
||||
/**
|
||||
Set the attribute key; case is preserved.
|
||||
@param key the new key; must not be null
|
||||
*/
|
||||
open func setKey(key: String) throws {
|
||||
try Validate.notEmpty(string: key);
|
||||
self.key = key.trim();
|
||||
}
|
||||
|
||||
/**
|
||||
Get the attribute value.
|
||||
@return the attribute value
|
||||
*/
|
||||
open func getValue() -> String {
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
Set the attribute value.
|
||||
@param value the new attribute value; must not be null
|
||||
*/
|
||||
@discardableResult
|
||||
open func setValue(value: String) -> String {
|
||||
let old = self.value;
|
||||
self.value = value;
|
||||
return old;
|
||||
}
|
||||
|
||||
/**
|
||||
Get the HTML representation of this attribute; e.g. {@code href="index.html"}.
|
||||
@return HTML
|
||||
*/
|
||||
public func html()-> String {
|
||||
let accum = StringBuilder();
|
||||
html(accum: accum, out: (Document("")).outputSettings());
|
||||
return accum.toString();
|
||||
}
|
||||
|
||||
public func html(accum: StringBuilder, out: OutputSettings ) {
|
||||
accum.append(key);
|
||||
if (!shouldCollapseAttribute(out: out)) {
|
||||
accum.append("=\"");
|
||||
Entities.escape(accum, value, out, true, false, false);
|
||||
accum.append("\"");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Get the string representation of this attribute, implemented as {@link #html()}.
|
||||
@return string
|
||||
*/
|
||||
open func toString()-> String {
|
||||
return html();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new Attribute from an unencoded key and a HTML attribute encoded value.
|
||||
* @param unencodedKey assumes the key is not encoded, as can be only run of simple \w chars.
|
||||
* @param encodedValue HTML attribute encoded value
|
||||
* @return attribute
|
||||
*/
|
||||
open static func createFromEncoded(unencodedKey: String, encodedValue: String) throws ->Attribute {
|
||||
let value = try Entities.unescape(string: encodedValue, strict: true);
|
||||
return try Attribute(key: unencodedKey, value: value);
|
||||
}
|
||||
|
||||
public func isDataAttribute() -> Bool {
|
||||
return key.startsWith(Attributes.dataPrefix) && key.characters.count > Attributes.dataPrefix.characters.count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Collapsible if it's a boolean attribute and value is empty or same as name
|
||||
*
|
||||
* @param out Outputsettings
|
||||
* @return Returns whether collapsible or not
|
||||
*/
|
||||
public final func shouldCollapseAttribute(out: OutputSettings) -> Bool {
|
||||
return ("" == value || value.equalsIgnoreCase(string: key))
|
||||
&& out.syntax() == OutputSettings.Syntax.html
|
||||
&& isBooleanAttribute();
|
||||
}
|
||||
|
||||
public func isBooleanAttribute() -> Bool
|
||||
{
|
||||
return (Attribute.booleanAttributes.binarySearch(Attribute.booleanAttributes,key) != -1)
|
||||
}
|
||||
|
||||
|
||||
public func hashCode() -> Int {
|
||||
var result = key.hashValue;
|
||||
result = 31 * result + value.hashValue;
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
public func clone() -> Attribute
|
||||
{
|
||||
do {
|
||||
return try Attribute(key: key,value: value);
|
||||
} catch Exception.Error( _ , let msg){
|
||||
print(msg);
|
||||
}catch{
|
||||
|
||||
}
|
||||
return try! Attribute(key: "",value: "")
|
||||
}
|
||||
}
|
||||
|
||||
extension Attribute : Equatable
|
||||
{
|
||||
static public func == (lhs: Attribute, rhs: Attribute) -> Bool
|
||||
{
|
||||
return lhs.value == rhs.value && lhs.key == rhs.key
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,268 @@
|
|||
//
|
||||
// Attributes.swift
|
||||
// SwifSoup
|
||||
//
|
||||
// Created by Nabil Chatbi on 29/09/16.
|
||||
// Copyright © 2016 Nabil Chatbi.. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
/**
|
||||
* The attributes of an Element.
|
||||
* <p>
|
||||
* Attributes are treated as a map: there can be only one value associated with an attribute key/name.
|
||||
* </p>
|
||||
* <p>
|
||||
* Attribute name and value comparisons are <b>case sensitive</b>. By default for HTML, attribute names are
|
||||
* normalized to lower-case on parsing. That means you should use lower-case strings when referring to attributes by
|
||||
* name.
|
||||
* </p>
|
||||
*
|
||||
* @author Jonathan Hedley, jonathan@hedley.net
|
||||
*/
|
||||
open class Attributes : NSCopying {
|
||||
|
||||
open static var dataPrefix : String = "data-";
|
||||
|
||||
fileprivate var attributes : OrderedDictionary<String, Attribute> = OrderedDictionary<String, Attribute>()
|
||||
// linked hash map to preserve insertion order.
|
||||
// null be default as so many elements have no attributes -- saves a good chunk of memory
|
||||
|
||||
public init(){}
|
||||
|
||||
/**
|
||||
Get an attribute value by key.
|
||||
@param key the (case-sensitive) attribute key
|
||||
@return the attribute value if set; or empty string if not set.
|
||||
@see #hasKey(String)
|
||||
*/
|
||||
open func get(key : String)-> String {
|
||||
let attr : Attribute? = attributes.get(key:key);
|
||||
return attr != nil ? attr!.getValue() : ""
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an attribute's value by case-insensitive key
|
||||
* @param key the attribute name
|
||||
* @return the first matching attribute value if set; or empty string if not set.
|
||||
*/
|
||||
open func getIgnoreCase(key : String )throws -> String {
|
||||
try Validate.notEmpty(string: key);
|
||||
|
||||
for attrKey in (attributes.keySet())
|
||||
{
|
||||
if attrKey.equalsIgnoreCase(string: key){
|
||||
return attributes.get(key: attrKey)!.getValue();
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
/**
|
||||
Set a new attribute, or replace an existing one by key.
|
||||
@param key attribute key
|
||||
@param value attribute value
|
||||
*/
|
||||
open func put(_ key : String , _ value : String) throws {
|
||||
let attr = try Attribute(key: key, value: value);
|
||||
put(attribute: attr);
|
||||
}
|
||||
|
||||
/**
|
||||
Set a new boolean attribute, remove attribute if value is false.
|
||||
@param key attribute key
|
||||
@param value attribute value
|
||||
*/
|
||||
open func put(_ key : String , _ value : Bool) throws {
|
||||
if (value){
|
||||
try put(attribute: BooleanAttribute(key: key));
|
||||
}else{
|
||||
try remove(key: key);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Set a new attribute, or replace an existing one by key.
|
||||
@param attribute attribute
|
||||
*/
|
||||
open func put(attribute : Attribute) {
|
||||
attributes.put(value: attribute, forKey:attribute.getKey());
|
||||
}
|
||||
|
||||
/**
|
||||
Remove an attribute by key. <b>Case sensitive.</b>
|
||||
@param key attribute key to remove
|
||||
*/
|
||||
open func remove(key: String)throws {
|
||||
try Validate.notEmpty(string: key);
|
||||
attributes.remove(key: key);
|
||||
}
|
||||
|
||||
/**
|
||||
Remove an attribute by key. <b>Case insensitive.</b>
|
||||
@param key attribute key to remove
|
||||
*/
|
||||
open func removeIgnoreCase(key : String ) throws {
|
||||
try Validate.notEmpty(string: key);
|
||||
for attrKey in attributes.keySet(){
|
||||
if (attrKey.equalsIgnoreCase(string: key)){
|
||||
attributes.remove(key: attrKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Tests if these attributes contain an attribute with this key.
|
||||
@param key case-sensitive key to check for
|
||||
@return true if key exists, false otherwise
|
||||
*/
|
||||
open func hasKey(key : String) -> Bool {
|
||||
return attributes.containsKey(key: key);
|
||||
}
|
||||
|
||||
/**
|
||||
Tests if these attributes contain an attribute with this key.
|
||||
@param key key to check for
|
||||
@return true if key exists, false otherwise
|
||||
*/
|
||||
open func hasKeyIgnoreCase(key: String) -> Bool {
|
||||
for attrKey in attributes.keySet() {
|
||||
if (attrKey.equalsIgnoreCase(string: key)){
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
Get the number of attributes in this set.
|
||||
@return size
|
||||
*/
|
||||
open func size() -> Int {
|
||||
return attributes.count;//TODO: check retyrn right size
|
||||
}
|
||||
|
||||
/**
|
||||
Add all the attributes from the incoming set to this set.
|
||||
@param incoming attributes to add to these attributes.
|
||||
*/
|
||||
open func addAll(incoming: Attributes?) {
|
||||
guard let incoming = incoming else {
|
||||
return
|
||||
}
|
||||
|
||||
if (incoming.size() == 0){
|
||||
return;
|
||||
}
|
||||
attributes.putAll(all: incoming.attributes);
|
||||
}
|
||||
|
||||
open func iterator() -> IndexingIterator<Array<Attribute>> {
|
||||
if (attributes.isEmpty)
|
||||
{
|
||||
let args : [Attribute] = []
|
||||
return args.makeIterator()
|
||||
}
|
||||
return attributes.orderedValues.makeIterator()
|
||||
}
|
||||
|
||||
/**
|
||||
Get the attributes as a List, for iteration. Do not modify the keys of the attributes via this view, as changes
|
||||
to keys will not be recognised in the containing set.
|
||||
@return an view of the attributes as a List.
|
||||
*/
|
||||
open func asList() -> Array<Attribute> {
|
||||
var list : Array<Attribute> = Array(/*attributes.size()*/)
|
||||
for entry in attributes.orderedValues {
|
||||
list.append(entry);
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a filtered view of attributes that are HTML5 custom data attributes; that is, attributes with keys
|
||||
* starting with {@code data-}.
|
||||
* @return map of custom data attributes.
|
||||
*/
|
||||
//Map<String, String>
|
||||
open func dataset() -> Dictionary<String,String> {
|
||||
var dataset = Dictionary<String,String>()
|
||||
for attribute in attributes{
|
||||
let attr = attribute.1
|
||||
if(attr.isDataAttribute()){
|
||||
let key = attr.getKey().substring(Attributes.dataPrefix.characters.count)
|
||||
dataset[key] = attribute.1.getValue()
|
||||
}
|
||||
}
|
||||
return dataset
|
||||
}
|
||||
|
||||
/**
|
||||
Get the HTML representation of these attributes.
|
||||
@return HTML
|
||||
@throws SerializationException if the HTML representation of the attributes cannot be constructed.
|
||||
*/
|
||||
open func html()throws -> String {
|
||||
let accum = StringBuilder();
|
||||
try html(accum: accum, out: Document("").outputSettings()); // output settings a bit funky, but this html() seldom used
|
||||
return accum.toString();
|
||||
}
|
||||
|
||||
public func html(accum: StringBuilder,out: OutputSettings ) throws {
|
||||
for attribute in attributes.orderedValues {
|
||||
accum.append(" ");
|
||||
attribute.html(accum: accum, out: out);
|
||||
}
|
||||
}
|
||||
|
||||
open func toString()throws -> String {
|
||||
return try html();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if these attributes are equal to another set of attributes, by comparing the two sets
|
||||
* @param o attributes to compare with
|
||||
* @return if both sets of attributes have the same content
|
||||
*/
|
||||
open func equals(o: AnyObject?) -> Bool {
|
||||
if(o == nil){return false}
|
||||
if (self === o.self) {return true;}
|
||||
guard let that : Attributes = o as? Attributes else {return false}
|
||||
return (attributes == that.attributes)
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the hashcode of these attributes, by iterating all attributes and summing their hashcodes.
|
||||
* @return calculated hashcode
|
||||
*/
|
||||
open func hashCode() -> Int {
|
||||
return attributes.hashCode()
|
||||
}
|
||||
|
||||
public func copy(with zone: NSZone? = nil) -> Any
|
||||
{
|
||||
let clone = Attributes();
|
||||
clone.attributes = attributes.clone()
|
||||
return clone;
|
||||
}
|
||||
|
||||
open func clone() -> Attributes {
|
||||
return self.copy() as! Attributes;
|
||||
}
|
||||
|
||||
fileprivate static func dataKey(key: String) -> String {
|
||||
return dataPrefix + key;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension Attributes : Sequence {
|
||||
public func makeIterator() -> AnyIterator<Attribute> {
|
||||
var list = attributes.orderedValues
|
||||
return AnyIterator{
|
||||
return list.count > 0 ? list.removeFirst() : nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
//
|
||||
// BooleanAttribute.swift
|
||||
// SwifSoup
|
||||
//
|
||||
// Created by Nabil Chatbi on 29/09/16.
|
||||
// Copyright © 2016 Nabil Chatbi.. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
/**
|
||||
* A boolean attribute that is written out without any value.
|
||||
*/
|
||||
open class BooleanAttribute : Attribute {
|
||||
/**
|
||||
* Create a new boolean attribute from unencoded (raw) key.
|
||||
* @param key attribute key
|
||||
*/
|
||||
init(key : String) throws {
|
||||
try super.init(key: key, value: "")
|
||||
}
|
||||
|
||||
|
||||
override public func isBooleanAttribute() -> Bool {
|
||||
return true;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,78 @@
|
|||
//
|
||||
// Comment.swift
|
||||
// SwiftSoup
|
||||
//
|
||||
// Created by Nabil Chatbi on 22/10/16.
|
||||
// Copyright © 2016 Nabil Chatbi.. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
/**
|
||||
A comment node.
|
||||
*/
|
||||
public class Comment : Node {
|
||||
private static let COMMENT_KEY: String = "comment";
|
||||
|
||||
/**
|
||||
Create a new comment node.
|
||||
@param data The contents of the comment
|
||||
@param baseUri base URI
|
||||
*/
|
||||
public init(_ data: String, _ baseUri: String) {
|
||||
super.init(baseUri);
|
||||
do{
|
||||
try attributes?.put(Comment.COMMENT_KEY, data);
|
||||
}catch{}
|
||||
}
|
||||
|
||||
public override func nodeName()->String {
|
||||
return "#comment";
|
||||
}
|
||||
|
||||
/**
|
||||
Get the contents of the comment.
|
||||
@return comment content
|
||||
*/
|
||||
public func getData()->String {
|
||||
return attributes!.get(key: Comment.COMMENT_KEY);
|
||||
}
|
||||
|
||||
override func outerHtmlHead(_ accum: StringBuilder, _ depth: Int, _ out: OutputSettings) {
|
||||
if (out.prettyPrint()){
|
||||
indent(accum, depth, out);
|
||||
}
|
||||
accum
|
||||
.append("<!--")
|
||||
.append(getData())
|
||||
.append("-->");
|
||||
}
|
||||
|
||||
override func outerHtmlTail(_ accum: StringBuilder, _ depth: Int, _ out: OutputSettings) {}
|
||||
|
||||
public override func toString()->String {
|
||||
do{
|
||||
return try
|
||||
outerHtml();
|
||||
}catch{
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
public override func copy(with zone: NSZone? = nil) -> Any
|
||||
{
|
||||
let clone = Comment(attributes!.get(key: Comment.COMMENT_KEY),baseUri!)
|
||||
return copy(clone: clone)
|
||||
}
|
||||
|
||||
public override func copy(parent: Node?)->Node
|
||||
{
|
||||
let clone = Comment(attributes!.get(key: Comment.COMMENT_KEY),baseUri!)
|
||||
return copy(clone: clone,parent: parent)
|
||||
}
|
||||
|
||||
public override func copy(clone: Node, parent: Node?)->Node
|
||||
{
|
||||
return super.copy(clone: clone,parent: parent)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,93 @@
|
|||
//
|
||||
// DataNode.swift
|
||||
// SwifSoup
|
||||
//
|
||||
// Created by Nabil Chatbi on 29/09/16.
|
||||
// Copyright © 2016 Nabil Chatbi.. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
/**
|
||||
A data node, for contents of style, script tags etc, where contents should not show in text().
|
||||
*/
|
||||
open class DataNode : Node{
|
||||
private static let DATA_KEY : String = "data";
|
||||
|
||||
/**
|
||||
Create a new DataNode.
|
||||
@param data data contents
|
||||
@param baseUri base URI
|
||||
*/
|
||||
public init(_ data: String, _ baseUri: String) {
|
||||
super.init(baseUri);
|
||||
do{
|
||||
try attributes?.put(DataNode.DATA_KEY, data);
|
||||
}catch{}
|
||||
|
||||
}
|
||||
|
||||
open override func nodeName()->String {
|
||||
return "#data";
|
||||
}
|
||||
|
||||
/**
|
||||
Get the data contents of this node. Will be unescaped and with original new lines, space etc.
|
||||
@return data
|
||||
*/
|
||||
open func getWholeData()->String {
|
||||
return attributes!.get(key: DataNode.DATA_KEY)
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the data contents of this node.
|
||||
* @param data unencoded data
|
||||
* @return this node, for chaining
|
||||
*/
|
||||
@discardableResult
|
||||
open func setWholeData(_ data: String)->DataNode {
|
||||
do{
|
||||
try attributes?.put(DataNode.DATA_KEY, data);
|
||||
}catch{}
|
||||
return self;
|
||||
}
|
||||
|
||||
override func outerHtmlHead(_ accum: StringBuilder, _ depth: Int, _ out: OutputSettings)throws {
|
||||
accum.append(getWholeData()); // data is not escaped in return from data nodes, so " in script, style is plain
|
||||
}
|
||||
|
||||
override func outerHtmlTail(_ accum: StringBuilder, _ depth: Int, _ out: OutputSettings) {}
|
||||
|
||||
|
||||
open override func toString()throws->String {
|
||||
return try outerHtml();
|
||||
}
|
||||
|
||||
/**
|
||||
Create a new DataNode from HTML encoded data.
|
||||
@param encodedData encoded data
|
||||
@param baseUri bass URI
|
||||
@return new DataNode
|
||||
*/
|
||||
open static func createFromEncoded(_ encodedData: String, _ baseUri: String)throws->DataNode {
|
||||
let data = try Entities.unescape(encodedData);
|
||||
return DataNode(data, baseUri);
|
||||
}
|
||||
|
||||
public override func copy(with zone: NSZone? = nil) -> Any
|
||||
{
|
||||
let clone = DataNode(attributes!.get(key: DataNode.DATA_KEY),baseUri!)
|
||||
return copy(clone: clone)
|
||||
}
|
||||
|
||||
public override func copy(parent: Node?)->Node
|
||||
{
|
||||
let clone = DataNode(attributes!.get(key: DataNode.DATA_KEY),baseUri!)
|
||||
return copy(clone: clone,parent: parent)
|
||||
}
|
||||
|
||||
public override func copy(clone: Node, parent: Node?)->Node
|
||||
{
|
||||
return super.copy(clone: clone,parent: parent)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,578 @@
|
|||
//
|
||||
// Document.swift
|
||||
// SwifSoup
|
||||
//
|
||||
// Created by Nabil Chatbi on 29/09/16.
|
||||
// Copyright © 2016 Nabil Chatbi.. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
open class Document : Element
|
||||
{
|
||||
public enum QuirksMode {
|
||||
case noQuirks, quirks, limitedQuirks
|
||||
}
|
||||
|
||||
private var _outputSettings: OutputSettings = OutputSettings();
|
||||
private var _quirksMode: Document.QuirksMode = QuirksMode.noQuirks;
|
||||
private let _location: String;
|
||||
private var updateMetaCharset: Bool = false;
|
||||
|
||||
/**
|
||||
Create a new, empty Document.
|
||||
@param baseUri base URI of document
|
||||
@see org.jsoup.Jsoup#parse
|
||||
@see #createShell
|
||||
*/
|
||||
public init(_ baseUri: String){
|
||||
self._location = baseUri;
|
||||
super.init(try! Tag.valueOf("#root", ParseSettings.htmlDefault), baseUri);
|
||||
}
|
||||
|
||||
/**
|
||||
Create a valid, empty shell of a document, suitable for adding more elements to.
|
||||
@param baseUri baseUri of document
|
||||
@return document with html, head, and body elements.
|
||||
*/
|
||||
static open func createShell(_ baseUri: String)->Document {
|
||||
let doc: Document = Document(baseUri);
|
||||
let html: Element = try! doc.appendElement("html");
|
||||
try! html.appendElement("head");
|
||||
try! html.appendElement("body");
|
||||
|
||||
return doc;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the URL this Document was parsed from. If the starting URL is a redirect,
|
||||
* this will return the final URL from which the document was served from.
|
||||
* @return location
|
||||
*/
|
||||
public func location()->String {
|
||||
return _location;
|
||||
}
|
||||
|
||||
/**
|
||||
Accessor to the document's {@code head} element.
|
||||
@return {@code head}
|
||||
*/
|
||||
public func head()->Element? {
|
||||
return findFirstElementByTagName("head", self);
|
||||
}
|
||||
|
||||
/**
|
||||
Accessor to the document's {@code body} element.
|
||||
@return {@code body}
|
||||
*/
|
||||
public func body()->Element? {
|
||||
return findFirstElementByTagName("body", self);
|
||||
}
|
||||
|
||||
/**
|
||||
Get the string contents of the document's {@code title} element.
|
||||
@return Trimmed title, or empty string if none set.
|
||||
*/
|
||||
public func title()throws->String {
|
||||
// title is a preserve whitespace tag (for document output), but normalised here
|
||||
let titleEl: Element? = try getElementsByTag("title").first();
|
||||
return titleEl != nil ? try StringUtil.normaliseWhitespace(titleEl!.text()).trim() : "";
|
||||
}
|
||||
|
||||
/**
|
||||
Set the document's {@code title} element. Updates the existing element, or adds {@code title} to {@code head} if
|
||||
not present
|
||||
@param title string to set as title
|
||||
*/
|
||||
public func title(_ title: String)throws {
|
||||
let titleEl: Element? = try getElementsByTag("title").first();
|
||||
if (titleEl == nil) { // add to head
|
||||
try head()?.appendElement("title").text(title);
|
||||
} else {
|
||||
try titleEl?.text(title);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Create a new Element, with this document's base uri. Does not make the new element a child of this document.
|
||||
@param tagName element tag name (e.g. {@code a})
|
||||
@return new element
|
||||
*/
|
||||
public func createElement(_ tagName: String)throws->Element {
|
||||
return try Element(Tag.valueOf(tagName, ParseSettings.preserveCase), self.getBaseUri());
|
||||
}
|
||||
|
||||
/**
|
||||
Normalise the document. This happens after the parse phase so generally does not need to be called.
|
||||
Moves any text content that is not in the body element into the body.
|
||||
@return this document after normalisation
|
||||
*/
|
||||
@discardableResult
|
||||
public func normalise()throws->Document {
|
||||
var htmlE: Element? = findFirstElementByTagName("html", self);
|
||||
if (htmlE == nil){
|
||||
htmlE = try appendElement("html")
|
||||
}
|
||||
let htmlEl: Element = htmlE!
|
||||
|
||||
if (head() == nil){
|
||||
try htmlEl.prependElement("head")
|
||||
}
|
||||
if (body() == nil){
|
||||
try htmlEl.appendElement("body")
|
||||
}
|
||||
|
||||
// pull text nodes out of root, html, and head els, and push into body. non-text nodes are already taken care
|
||||
// of. do in inverse order to maintain text order.
|
||||
try normaliseTextNodes(head()!);
|
||||
try normaliseTextNodes(htmlEl);
|
||||
try normaliseTextNodes(self);
|
||||
|
||||
try normaliseStructure("head", htmlEl);
|
||||
try normaliseStructure("body", htmlEl);
|
||||
|
||||
try ensureMetaCharsetElement();
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
// does not recurse.
|
||||
private func normaliseTextNodes(_ element: Element)throws {
|
||||
var toMove: Array<Node> = Array<Node>();
|
||||
for node:Node in element.childNodes
|
||||
{
|
||||
if let tn = (node as? TextNode) {
|
||||
if (!tn.isBlank()){
|
||||
toMove.append(tn);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for i in toMove.count-1...0
|
||||
{
|
||||
let node: Node = toMove[i]
|
||||
try element.removeChild(node);
|
||||
try body()?.prependChild(TextNode(" ", ""));
|
||||
try body()?.prependChild(node);
|
||||
}
|
||||
}
|
||||
|
||||
// merge multiple <head> or <body> contents into one, delete the remainder, and ensure they are owned by <html>
|
||||
private func normaliseStructure(_ tag: String, _ htmlEl: Element)throws {
|
||||
let elements: Elements = try self.getElementsByTag(tag);
|
||||
let master: Element? = elements.first(); // will always be available as created above if not existent
|
||||
if (elements.size() > 1) { // dupes, move contents to master
|
||||
var toMove:Array<Node> = Array<Node>();
|
||||
for i in 1..<elements.size()
|
||||
{
|
||||
let dupe: Node = elements.get(i);
|
||||
for node:Node in dupe.childNodes
|
||||
{
|
||||
toMove.append(node);
|
||||
}
|
||||
try dupe.remove();
|
||||
}
|
||||
|
||||
for dupe:Node in toMove{
|
||||
try master?.appendChild(dupe);
|
||||
}
|
||||
}
|
||||
// ensure parented by <html>
|
||||
if (!(master != nil && master!.parent() != nil && master!.parent()!.equals(htmlEl))) {
|
||||
try htmlEl.appendChild(master!); // includes remove()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// fast method to get first by tag name, used for html, head, body finders
|
||||
private func findFirstElementByTagName(_ tag: String, _ node: Node)->Element? {
|
||||
if (node.nodeName()==tag){
|
||||
return node as? Element;
|
||||
}else {
|
||||
for child:Node in node.childNodes {
|
||||
let found: Element? = findFirstElementByTagName(tag, child);
|
||||
if (found != nil){
|
||||
return found;
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
open override func outerHtml()throws->String {
|
||||
return try super.html(); // no outer wrapper tag
|
||||
}
|
||||
|
||||
/**
|
||||
Set the text of the {@code body} of this document. Any existing nodes within the body will be cleared.
|
||||
@param text unencoded text
|
||||
@return this document
|
||||
*/
|
||||
@discardableResult
|
||||
public override func text(_ text: String)throws->Element {
|
||||
try body()?.text(text); // overridden to not nuke doc structure
|
||||
return self;
|
||||
}
|
||||
|
||||
open override func nodeName()->String {
|
||||
return "#document";
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the charset used in this document. This method is equivalent
|
||||
* to {@link OutputSettings#charset(java.nio.charset.Charset)
|
||||
* OutputSettings.charset(Charset)} but in addition it updates the
|
||||
* charset / encoding element within the document.
|
||||
*
|
||||
* <p>This enables
|
||||
* {@link #updateMetaCharsetElement(boolean) meta charset update}.</p>
|
||||
*
|
||||
* <p>If there's no element with charset / encoding information yet it will
|
||||
* be created. Obsolete charset / encoding definitions are removed!</p>
|
||||
*
|
||||
* <p><b>Elements used:</b></p>
|
||||
*
|
||||
* <ul>
|
||||
* <li><b>Html:</b> <i><meta charset="CHARSET"></i></li>
|
||||
* <li><b>Xml:</b> <i><?xml version="1.0" encoding="CHARSET"></i></li>
|
||||
* </ul>
|
||||
*
|
||||
* @param charset Charset
|
||||
*
|
||||
* @see #updateMetaCharsetElement(boolean)
|
||||
* @see OutputSettings#charset(java.nio.charset.Charset)
|
||||
*/
|
||||
public func charset(_ charset: String.Encoding)throws {
|
||||
updateMetaCharsetElement(true);
|
||||
_outputSettings.charset(charset);
|
||||
try ensureMetaCharsetElement();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the charset used in this document. This method is equivalent
|
||||
* to {@link OutputSettings#charset()}.
|
||||
*
|
||||
* @return Current Charset
|
||||
*
|
||||
* @see OutputSettings#charset()
|
||||
*/
|
||||
public func charset()->String.Encoding {
|
||||
return _outputSettings.charset();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether the element with charset information in this document is
|
||||
* updated on changes through {@link #charset(java.nio.charset.Charset)
|
||||
* Document.charset(Charset)} or not.
|
||||
*
|
||||
* <p>If set to <tt>false</tt> <i>(default)</i> there are no elements
|
||||
* modified.</p>
|
||||
*
|
||||
* @param update If <tt>true</tt> the element updated on charset
|
||||
* changes, <tt>false</tt> if not
|
||||
*
|
||||
* @see #charset(java.nio.charset.Charset)
|
||||
*/
|
||||
public func updateMetaCharsetElement(_ update: Bool) {
|
||||
self.updateMetaCharset = update;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the element with charset information in this document is
|
||||
* updated on changes through {@link #charset(java.nio.charset.Charset)
|
||||
* Document.charset(Charset)} or not.
|
||||
*
|
||||
* @return Returns <tt>true</tt> if the element is updated on charset
|
||||
* changes, <tt>false</tt> if not
|
||||
*/
|
||||
public func updateMetaCharsetElement()->Bool {
|
||||
return updateMetaCharset;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures a meta charset (html) or xml declaration (xml) with the current
|
||||
* encoding used. This only applies with
|
||||
* {@link #updateMetaCharsetElement(boolean) updateMetaCharset} set to
|
||||
* <tt>true</tt>, otherwise this method does nothing.
|
||||
*
|
||||
* <ul>
|
||||
* <li>An exsiting element gets updated with the current charset</li>
|
||||
* <li>If there's no element yet it will be inserted</li>
|
||||
* <li>Obsolete elements are removed</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p><b>Elements used:</b></p>
|
||||
*
|
||||
* <ul>
|
||||
* <li><b>Html:</b> <i><meta charset="CHARSET"></i></li>
|
||||
* <li><b>Xml:</b> <i><?xml version="1.0" encoding="CHARSET"></i></li>
|
||||
* </ul>
|
||||
*/
|
||||
private func ensureMetaCharsetElement()throws {
|
||||
if (updateMetaCharset) {
|
||||
let syntax: OutputSettings.Syntax = outputSettings().syntax();
|
||||
|
||||
if (syntax == OutputSettings.Syntax.html) {
|
||||
let metaCharset: Element? = try select("meta[charset]").first();
|
||||
|
||||
if (metaCharset != nil) {
|
||||
try metaCharset?.attr("charset", charset().displayName());
|
||||
} else {
|
||||
let head: Element? = self.head();
|
||||
|
||||
if (head != nil) {
|
||||
try head?.appendElement("meta").attr("charset", charset().displayName());
|
||||
}
|
||||
}
|
||||
|
||||
// Remove obsolete elements
|
||||
let s = try select("meta[name=charset]")
|
||||
try s.remove()
|
||||
|
||||
} else if (syntax == OutputSettings.Syntax.xml) {
|
||||
let node: Node = getChildNodes()[0]
|
||||
|
||||
if let decl = (node as? XmlDeclaration) {
|
||||
|
||||
if (decl.name()=="xml") {
|
||||
try decl.attr("encoding", charset().displayName());
|
||||
|
||||
_ = try decl.attr("version");
|
||||
try decl.attr("version", "1.0");
|
||||
} else {
|
||||
try Validate.notNull(obj: baseUri)
|
||||
let decl = XmlDeclaration("xml", baseUri!, false);
|
||||
try decl.attr("version", "1.0");
|
||||
try decl.attr("encoding", charset().displayName());
|
||||
|
||||
try prependChild(decl);
|
||||
}
|
||||
} else {
|
||||
try Validate.notNull(obj: baseUri)
|
||||
let decl = XmlDeclaration("xml", baseUri!, false);
|
||||
try decl.attr("version", "1.0");
|
||||
try decl.attr("encoding", charset().displayName());
|
||||
|
||||
try prependChild(decl);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the document's current output settings.
|
||||
* @return the document's current output settings.
|
||||
*/
|
||||
public func outputSettings()->OutputSettings {
|
||||
return _outputSettings
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the document's output settings.
|
||||
* @param outputSettings new output settings.
|
||||
* @return this document, for chaining.
|
||||
*/
|
||||
@discardableResult
|
||||
public func outputSettings(_ outputSettings: OutputSettings)->Document {
|
||||
self._outputSettings = outputSettings;
|
||||
return self;
|
||||
}
|
||||
|
||||
public func quirksMode()->Document.QuirksMode {
|
||||
return _quirksMode
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
public func quirksMode(_ quirksMode: Document.QuirksMode)->Document {
|
||||
self._quirksMode = quirksMode;
|
||||
return self;
|
||||
}
|
||||
|
||||
public override func copy(with zone: NSZone? = nil) -> Any
|
||||
{
|
||||
let clone = Document(_location)
|
||||
return copy(clone: clone)
|
||||
}
|
||||
|
||||
public override func copy(parent: Node?)->Node
|
||||
{
|
||||
let clone = Document(_location)
|
||||
return copy(clone: clone,parent: parent)
|
||||
}
|
||||
|
||||
public override func copy(clone: Node, parent: Node?)->Node
|
||||
{
|
||||
let clone = clone as! Document
|
||||
clone._outputSettings = _outputSettings.copy() as! OutputSettings;
|
||||
clone._quirksMode = _quirksMode
|
||||
clone.updateMetaCharset = updateMetaCharset
|
||||
return super.copy(clone: clone,parent: parent)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
public class OutputSettings: NSCopying {
|
||||
/**
|
||||
* The output serialization syntax.
|
||||
*/
|
||||
public enum Syntax {case html, xml}
|
||||
|
||||
private var _escapeMode : Entities.EscapeMode = Entities.EscapeMode.base;
|
||||
private var _encoder : String.Encoding = String.Encoding.utf8 // Charset.forName("UTF-8");
|
||||
private var _prettyPrint : Bool = true;
|
||||
private var _outline : Bool = false;
|
||||
private var _indentAmount : UInt = 1;
|
||||
private var _syntax = Syntax.html;
|
||||
|
||||
public init() {}
|
||||
|
||||
/**
|
||||
* Get the document's current HTML escape mode: <code>base</code>, which provides a limited set of named HTML
|
||||
* entities and escapes other characters as numbered entities for maximum compatibility; or <code>extended</code>,
|
||||
* which uses the complete set of HTML named entities.
|
||||
* <p>
|
||||
* The default escape mode is <code>base</code>.
|
||||
* @return the document's current escape mode
|
||||
*/
|
||||
public func escapeMode() -> Entities.EscapeMode {
|
||||
return _escapeMode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the document's escape mode, which determines how characters are escaped when the output character set
|
||||
* does not support a given character:- using either a named or a numbered escape.
|
||||
* @param escapeMode the new escape mode to use
|
||||
* @return the document's output settings, for chaining
|
||||
*/
|
||||
@discardableResult
|
||||
public func escapeMode(_ escapeMode: Entities.EscapeMode) -> OutputSettings {
|
||||
self._escapeMode = escapeMode;
|
||||
return self;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the document's current output charset, which is used to control which characters are escaped when
|
||||
* generating HTML (via the <code>html()</code> methods), and which are kept intact.
|
||||
* <p>
|
||||
* Where possible (when parsing from a URL or File), the document's output charset is automatically set to the
|
||||
* input charset. Otherwise, it defaults to UTF-8.
|
||||
* @return the document's current charset.
|
||||
*/
|
||||
public func encoder() -> String.Encoding {
|
||||
return _encoder
|
||||
}
|
||||
public func charset() -> String.Encoding {
|
||||
return _encoder
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the document's output charset.
|
||||
* @param charset the new charset to use.
|
||||
* @return the document's output settings, for chaining
|
||||
*/
|
||||
@discardableResult
|
||||
public func encoder(_ encoder: String.Encoding) -> OutputSettings {
|
||||
self._encoder = encoder;
|
||||
return self;
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
public func charset(_ e: String.Encoding) -> OutputSettings {
|
||||
return encoder(e);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Get the document's current output syntax.
|
||||
* @return current syntax
|
||||
*/
|
||||
public func syntax()-> Syntax {
|
||||
return _syntax;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the document's output syntax. Either {@code html}, with empty tags and boolean attributes (etc), or
|
||||
* {@code xml}, with self-closing tags.
|
||||
* @param syntax serialization syntax
|
||||
* @return the document's output settings, for chaining
|
||||
*/
|
||||
@discardableResult
|
||||
public func syntax(syntax: Syntax)->OutputSettings {
|
||||
_syntax = syntax;
|
||||
return self;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get if pretty printing is enabled. Default is true. If disabled, the HTML output methods will not re-format
|
||||
* the output, and the output will generally look like the input.
|
||||
* @return if pretty printing is enabled.
|
||||
*/
|
||||
public func prettyPrint()->Bool {
|
||||
return _prettyPrint;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable or disable pretty printing.
|
||||
* @param pretty new pretty print setting
|
||||
* @return this, for chaining
|
||||
*/
|
||||
@discardableResult
|
||||
public func prettyPrint(pretty: Bool)->OutputSettings {
|
||||
_prettyPrint = pretty;
|
||||
return self;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get if outline mode is enabled. Default is false. If enabled, the HTML output methods will consider
|
||||
* all tags as block.
|
||||
* @return if outline mode is enabled.
|
||||
*/
|
||||
public func outline()->Bool {
|
||||
return _outline;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable or disable HTML outline mode.
|
||||
* @param outlineMode new outline setting
|
||||
* @return this, for chaining
|
||||
*/
|
||||
@discardableResult
|
||||
public func outline(outlineMode: Bool)->OutputSettings {
|
||||
_outline = outlineMode;
|
||||
return self;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current tag indent amount, used when pretty printing.
|
||||
* @return the current indent amount
|
||||
*/
|
||||
public func indentAmount()-> UInt {
|
||||
return _indentAmount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the indent amount for pretty printing
|
||||
* @param indentAmount number of spaces to use for indenting each level. Must be {@literal >=} 0.
|
||||
* @return this, for chaining
|
||||
*/
|
||||
@discardableResult
|
||||
public func indentAmount(indentAmount: UInt)-> OutputSettings {
|
||||
_indentAmount = indentAmount;
|
||||
return self;
|
||||
}
|
||||
|
||||
|
||||
public func copy(with zone: NSZone? = nil) -> Any{
|
||||
let clone: OutputSettings = OutputSettings()
|
||||
clone.charset(_encoder); // new charset and charset encoder
|
||||
clone._escapeMode = _escapeMode//Entities.EscapeMode.valueOf(escapeMode.name());
|
||||
// indentAmount, prettyPrint are primitives so object.clone() will handle
|
||||
return clone;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,100 @@
|
|||
//
|
||||
// DocumentType.swift
|
||||
// SwifSoup
|
||||
//
|
||||
// Created by Nabil Chatbi on 29/09/16.
|
||||
// Copyright © 2016 Nabil Chatbi.. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
/**
|
||||
* A {@code <!DOCTYPE>} node.
|
||||
*/
|
||||
public class DocumentType : Node {
|
||||
private static let NAME: String = "name";
|
||||
private static let PUBLIC_ID: String = "publicId";
|
||||
private static let SYSTEM_ID: String = "systemId";
|
||||
// todo: quirk mode from publicId and systemId
|
||||
|
||||
/**
|
||||
* Create a new doctype element.
|
||||
* @param name the doctype's name
|
||||
* @param publicId the doctype's public ID
|
||||
* @param systemId the doctype's system ID
|
||||
* @param baseUri the doctype's base URI
|
||||
*/
|
||||
public init(_ name: String, _ publicId: String, _ systemId: String, _ baseUri: String) {
|
||||
super.init(baseUri);
|
||||
do{
|
||||
try attr(DocumentType.NAME, name);
|
||||
try attr(DocumentType.PUBLIC_ID, publicId);
|
||||
try attr(DocumentType.SYSTEM_ID, systemId);
|
||||
}catch{}
|
||||
}
|
||||
|
||||
public override func nodeName()->String {
|
||||
return "#doctype";
|
||||
}
|
||||
|
||||
override func outerHtmlHead(_ accum: StringBuilder, _ depth: Int, _ out: OutputSettings) {
|
||||
if (out.syntax() == OutputSettings.Syntax.html && !has(DocumentType.PUBLIC_ID) && !has(DocumentType.SYSTEM_ID)) {
|
||||
// looks like a html5 doctype, go lowercase for aesthetics
|
||||
accum.append("<!doctype");
|
||||
} else {
|
||||
accum.append("<!DOCTYPE");
|
||||
}
|
||||
if (has(DocumentType.NAME)){
|
||||
do{
|
||||
accum.append(" ").append(try attr(DocumentType.NAME));
|
||||
}catch{}
|
||||
|
||||
}
|
||||
if (has(DocumentType.PUBLIC_ID)){
|
||||
do{
|
||||
accum.append(" PUBLIC \"").append(try attr(DocumentType.PUBLIC_ID)).append("\"");
|
||||
}catch{}
|
||||
|
||||
}
|
||||
if (has(DocumentType.SYSTEM_ID)){
|
||||
do{
|
||||
accum.append(" \"").append(try attr(DocumentType.SYSTEM_ID)).append("\"");
|
||||
}catch{}
|
||||
|
||||
}
|
||||
accum.append(">");
|
||||
}
|
||||
|
||||
override func outerHtmlTail(_ accum: StringBuilder, _ depth: Int, _ out: OutputSettings) {
|
||||
}
|
||||
|
||||
private func has(_ attribute: String)->Bool {
|
||||
do{
|
||||
return !StringUtil.isBlank(try attr(attribute));
|
||||
}catch{return false}
|
||||
}
|
||||
|
||||
public override func copy(with zone: NSZone? = nil) -> Any
|
||||
{
|
||||
let clone = DocumentType(attributes!.get(key: DocumentType.NAME),
|
||||
attributes!.get(key: DocumentType.PUBLIC_ID),
|
||||
attributes!.get(key: DocumentType.SYSTEM_ID),
|
||||
baseUri!)
|
||||
return copy(clone: clone)
|
||||
}
|
||||
|
||||
public override func copy(parent: Node?)->Node
|
||||
{
|
||||
let clone = DocumentType(attributes!.get(key: DocumentType.NAME),
|
||||
attributes!.get(key: DocumentType.PUBLIC_ID),
|
||||
attributes!.get(key: DocumentType.SYSTEM_ID),
|
||||
baseUri!)
|
||||
return copy(clone: clone,parent: parent)
|
||||
}
|
||||
|
||||
public override func copy(clone: Node, parent: Node?)->Node
|
||||
{
|
||||
return super.copy(clone: clone,parent: parent)
|
||||
}
|
||||
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,337 @@
|
|||
//
|
||||
// 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 {
|
||||
static let entityPattern : Pattern = Pattern("^(\\w+)=(\\w+)(?:,(\\w+))?;(\\w+)$")
|
||||
static let empty = -1;
|
||||
static let emptyName = "";
|
||||
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(file: "entities-xhtml.properties", size: 4, id: 0)
|
||||
/** Default HTML output entities. */
|
||||
public static let base : EscapeMode = EscapeMode(file: "entities-base.properties", size: 106, id: 1)
|
||||
/** Complete HTML entities. */
|
||||
public static let extended: EscapeMode = EscapeMode(file: "entities-full.properties", 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
|
||||
}
|
||||
|
||||
init(file: 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 frameworkBundle = Bundle(for: Entities.self)
|
||||
guard let path = frameworkBundle.path(forResource:file, ofType: "") else {
|
||||
return
|
||||
}
|
||||
if let aStreamReader = StreamReader(path:path) {
|
||||
defer
|
||||
{
|
||||
aStreamReader.close()
|
||||
}
|
||||
|
||||
var i = 0;
|
||||
while let entry = aStreamReader.nextLine() {
|
||||
// NotNestedLessLess=10913,824;1887
|
||||
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
|
||||
{
|
||||
let index = nameKeys.binarySearch(nameKeys,name)
|
||||
return index >= 0 ? codeVals[index] : empty;
|
||||
}
|
||||
|
||||
public func nameForCodepoint(_ codepoint: Int )->String {
|
||||
//let ss = codeKeys.index(of: codepoint)
|
||||
let index = codeKeys.binarySearch(codeKeys,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
|
||||
*/
|
||||
open 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)
|
||||
*/
|
||||
open 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
|
||||
*/
|
||||
open 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
|
||||
*/
|
||||
open 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;
|
||||
}
|
||||
|
||||
open 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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
open 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.characters
|
||||
{
|
||||
codePoint = ch.unicodeScalar
|
||||
|
||||
if (normaliseWhite) {
|
||||
if (codePoint.isWhitespace) {
|
||||
if ((stripLeadingWhite && !reachedNonWhite) || lastWasWhite){
|
||||
continue;
|
||||
}
|
||||
accum.append(" ");
|
||||
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 "&":
|
||||
accum.append("&");
|
||||
break;
|
||||
case UnicodeScalar(UInt32(0xA0))!:
|
||||
if (escapeMode != EscapeMode.xhtml){
|
||||
accum.append(" ");
|
||||
}else{
|
||||
accum.append(" ");
|
||||
}
|
||||
break;
|
||||
case "<":
|
||||
// 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("<");
|
||||
}else{
|
||||
accum.append(c);
|
||||
}
|
||||
break;
|
||||
case ">":
|
||||
if (!inAttribute){
|
||||
accum.append(">");
|
||||
}else{
|
||||
accum.append(c);}
|
||||
break;
|
||||
case "\"":
|
||||
if (inAttribute){
|
||||
accum.append(""");
|
||||
}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("&").append(name).append(";");
|
||||
}else{
|
||||
accum.append("&#x").append(String.toHexString(n:Int(codePoint.value)) ).append(";");
|
||||
}
|
||||
}
|
||||
|
||||
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)))
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,128 @@
|
|||
//
|
||||
// FormElement.swift
|
||||
// SwifSoup
|
||||
//
|
||||
// Created by Nabil Chatbi on 29/09/16.
|
||||
// Copyright © 2016 Nabil Chatbi.. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
/**
|
||||
* A HTML Form Element provides ready access to the form fields/controls that are associated with it. It also allows a
|
||||
* form to easily be submitted.
|
||||
*/
|
||||
public class FormElement : Element {
|
||||
private let _elements: Elements = Elements();
|
||||
|
||||
/**
|
||||
* Create a new, standalone form element.
|
||||
*
|
||||
* @param tag tag of this element
|
||||
* @param baseUri the base URI
|
||||
* @param attributes initial attributes
|
||||
*/
|
||||
public override init(_ tag: Tag, _ baseUri: String, _ attributes: Attributes) {
|
||||
super.init(tag, baseUri, attributes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of form control elements associated with this form.
|
||||
* @return form controls associated with this element.
|
||||
*/
|
||||
public func elements()->Elements {
|
||||
return _elements;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a form control element to this form.
|
||||
* @param element form control to add
|
||||
* @return this form element, for chaining
|
||||
*/
|
||||
@discardableResult
|
||||
public func addElement(_ element: Element)->FormElement {
|
||||
_elements.add(element);
|
||||
return self;
|
||||
}
|
||||
|
||||
//todo:
|
||||
/**
|
||||
* Prepare to submit this form. A Connection object is created with the request set up from the form values. You
|
||||
* can then set up other options (like user-agent, timeout, cookies), then execute it.
|
||||
* @return a connection prepared from the values of this form.
|
||||
* @throws IllegalArgumentException if the form's absolute action URL cannot be determined. Make sure you pass the
|
||||
* document's base URI when parsing.
|
||||
*/
|
||||
// public func submit()throws->Connection {
|
||||
// let action: String = hasAttr("action") ? try absUrl("action") : try baseUri();
|
||||
// Validate.notEmpty(action, "Could not determine a form action URL for submit. Ensure you set a base URI when parsing.");
|
||||
// Connection.Method method = attr("method").toUpperCase().equals("POST") ?
|
||||
// Connection.Method.POST : Connection.Method.GET;
|
||||
//
|
||||
// return Jsoup.connect(action)
|
||||
// .data(formData())
|
||||
// .method(method);
|
||||
// }
|
||||
|
||||
//todo:
|
||||
/**
|
||||
* Get the data that this form submits. The returned list is a copy of the data, and changes to the contents of the
|
||||
* list will not be reflected in the DOM.
|
||||
* @return a list of key vals
|
||||
*/
|
||||
// public List<Connection.KeyVal> formData() {
|
||||
// ArrayList<Connection.KeyVal> data = new ArrayList<Connection.KeyVal>();
|
||||
//
|
||||
// // iterate the form control elements and accumulate their values
|
||||
// for (Element el: elements) {
|
||||
// if (!el.tag().isFormSubmittable()) continue; // contents are form listable, superset of submitable
|
||||
// if (el.hasAttr("disabled")) continue; // skip disabled form inputs
|
||||
// String name = el.attr("name");
|
||||
// if (name.length() == 0) continue;
|
||||
// String type = el.attr("type");
|
||||
//
|
||||
// if ("select".equals(el.tagName())) {
|
||||
// Elements options = el.select("option[selected]");
|
||||
// boolean set = false;
|
||||
// for (Element option: options) {
|
||||
// data.add(HttpConnection.KeyVal.create(name, option.val()));
|
||||
// set = true;
|
||||
// }
|
||||
// if (!set) {
|
||||
// Element option = el.select("option").first();
|
||||
// if (option != null)
|
||||
// data.add(HttpConnection.KeyVal.create(name, option.val()));
|
||||
// }
|
||||
// } else if ("checkbox".equalsIgnoreCase(type) || "radio".equalsIgnoreCase(type)) {
|
||||
// // only add checkbox or radio if they have the checked attribute
|
||||
// if (el.hasAttr("checked")) {
|
||||
// final String val = el.val().length() > 0 ? el.val() : "on";
|
||||
// data.add(HttpConnection.KeyVal.create(name, val));
|
||||
// }
|
||||
// } else {
|
||||
// data.add(HttpConnection.KeyVal.create(name, el.val()));
|
||||
// }
|
||||
// }
|
||||
// return data;
|
||||
// }
|
||||
|
||||
public override func copy(with zone: NSZone? = nil) -> Any
|
||||
{
|
||||
let clone = FormElement(_tag,baseUri!,attributes!)
|
||||
return copy(clone: clone)
|
||||
}
|
||||
|
||||
public override func copy(parent: Node?)->Node
|
||||
{
|
||||
let clone = FormElement(_tag,baseUri!,attributes!)
|
||||
return copy(clone: clone,parent: parent)
|
||||
}
|
||||
public override func copy(clone: Node, parent: Node?)->Node
|
||||
{
|
||||
let clone = clone as! FormElement
|
||||
for att in _elements.array(){
|
||||
clone._elements.add(att)
|
||||
}
|
||||
return super.copy(clone: clone,parent: parent)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,841 @@
|
|||
//
|
||||
// Node.swift
|
||||
// SwifSoup
|
||||
//
|
||||
// Created by Nabil Chatbi on 29/09/16.
|
||||
// Copyright © 2016 Nabil Chatbi.. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
open class Node : Equatable, Hashable
|
||||
{
|
||||
private static let EMPTY_NODES : Array<Node> = Array<Node>();
|
||||
var parentNode : Node?
|
||||
var childNodes : Array <Node>
|
||||
var attributes : Attributes?
|
||||
var baseUri : String?
|
||||
|
||||
/**
|
||||
* Get the list index of this node in its node sibling list. I.e. if this is the first node
|
||||
* sibling, returns 0.
|
||||
* @return position in node sibling list
|
||||
* @see org.jsoup.nodes.Element#elementSiblingIndex()
|
||||
*/
|
||||
public private(set) var siblingIndex : Int = 0
|
||||
|
||||
/**
|
||||
Create a new Node.
|
||||
@param baseUri base URI
|
||||
@param attributes attributes (not null, but may be empty)
|
||||
*/
|
||||
public init(_ baseUri: String, _ attributes: Attributes) {
|
||||
self.childNodes = Node.EMPTY_NODES
|
||||
self.baseUri = baseUri.trim()
|
||||
self.attributes = attributes
|
||||
}
|
||||
|
||||
public init(_ baseUri: String) {
|
||||
childNodes = Node.EMPTY_NODES
|
||||
self.baseUri = baseUri.trim()
|
||||
self.attributes = Attributes()
|
||||
}
|
||||
|
||||
/**
|
||||
* Default constructor. Doesn't setup base uri, children, or attributes; use with caution.
|
||||
*/
|
||||
public init() {
|
||||
self.childNodes = Node.EMPTY_NODES;
|
||||
self.attributes = nil;
|
||||
self.baseUri = nil;
|
||||
}
|
||||
|
||||
/**
|
||||
Get the node name of this node. Use for debugging purposes and not logic switching (for that, use instanceof).
|
||||
@return node name
|
||||
*/
|
||||
public func nodeName()->String {
|
||||
preconditionFailure("This method must be overridden")
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an attribute's value by its key. <b>Case insensitive</b>
|
||||
* <p>
|
||||
* To get an absolute URL from an attribute that may be a relative URL, prefix the key with <code><b>abs</b></code>,
|
||||
* which is a shortcut to the {@link #absUrl} method.
|
||||
* </p>
|
||||
* E.g.:
|
||||
* <blockquote><code>String url = a.attr("abs:href");</code></blockquote>
|
||||
*
|
||||
* @param attributeKey The attribute key.
|
||||
* @return The attribute, or empty string if not present (to avoid nulls).
|
||||
* @see #attributes()
|
||||
* @see #hasAttr(String)
|
||||
* @see #absUrl(String)
|
||||
*/
|
||||
open func attr(_ attributeKey: String)throws ->String {
|
||||
let val : String = try attributes!.getIgnoreCase(key: attributeKey);
|
||||
if (val.characters.count > 0){
|
||||
return val;
|
||||
}else if (attributeKey.lowercased().startsWith("abs:")){
|
||||
return try absUrl(attributeKey.substring("abs:".characters.count));
|
||||
}else {return "";}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all of the element's attributes.
|
||||
* @return attributes (which implements iterable, in same order as presented in original HTML).
|
||||
*/
|
||||
open func getAttributes()->Attributes? {
|
||||
return attributes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set an attribute (key=value). If the attribute already exists, it is replaced.
|
||||
* @param attributeKey The attribute key.
|
||||
* @param attributeValue The attribute value.
|
||||
* @return this (for chaining)
|
||||
*/
|
||||
@discardableResult
|
||||
open func attr(_ attributeKey: String, _ attributeValue: String)throws->Node {
|
||||
try attributes?.put(attributeKey , attributeValue)
|
||||
return self;
|
||||
}
|
||||
|
||||
/**
|
||||
* Test if this element has an attribute. <b>Case insensitive</b>
|
||||
* @param attributeKey The attribute key to check.
|
||||
* @return true if the attribute exists, false if not.
|
||||
*/
|
||||
open func hasAttr(_ attributeKey: String)->Bool {
|
||||
guard let attributes = attributes else {
|
||||
return false
|
||||
}
|
||||
if (attributeKey.startsWith("abs:")) {
|
||||
let key : String = attributeKey.substring("abs:".characters.count);
|
||||
do{
|
||||
let abs = try absUrl(key)
|
||||
if (attributes.hasKeyIgnoreCase(key: key) && !"".equals(abs)){
|
||||
return true;
|
||||
}
|
||||
}catch{
|
||||
return false
|
||||
}
|
||||
|
||||
}
|
||||
return attributes.hasKeyIgnoreCase(key: attributeKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove an attribute from this element.
|
||||
* @param attributeKey The attribute to remove.
|
||||
* @return this (for chaining)
|
||||
*/
|
||||
@discardableResult
|
||||
open func removeAttr(_ attributeKey: String)throws->Node {
|
||||
try attributes?.removeIgnoreCase(key: attributeKey);
|
||||
return self;
|
||||
}
|
||||
|
||||
/**
|
||||
Get the base URI of this node.
|
||||
@return base URI
|
||||
*/
|
||||
open func getBaseUri()->String {
|
||||
return baseUri!;
|
||||
}
|
||||
|
||||
/**
|
||||
Update the base URI of this node and all of its descendants.
|
||||
@param baseUri base URI to set
|
||||
*/
|
||||
open func setBaseUri(_ baseUri: String)throws
|
||||
{
|
||||
class nodeVisitor : NodeVisitor
|
||||
{
|
||||
private let baseUri : String
|
||||
init(_ baseUri: String)
|
||||
{
|
||||
self.baseUri = baseUri
|
||||
}
|
||||
|
||||
func head(_ node: Node, _ depth: Int)throws {
|
||||
node.baseUri = baseUri;
|
||||
}
|
||||
|
||||
func tail(_ node: Node, _ depth: Int)throws {
|
||||
}
|
||||
}
|
||||
try traverse(nodeVisitor(baseUri));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an absolute URL from a URL attribute that may be relative (i.e. an <code><a href></code> or
|
||||
* <code><img src></code>).
|
||||
* <p>
|
||||
* E.g.: <code>String absUrl = linkEl.absUrl("href");</code>
|
||||
* </p>
|
||||
* <p>
|
||||
* If the attribute value is already absolute (i.e. it starts with a protocol, like
|
||||
* <code>http://</code> or <code>https://</code> etc), and it successfully parses as a URL, the attribute is
|
||||
* returned directly. Otherwise, it is treated as a URL relative to the element's {@link #baseUri}, and made
|
||||
* absolute using that.
|
||||
* </p>
|
||||
* <p>
|
||||
* As an alternate, you can use the {@link #attr} method with the <code>abs:</code> prefix, e.g.:
|
||||
* <code>String absUrl = linkEl.attr("abs:href");</code>
|
||||
* </p>
|
||||
*
|
||||
* @param attributeKey The attribute key
|
||||
* @return An absolute URL if one could be made, or an empty string (not null) if the attribute was missing or
|
||||
* could not be made successfully into a URL.
|
||||
* @see #attr
|
||||
* @see java.net.URL#URL(java.net.URL, String)
|
||||
*/
|
||||
open func absUrl(_ attributeKey: String)throws->String {
|
||||
try Validate.notEmpty(string: attributeKey);
|
||||
|
||||
if (!hasAttr(attributeKey)) {
|
||||
return ""; // nothing to make absolute with
|
||||
} else {
|
||||
return StringUtil.resolve(baseUri!, relUrl: try attr(attributeKey));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Get a child node by its 0-based index.
|
||||
@param index index of child node
|
||||
@return the child node at this index. Throws a {@code IndexOutOfBoundsException} if the index is out of bounds.
|
||||
*/
|
||||
open func childNode(_ index: Int)->Node {
|
||||
return childNodes[index]
|
||||
}
|
||||
|
||||
/**
|
||||
Get this node's children. Presented as an unmodifiable list: new children can not be added, but the child nodes
|
||||
themselves can be manipulated.
|
||||
@return list of children. If no children, returns an empty list.
|
||||
*/
|
||||
open func getChildNodes()->Array<Node> {
|
||||
return childNodes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a deep copy of this node's children. Changes made to these nodes will not be reflected in the original
|
||||
* nodes
|
||||
* @return a deep copy of this node's children
|
||||
*/
|
||||
open func childNodesCopy()->Array<Node>{
|
||||
var children: Array<Node> = Array<Node>();
|
||||
for node: Node in childNodes
|
||||
{
|
||||
children.append(node.copy() as! Node);
|
||||
}
|
||||
return children;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the number of child nodes that this node holds.
|
||||
* @return the number of child nodes that this node holds.
|
||||
*/
|
||||
public func childNodeSize()->Int {
|
||||
return childNodes.count;
|
||||
}
|
||||
|
||||
final func childNodesAsArray()->[Node] {
|
||||
return childNodes as Array;
|
||||
}
|
||||
|
||||
/**
|
||||
Gets this node's parent node.
|
||||
@return parent node; or null if no parent.
|
||||
*/
|
||||
open func parent() -> Node? {
|
||||
return parentNode;
|
||||
}
|
||||
|
||||
/**
|
||||
Gets this node's parent node. Node overridable by extending classes, so useful if you really just need the Node type.
|
||||
@return parent node; or null if no parent.
|
||||
*/
|
||||
final func getParentNode()->Node? {
|
||||
return parentNode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the Document associated with this Node.
|
||||
* @return the Document associated with this Node, or null if there is no such Document.
|
||||
*/
|
||||
open func ownerDocument()-> Document? {
|
||||
if let this = self as? Document{
|
||||
return this;
|
||||
}else if (parentNode == nil){
|
||||
return nil;
|
||||
}else{
|
||||
return parentNode!.ownerDocument();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove (delete) this node from the DOM tree. If this node has children, they are also removed.
|
||||
*/
|
||||
open func remove()throws {
|
||||
try parentNode?.removeChild(self);
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert the specified HTML into the DOM before this node (i.e. as a preceding sibling).
|
||||
* @param html HTML to add before this node
|
||||
* @return this node, for chaining
|
||||
* @see #after(String)
|
||||
*/
|
||||
@discardableResult
|
||||
open func before(_ html: String)throws->Node {
|
||||
try addSiblingHtml(siblingIndex, html);
|
||||
return self;
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert the specified node into the DOM before this node (i.e. as a preceding sibling).
|
||||
* @param node to add before this node
|
||||
* @return this node, for chaining
|
||||
* @see #after(Node)
|
||||
*/
|
||||
@discardableResult
|
||||
open func before(_ node: Node)throws ->Node {
|
||||
try Validate.notNull(obj: node)
|
||||
try Validate.notNull(obj: parentNode)
|
||||
|
||||
try parentNode?.addChildren(siblingIndex,node)
|
||||
return self;
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert the specified HTML into the DOM after this node (i.e. as a following sibling).
|
||||
* @param html HTML to add after this node
|
||||
* @return this node, for chaining
|
||||
* @see #before(String)
|
||||
*/
|
||||
@discardableResult
|
||||
open func after(_ html: String)throws ->Node {
|
||||
try addSiblingHtml(siblingIndex + 1, html);
|
||||
return self;
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert the specified node into the DOM after this node (i.e. as a following sibling).
|
||||
* @param node to add after this node
|
||||
* @return this node, for chaining
|
||||
* @see #before(Node)
|
||||
*/
|
||||
@discardableResult
|
||||
open func after(_ node: Node)throws->Node {
|
||||
try Validate.notNull(obj: node);
|
||||
try Validate.notNull(obj: parentNode);
|
||||
|
||||
try parentNode?.addChildren(siblingIndex+1,node)
|
||||
return self;
|
||||
}
|
||||
|
||||
private func addSiblingHtml(_ index: Int, _ html: String)throws {
|
||||
try Validate.notNull(obj: parentNode);
|
||||
|
||||
let context : Element? = parent() as? Element;
|
||||
|
||||
let nodes : Array<Node> = try Parser.parseFragment(html, context, getBaseUri());
|
||||
try parentNode?.addChildren(index,nodes)
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Insert the specified HTML into the DOM after this node (i.e. as a following sibling).
|
||||
* @param html HTML to add after this node
|
||||
* @return this node, for chaining
|
||||
* @see #before(String)
|
||||
*/
|
||||
@discardableResult
|
||||
open func after(html: String)throws->Node {
|
||||
try addSiblingHtml(siblingIndex + 1, html);
|
||||
return self;
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert the specified node into the DOM after this node (i.e. as a following sibling).
|
||||
* @param node to add after this node
|
||||
* @return this node, for chaining
|
||||
* @see #before(Node)
|
||||
*/
|
||||
@discardableResult
|
||||
open func after(node: Node)throws->Node {
|
||||
try Validate.notNull(obj: node);
|
||||
try Validate.notNull(obj: parentNode);
|
||||
|
||||
try parentNode?.addChildren(siblingIndex + 1, node);
|
||||
return self;
|
||||
}
|
||||
|
||||
open func addSiblingHtml(index: Int, _ html: String)throws {
|
||||
try Validate.notNull(obj: html);
|
||||
try Validate.notNull(obj: parentNode);
|
||||
|
||||
let context : Element? = parent() as? Element;
|
||||
let nodes : Array<Node> = try Parser.parseFragment(html, context, getBaseUri());
|
||||
try parentNode?.addChildren(index, nodes);
|
||||
}
|
||||
|
||||
/**
|
||||
Wrap the supplied HTML around this node.
|
||||
@param html HTML to wrap around this element, e.g. {@code <div class="head"></div>}. Can be arbitrarily deep.
|
||||
@return this node, for chaining.
|
||||
*/
|
||||
@discardableResult
|
||||
open func wrap(_ html: String)throws->Node? {
|
||||
try Validate.notEmpty(string: html);
|
||||
|
||||
let context : Element? = parent() as? Element;
|
||||
var wrapChildren : Array<Node> = try Parser.parseFragment(html, context, getBaseUri());
|
||||
let wrapNode : Node? = wrapChildren.count > 0 ? wrapChildren[0] : nil;
|
||||
if (wrapNode == nil || !(((wrapNode as? Element) != nil))){ // nothing to wrap with; noop
|
||||
return nil;
|
||||
}
|
||||
|
||||
let wrap : Element = wrapNode as! Element;
|
||||
let deepest : Element = getDeepChild(el: wrap);
|
||||
try parentNode?.replaceChild(self, wrap);
|
||||
wrapChildren = wrapChildren.filter { $0 != wrap}
|
||||
try deepest.addChildren(self);
|
||||
|
||||
// remainder (unbalanced wrap, like <div></div><p></p> -- The <p> is remainder
|
||||
if (wrapChildren.count > 0)
|
||||
{
|
||||
for i in 0..<wrapChildren.count
|
||||
{
|
||||
let remainder : Node = wrapChildren[i]
|
||||
try remainder.parentNode?.removeChild(remainder);
|
||||
try wrap.appendChild(remainder);
|
||||
}
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes this node from the DOM, and moves its children up into the node's parent. This has the effect of dropping
|
||||
* the node but keeping its children.
|
||||
* <p>
|
||||
* For example, with the input html:
|
||||
* </p>
|
||||
* <p>{@code <div>One <span>Two <b>Three</b></span></div>}</p>
|
||||
* Calling {@code element.unwrap()} on the {@code span} element will result in the html:
|
||||
* <p>{@code <div>One Two <b>Three</b></div>}</p>
|
||||
* and the {@code "Two "} {@link TextNode} being returned.
|
||||
*
|
||||
* @return the first child of this node, after the node has been unwrapped. Null if the node had no children.
|
||||
* @see #remove()
|
||||
* @see #wrap(String)
|
||||
*/
|
||||
@discardableResult
|
||||
open func unwrap()throws ->Node? {
|
||||
try Validate.notNull(obj: parentNode);
|
||||
|
||||
let firstChild : Node? = childNodes.count > 0 ? childNodes[0] : nil;
|
||||
try parentNode?.addChildren(siblingIndex, self.childNodesAsArray());
|
||||
try self.remove();
|
||||
|
||||
return firstChild;
|
||||
}
|
||||
|
||||
private func getDeepChild(el: Element)->Element {
|
||||
let children = el.children();
|
||||
if (children.size() > 0){
|
||||
return getDeepChild(el: children.get(0));
|
||||
}else{
|
||||
return el;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace this node in the DOM with the supplied node.
|
||||
* @param in the node that will will replace the existing node.
|
||||
*/
|
||||
public func replaceWith(_ input: Node)throws {
|
||||
try Validate.notNull(obj: input);
|
||||
try Validate.notNull(obj: parentNode);
|
||||
try parentNode?.replaceChild(self, input);
|
||||
}
|
||||
|
||||
public func setParentNode(_ parentNode: Node)throws {
|
||||
if (self.parentNode != nil){
|
||||
try self.parentNode?.removeChild(self);
|
||||
}
|
||||
self.parentNode = parentNode;
|
||||
}
|
||||
|
||||
public func replaceChild(_ out: Node, _ input: Node)throws {
|
||||
try Validate.isTrue(val: out.parentNode === self);
|
||||
try Validate.notNull(obj: input);
|
||||
if (input.parentNode != nil){
|
||||
try input.parentNode?.removeChild(input);
|
||||
}
|
||||
|
||||
let index : Int = out.siblingIndex;
|
||||
childNodes[index] = input
|
||||
input.parentNode = self;
|
||||
input.setSiblingIndex(index);
|
||||
out.parentNode = nil;
|
||||
}
|
||||
|
||||
public func removeChild(_ out: Node)throws {
|
||||
try Validate.isTrue(val: out.parentNode === self)
|
||||
let index : Int = out.siblingIndex
|
||||
childNodes.remove(at: index)
|
||||
reindexChildren(index)
|
||||
out.parentNode = nil
|
||||
}
|
||||
|
||||
public func addChildren(_ children: Node...)throws {
|
||||
//most used. short circuit addChildren(int), which hits reindex children and array copy
|
||||
try addChildren(children)
|
||||
}
|
||||
|
||||
public func addChildren(_ children: [Node])throws {
|
||||
//most used. short circuit addChildren(int), which hits reindex children and array copy
|
||||
for child in children
|
||||
{
|
||||
try reparentChild(child)
|
||||
ensureChildNodes()
|
||||
childNodes.append(child)
|
||||
child.setSiblingIndex(childNodes.count-1)
|
||||
}
|
||||
}
|
||||
|
||||
public func addChildren(_ index: Int,_ children: Node...)throws {
|
||||
try addChildren(index,children)
|
||||
}
|
||||
|
||||
public func addChildren(_ index: Int,_ children: [Node])throws {
|
||||
ensureChildNodes();
|
||||
for i in (0..<children.count).reversed()
|
||||
{
|
||||
let input : Node = children[i]
|
||||
try reparentChild(input)
|
||||
childNodes.insert(input, at: index)
|
||||
reindexChildren(index)
|
||||
}
|
||||
}
|
||||
|
||||
public func ensureChildNodes()
|
||||
{
|
||||
// if (childNodes === Node.EMPTY_NODES) {
|
||||
// childNodes = Array<Node>();
|
||||
// }
|
||||
}
|
||||
|
||||
public func reparentChild(_ child: Node)throws {
|
||||
if (child.parentNode != nil){
|
||||
try child.parentNode?.removeChild(child)
|
||||
}
|
||||
try child.setParentNode(self)
|
||||
}
|
||||
|
||||
private func reindexChildren(_ start: Int) {
|
||||
for i in start..<childNodes.count
|
||||
{
|
||||
childNodes[i].setSiblingIndex(i)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Retrieves this node's sibling nodes. Similar to {@link #childNodes() node.parent.childNodes()}, but does not
|
||||
include this node (a node is not a sibling of itself).
|
||||
@return node siblings. If the node has no parent, returns an empty list.
|
||||
*/
|
||||
open func siblingNodes()->Array<Node> {
|
||||
if (parentNode == nil){
|
||||
return Array<Node>()
|
||||
}
|
||||
|
||||
let nodes : Array<Node> = parentNode!.childNodes;
|
||||
var siblings : Array<Node> = Array<Node>();
|
||||
for node in nodes
|
||||
{
|
||||
if (node !== self){
|
||||
siblings.append(node)
|
||||
}
|
||||
}
|
||||
|
||||
return siblings
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
Get this node's next sibling.
|
||||
@return next sibling, or null if this is the last sibling
|
||||
*/
|
||||
open func nextSibling()->Node? {
|
||||
if (parentNode == nil){
|
||||
return nil // root
|
||||
}
|
||||
|
||||
let siblings : Array<Node> = parentNode!.childNodes
|
||||
let index : Int = siblingIndex+1
|
||||
if (siblings.count > index){
|
||||
return siblings[index]
|
||||
}else{
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Get this node's previous sibling.
|
||||
@return the previous sibling, or null if this is the first sibling
|
||||
*/
|
||||
open func previousSibling()->Node? {
|
||||
if (parentNode == nil){
|
||||
return nil // root
|
||||
}
|
||||
|
||||
if (siblingIndex > 0){
|
||||
return parentNode?.childNodes[siblingIndex-1];
|
||||
}else{
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
public func setSiblingIndex(_ siblingIndex: Int) {
|
||||
self.siblingIndex = siblingIndex
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform a depth-first traversal through this node and its descendants.
|
||||
* @param nodeVisitor the visitor callbacks to perform on each node
|
||||
* @return this node, for chaining
|
||||
*/
|
||||
@discardableResult
|
||||
open func traverse(_ nodeVisitor: NodeVisitor)throws->Node {
|
||||
let traversor : NodeTraversor = NodeTraversor(nodeVisitor)
|
||||
try traversor.traverse(self)
|
||||
return self
|
||||
}
|
||||
|
||||
/**
|
||||
Get the outer HTML of this node.
|
||||
@return HTML
|
||||
*/
|
||||
open func outerHtml()throws->String {
|
||||
let accum : StringBuilder = StringBuilder(128)
|
||||
try outerHtml(accum)
|
||||
return accum.toString()
|
||||
}
|
||||
|
||||
public func outerHtml(_ accum: StringBuilder)throws {
|
||||
try NodeTraversor(OuterHtmlVisitor(accum, getOutputSettings())).traverse(self);
|
||||
}
|
||||
|
||||
// if this node has no document (or parent), retrieve the default output settings
|
||||
func getOutputSettings()-> OutputSettings {
|
||||
return ownerDocument() != nil ? ownerDocument()!.outputSettings() : (Document("")).outputSettings();
|
||||
}
|
||||
|
||||
/**
|
||||
Get the outer HTML of this node.
|
||||
@param accum accumulator to place HTML into
|
||||
@throws IOException if appending to the given accumulator fails.
|
||||
*/
|
||||
func outerHtmlHead(_ accum: StringBuilder, _ depth: Int, _ out: OutputSettings) throws
|
||||
{
|
||||
preconditionFailure("This method must be overridden")
|
||||
};
|
||||
|
||||
func outerHtmlTail(_ accum: StringBuilder, _ depth: Int, _ out: OutputSettings) throws
|
||||
{
|
||||
preconditionFailure("This method must be overridden")
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Write this node and its children to the given {@link Appendable}.
|
||||
*
|
||||
* @param appendable the {@link Appendable} to write to.
|
||||
* @return the supplied {@link Appendable}, for chaining.
|
||||
*/
|
||||
open func html(_ appendable: StringBuilder)throws -> StringBuilder {
|
||||
try outerHtml(appendable);
|
||||
return appendable;
|
||||
}
|
||||
|
||||
open func toString()throws->String {
|
||||
return try outerHtml();
|
||||
}
|
||||
|
||||
public func indent(_ accum: StringBuilder, _ depth: Int, _ out: OutputSettings) {
|
||||
accum.append("\n").append(StringUtil.padding(depth * Int(out.indentAmount())));
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this node is the same instance of another (object identity test).
|
||||
* @param o other object to compare to
|
||||
* @return true if the content of this node is the same as the other
|
||||
* @see Node#hasSameValue(Object) to compare nodes by their value
|
||||
*/
|
||||
|
||||
open func equals(_ o: Node)->Bool {
|
||||
// implemented just so that javadoc is clear this is an identity test
|
||||
return self === o;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this node is has the same content as another node. A node is considered the same if its name, attributes and content match the
|
||||
* other node; particularly its position in the tree does not influence its similarity.
|
||||
* @param o other object to compare to
|
||||
* @return true if the content of this node is the same as the other
|
||||
*/
|
||||
|
||||
open func hasSameValue(_ o : Node)throws->Bool {
|
||||
if (self === o){return true;}
|
||||
// if (type(of:self) != type(of: o))
|
||||
// {
|
||||
// return false
|
||||
// }
|
||||
|
||||
return try self.outerHtml() == o.outerHtml();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a stand-alone, deep copy of this node, and all of its children. The cloned node will have no siblings or
|
||||
* parent node. As a stand-alone object, any changes made to the clone or any of its children will not impact the
|
||||
* original node.
|
||||
* <p>
|
||||
* The cloned node may be adopted into another Document or node structure using {@link Element#appendChild(Node)}.
|
||||
* @return stand-alone cloned node
|
||||
*/
|
||||
public func copy(with zone: NSZone? = nil) -> Any
|
||||
{
|
||||
return copy(clone: Node())
|
||||
}
|
||||
|
||||
public func copy(parent: Node?)->Node
|
||||
{
|
||||
let clone = Node()
|
||||
return copy(clone: clone,parent: parent)
|
||||
}
|
||||
|
||||
public func copy(clone: Node)->Node
|
||||
{
|
||||
let thisClone : Node = copy(clone: clone, parent: nil) // splits for orphan
|
||||
|
||||
// Queue up nodes that need their children cloned (BFS).
|
||||
var nodesToProcess : Array<Node> = Array<Node>();
|
||||
nodesToProcess.append(thisClone);
|
||||
|
||||
while (!nodesToProcess.isEmpty) {
|
||||
let currParent : Node = nodesToProcess.removeFirst()
|
||||
|
||||
for i in 0..<currParent.childNodes.count
|
||||
{
|
||||
let childClone : Node = currParent.childNodes[i].copy(parent:currParent);
|
||||
currParent.childNodes[i] = childClone
|
||||
nodesToProcess.append(childClone);
|
||||
}
|
||||
}
|
||||
return thisClone
|
||||
}
|
||||
|
||||
/*
|
||||
* Return a clone of the node using the given parent (which can be null).
|
||||
* Not a deep copy of children.
|
||||
*/
|
||||
public func copy(clone: Node, parent: Node?)->Node
|
||||
{
|
||||
clone.parentNode = parent; // can be null, to create an orphan split
|
||||
clone.siblingIndex = parent == nil ? 0 : siblingIndex;
|
||||
clone.attributes = attributes != nil ? attributes?.clone() : nil;
|
||||
clone.baseUri = baseUri;
|
||||
clone.childNodes = Array<Node>();
|
||||
|
||||
for child in childNodes{
|
||||
clone.childNodes.append(child);
|
||||
}
|
||||
|
||||
return clone;
|
||||
}
|
||||
|
||||
private class OuterHtmlVisitor : NodeVisitor {
|
||||
private var accum : StringBuilder ;
|
||||
private var out : OutputSettings;
|
||||
|
||||
init(_ accum: StringBuilder, _ out: OutputSettings) {
|
||||
self.accum = accum;
|
||||
self.out = out;
|
||||
}
|
||||
|
||||
open func head(_ node: Node, _ depth: Int)throws{
|
||||
|
||||
try node.outerHtmlHead(accum, depth, out);
|
||||
}
|
||||
|
||||
open func tail(_ node: Node, _ depth: Int)throws {
|
||||
if (!(node.nodeName() == "#text"))
|
||||
{ // saves a void hit.
|
||||
try node.outerHtmlTail(accum, depth, out);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/// Returns a Boolean value indicating whether two values are equal.
|
||||
///
|
||||
/// Equality is the inverse of inequality. For any values `a` and `b`,
|
||||
/// `a == b` implies that `a != b` is `false`.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - lhs: A value to compare.
|
||||
/// - rhs: Another value to compare.
|
||||
public static func ==(lhs: Node, rhs: Node) -> Bool{
|
||||
return lhs === rhs
|
||||
}
|
||||
|
||||
|
||||
/// The hash value.
|
||||
///
|
||||
/// Hash values are not guaranteed to be equal across different executions of
|
||||
/// your program. Do not save hash values to use during a future execution.
|
||||
public var hashValue: Int {
|
||||
var result : Int = description.hashValue
|
||||
result = Int.addWithOverflow(Int.multiplyWithOverflow(31, result).0, baseUri != nil ? baseUri!.hashValue : 31).0
|
||||
return result
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
extension Node : CustomStringConvertible
|
||||
{
|
||||
public var description: String {
|
||||
do{
|
||||
return try toString()
|
||||
}catch{
|
||||
|
||||
}
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
extension Node : CustomDebugStringConvertible
|
||||
{
|
||||
public var debugDescription: String {
|
||||
do{
|
||||
return try String(describing: type(of: self)) + " " + toString()
|
||||
}catch{
|
||||
|
||||
}
|
||||
return String(describing: type(of: self))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,212 @@
|
|||
//
|
||||
// TextNode.swift
|
||||
// SwifSoup
|
||||
//
|
||||
// Created by Nabil Chatbi on 29/09/16.
|
||||
// Copyright © 2016 Nabil Chatbi.. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
/**
|
||||
A text node.
|
||||
*/
|
||||
open class TextNode : Node {
|
||||
/*
|
||||
TextNode is a node, and so by default comes with attributes and children. The attributes are seldom used, but use
|
||||
memory, and the child nodes are never used. So we don't have them, and override accessors to attributes to create
|
||||
them as needed on the fly.
|
||||
*/
|
||||
private static let TEXT_KEY : String = "text"
|
||||
var _text : String
|
||||
|
||||
/**
|
||||
Create a new TextNode representing the supplied (unencoded) text).
|
||||
|
||||
@param text raw text
|
||||
@param baseUri base uri
|
||||
@see #createFromEncoded(String, String)
|
||||
*/
|
||||
public init(_ text: String, _ baseUri: String?) {
|
||||
self._text = text
|
||||
super.init()
|
||||
self.baseUri = baseUri;
|
||||
|
||||
}
|
||||
|
||||
open override func nodeName()->String {
|
||||
return "#text"
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the text content of this text node.
|
||||
* @return Unencoded, normalised text.
|
||||
* @see TextNode#getWholeText()
|
||||
*/
|
||||
open func text()->String {
|
||||
return TextNode.normaliseWhitespace(getWholeText())
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the text content of this text node.
|
||||
* @param text unencoded text
|
||||
* @return this, for chaining
|
||||
*/
|
||||
@discardableResult
|
||||
public func text(_ text: String)->TextNode {
|
||||
self._text = text;
|
||||
guard let attributes = attributes else {
|
||||
return self
|
||||
}
|
||||
do{
|
||||
try attributes.put(TextNode.TEXT_KEY, text)
|
||||
}catch{
|
||||
|
||||
}
|
||||
return self
|
||||
}
|
||||
|
||||
/**
|
||||
Get the (unencoded) text of this text node, including any newlines and spaces present in the original.
|
||||
@return text
|
||||
*/
|
||||
open func getWholeText()->String {
|
||||
return attributes == nil ? _text : attributes!.get(key: TextNode.TEXT_KEY);
|
||||
}
|
||||
|
||||
/**
|
||||
Test if this text node is blank -- that is, empty or only whitespace (including newlines).
|
||||
@return true if this document is empty or only whitespace, false if it contains any text content.
|
||||
*/
|
||||
open func isBlank()->Bool {
|
||||
return StringUtil.isBlank(getWholeText());
|
||||
}
|
||||
|
||||
/**
|
||||
* Split this text node into two nodes at the specified string offset. After splitting, this node will contain the
|
||||
* original text up to the offset, and will have a new text node sibling containing the text after the offset.
|
||||
* @param offset string offset point to split node at.
|
||||
* @return the newly created text node containing the text after the offset.
|
||||
*/
|
||||
open func splitText(_ offset: Int)throws->TextNode {
|
||||
try Validate.isTrue(val: offset >= 0, msg: "Split offset must be not be negative");
|
||||
try Validate.isTrue(val: offset < _text.characters.count, msg: "Split offset must not be greater than current text length");
|
||||
|
||||
let head : String = getWholeText().substring(0, offset);
|
||||
let tail : String = getWholeText().substring(offset);
|
||||
text(head);
|
||||
let tailNode : TextNode = TextNode(tail, self.getBaseUri())
|
||||
if (parent() != nil){
|
||||
try parent()?.addChildren(siblingIndex+1, tailNode)
|
||||
}
|
||||
return tailNode;
|
||||
}
|
||||
|
||||
override func outerHtmlHead(_ accum: StringBuilder, _ depth: Int, _ out: OutputSettings)throws
|
||||
{
|
||||
if (out.prettyPrint() &&
|
||||
((siblingIndex == 0 && (parentNode as? Element) != nil && (parentNode as! Element).tag().formatAsBlock() && !isBlank()) ||
|
||||
(out.outline() && siblingNodes().count > 0 && !isBlank()) )){
|
||||
indent(accum, depth, out);
|
||||
}
|
||||
|
||||
let par : Element? = parent() as? Element
|
||||
let normaliseWhite = out.prettyPrint() && par != nil && !Element.preserveWhitespace(par!);
|
||||
|
||||
Entities.escape(accum, getWholeText(), out, false, normaliseWhite, false);
|
||||
}
|
||||
|
||||
override func outerHtmlTail(_ accum: StringBuilder, _ depth: Int, _ out: OutputSettings) {
|
||||
}
|
||||
|
||||
|
||||
open override func toString()throws->String {
|
||||
return try outerHtml();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new TextNode from HTML encoded (aka escaped) data.
|
||||
* @param encodedText Text containing encoded HTML (e.g. &lt;)
|
||||
* @param baseUri Base uri
|
||||
* @return TextNode containing unencoded data (e.g. <)
|
||||
*/
|
||||
open static func createFromEncoded(_ encodedText: String, _ baseUri: String)throws->TextNode {
|
||||
let text : String = try Entities.unescape(encodedText);
|
||||
return TextNode(text, baseUri);
|
||||
}
|
||||
|
||||
static open func normaliseWhitespace(_ text: String)->String {
|
||||
let _text = StringUtil.normaliseWhitespace(text);
|
||||
return _text;
|
||||
}
|
||||
|
||||
static open func stripLeadingWhitespace(_ text: String)->String {
|
||||
return text.replaceFirst(of: "^\\s+", with: "")
|
||||
//return text.replaceFirst("^\\s+", "");
|
||||
}
|
||||
|
||||
static open func lastCharIsWhitespace(_ sb: StringBuilder)->Bool {
|
||||
return sb.length != 0 && sb.toString().charAt(sb.length - 1) == " ";
|
||||
}
|
||||
|
||||
// attribute fiddling. create on first access.
|
||||
private func ensureAttributes() {
|
||||
if (attributes == nil) {
|
||||
attributes = Attributes();
|
||||
do{
|
||||
try attributes?.put(TextNode.TEXT_KEY, _text);
|
||||
}catch{}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
open override func attr(_ attributeKey: String)throws->String {
|
||||
ensureAttributes();
|
||||
return try super.attr(attributeKey);
|
||||
}
|
||||
|
||||
open override func getAttributes()->Attributes {
|
||||
ensureAttributes();
|
||||
return super.getAttributes()!;
|
||||
}
|
||||
|
||||
open override func attr(_ attributeKey: String, _ attributeValue: String)throws->Node {
|
||||
ensureAttributes();
|
||||
return try super.attr(attributeKey, attributeValue);
|
||||
}
|
||||
|
||||
|
||||
open override func hasAttr(_ attributeKey: String)->Bool {
|
||||
ensureAttributes();
|
||||
return super.hasAttr(attributeKey);
|
||||
}
|
||||
|
||||
|
||||
open override func removeAttr(_ attributeKey: String)throws->Node {
|
||||
ensureAttributes();
|
||||
return try super.removeAttr(attributeKey);
|
||||
}
|
||||
|
||||
|
||||
open override func absUrl(_ attributeKey: String)throws->String {
|
||||
ensureAttributes();
|
||||
return try super.absUrl(attributeKey);
|
||||
}
|
||||
|
||||
public override func copy(with zone: NSZone? = nil) -> Any
|
||||
{
|
||||
let clone = TextNode(_text,baseUri)
|
||||
return super.copy(clone: clone)
|
||||
}
|
||||
|
||||
public override func copy(parent: Node?)->Node
|
||||
{
|
||||
let clone = TextNode(_text,baseUri)
|
||||
return super.copy(clone: clone,parent: parent)
|
||||
}
|
||||
|
||||
public override func copy(clone: Node, parent: Node?)->Node
|
||||
{
|
||||
return super.copy(clone: clone,parent: parent)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,89 @@
|
|||
//
|
||||
// XmlDeclaration.swift
|
||||
// SwifSoup
|
||||
//
|
||||
// Created by Nabil Chatbi on 29/09/16.
|
||||
// Copyright © 2016 Nabil Chatbi.. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
/**
|
||||
An XML Declaration.
|
||||
|
||||
@author Jonathan Hedley, jonathan@hedley.net */
|
||||
public class XmlDeclaration : Node {
|
||||
private let _name: String
|
||||
private let isProcessingInstruction: Bool // <! if true, <? if false, declaration (and last data char should be ?)
|
||||
|
||||
/**
|
||||
Create a new XML declaration
|
||||
@param name of declaration
|
||||
@param baseUri base uri
|
||||
@param isProcessingInstruction is processing instruction
|
||||
*/
|
||||
public init(_ name: String, _ baseUri: String, _ isProcessingInstruction: Bool) {
|
||||
self._name = name;
|
||||
self.isProcessingInstruction = isProcessingInstruction;
|
||||
super.init(baseUri);
|
||||
}
|
||||
|
||||
public override func nodeName()->String {
|
||||
return "#declaration";
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the name of this declaration.
|
||||
* @return name of this declaration.
|
||||
*/
|
||||
public func name()->String {
|
||||
return _name;
|
||||
}
|
||||
|
||||
/**
|
||||
Get the unencoded XML declaration.
|
||||
@return XML declaration
|
||||
*/
|
||||
public func getWholeDeclaration()throws->String {
|
||||
return try attributes!.html().trim(); // attr html starts with a " "
|
||||
}
|
||||
|
||||
override func outerHtmlHead(_ accum: StringBuilder, _ depth: Int, _ out: OutputSettings) {
|
||||
accum
|
||||
.append("<")
|
||||
.append(isProcessingInstruction ? "!" : "?")
|
||||
.append(_name);
|
||||
do{
|
||||
try attributes?.html(accum: accum, out: out);
|
||||
}catch{}
|
||||
accum
|
||||
.append(isProcessingInstruction ? "!" : "?")
|
||||
.append(">");
|
||||
}
|
||||
|
||||
override func outerHtmlTail(_ accum: StringBuilder, _ depth: Int, _ out: OutputSettings) {}
|
||||
|
||||
public override func toString()->String {
|
||||
do{
|
||||
return try outerHtml()
|
||||
}catch{}
|
||||
return ""
|
||||
}
|
||||
|
||||
public override func copy(with zone: NSZone? = nil) -> Any
|
||||
{
|
||||
let clone = XmlDeclaration(_name,baseUri!,isProcessingInstruction)
|
||||
return copy(clone: clone)
|
||||
}
|
||||
|
||||
public override func copy(parent: Node?)->Node
|
||||
{
|
||||
let clone = XmlDeclaration(_name,baseUri!,isProcessingInstruction)
|
||||
return copy(clone: clone,parent: parent)
|
||||
}
|
||||
public override func copy(clone: Node, parent: Node?)->Node
|
||||
{
|
||||
return super.copy(clone: clone,parent: parent)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,106 @@
|
|||
AElig=5i;1c
|
||||
AMP=12;2
|
||||
Aacute=5d;17
|
||||
Acirc=5e;18
|
||||
Agrave=5c;16
|
||||
Aring=5h;1b
|
||||
Atilde=5f;19
|
||||
Auml=5g;1a
|
||||
COPY=4p;h
|
||||
Ccedil=5j;1d
|
||||
ETH=5s;1m
|
||||
Eacute=5l;1f
|
||||
Ecirc=5m;1g
|
||||
Egrave=5k;1e
|
||||
Euml=5n;1h
|
||||
GT=1q;6
|
||||
Iacute=5p;1j
|
||||
Icirc=5q;1k
|
||||
Igrave=5o;1i
|
||||
Iuml=5r;1l
|
||||
LT=1o;4
|
||||
Ntilde=5t;1n
|
||||
Oacute=5v;1p
|
||||
Ocirc=5w;1q
|
||||
Ograve=5u;1o
|
||||
Oslash=60;1u
|
||||
Otilde=5x;1r
|
||||
Ouml=5y;1s
|
||||
QUOT=y;0
|
||||
REG=4u;n
|
||||
THORN=66;20
|
||||
Uacute=62;1w
|
||||
Ucirc=63;1x
|
||||
Ugrave=61;1v
|
||||
Uuml=64;1y
|
||||
Yacute=65;1z
|
||||
aacute=69;23
|
||||
acirc=6a;24
|
||||
acute=50;u
|
||||
aelig=6e;28
|
||||
agrave=68;22
|
||||
amp=12;3
|
||||
aring=6d;27
|
||||
atilde=6b;25
|
||||
auml=6c;26
|
||||
brvbar=4m;e
|
||||
ccedil=6f;29
|
||||
cedil=54;y
|
||||
cent=4i;a
|
||||
copy=4p;i
|
||||
curren=4k;c
|
||||
deg=4w;q
|
||||
divide=6v;2p
|
||||
eacute=6h;2b
|
||||
ecirc=6i;2c
|
||||
egrave=6g;2a
|
||||
eth=6o;2i
|
||||
euml=6j;2d
|
||||
frac12=59;13
|
||||
frac14=58;12
|
||||
frac34=5a;14
|
||||
gt=1q;7
|
||||
iacute=6l;2f
|
||||
icirc=6m;2g
|
||||
iexcl=4h;9
|
||||
igrave=6k;2e
|
||||
iquest=5b;15
|
||||
iuml=6n;2h
|
||||
laquo=4r;k
|
||||
lt=1o;5
|
||||
macr=4v;p
|
||||
micro=51;v
|
||||
middot=53;x
|
||||
nbsp=4g;8
|
||||
not=4s;l
|
||||
ntilde=6p;2j
|
||||
oacute=6r;2l
|
||||
ocirc=6s;2m
|
||||
ograve=6q;2k
|
||||
ordf=4q;j
|
||||
ordm=56;10
|
||||
oslash=6w;2q
|
||||
otilde=6t;2n
|
||||
ouml=6u;2o
|
||||
para=52;w
|
||||
plusmn=4x;r
|
||||
pound=4j;b
|
||||
quot=y;1
|
||||
raquo=57;11
|
||||
reg=4u;o
|
||||
sect=4n;f
|
||||
shy=4t;m
|
||||
sup1=55;z
|
||||
sup2=4y;s
|
||||
sup3=4z;t
|
||||
szlig=67;21
|
||||
thorn=72;2w
|
||||
times=5z;1t
|
||||
uacute=6y;2s
|
||||
ucirc=6z;2t
|
||||
ugrave=6x;2r
|
||||
uml=4o;g
|
||||
uuml=70;2u
|
||||
yacute=71;2v
|
||||
yen=4l;d
|
||||
yuml=73;2x
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,4 @@
|
|||
amp=12;1
|
||||
gt=1q;3
|
||||
lt=1o;2
|
||||
quot=y;0
|
|
@ -0,0 +1,461 @@
|
|||
//
|
||||
// CharacterReader.swift
|
||||
// SwiftSoup
|
||||
//
|
||||
// Created by Nabil Chatbi on 10/10/16.
|
||||
// Copyright © 2016 Nabil Chatbi.. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
/**
|
||||
CharacterReader consumes tokens off a string. To replace the old TokenQueue.
|
||||
*/
|
||||
public final class CharacterReader
|
||||
{
|
||||
public static let EOF : UnicodeScalar = "\u{FFFF}"//65535
|
||||
private static let maxCacheLen : Int = 12
|
||||
private let input : String
|
||||
private let length : Int
|
||||
private var pos : Int = 0
|
||||
private var mark : Int = 0
|
||||
private let stringCache : Array<String?> // holds reused strings in this doc, to lessen garbage
|
||||
|
||||
public init(_ input: String)
|
||||
{
|
||||
self.input = input
|
||||
self.length = self.input.unicodeScalars.count
|
||||
stringCache = Array(repeating:nil, count:512)
|
||||
}
|
||||
|
||||
public func getPos() -> Int {
|
||||
return self.pos
|
||||
}
|
||||
|
||||
public func isEmpty() -> Bool {
|
||||
return pos >= length;
|
||||
}
|
||||
|
||||
public func current() -> UnicodeScalar {
|
||||
return (pos >= length) ? CharacterReader.EOF : input.unicodeScalar(pos)
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
public func consume() -> UnicodeScalar {
|
||||
let val = (pos >= length) ? CharacterReader.EOF : input.unicodeScalar(pos)
|
||||
pos += 1;
|
||||
return val;
|
||||
}
|
||||
|
||||
public func unconsume() {
|
||||
pos -= 1;
|
||||
}
|
||||
|
||||
public func advance() {
|
||||
pos += 1;
|
||||
}
|
||||
|
||||
public func markPos() {
|
||||
mark = pos;
|
||||
}
|
||||
|
||||
public func rewindToMark() {
|
||||
pos = mark;
|
||||
}
|
||||
|
||||
public func consumeAsString() -> String {
|
||||
let p = pos;
|
||||
pos+=1
|
||||
return String(input[p])
|
||||
//return String(input, pos+=1, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of characters between the current position and the next instance of the input char
|
||||
* @param c scan target
|
||||
* @return offset between current position and next instance of target. -1 if not found.
|
||||
*/
|
||||
public func nextIndexOf(_ c : UnicodeScalar) -> Int {
|
||||
// doesn't handle scanning for surrogates
|
||||
for i in pos..<length {
|
||||
if (c == input.unicodeScalar(i)){
|
||||
return i - pos;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of characters between the current position and the next instance of the input sequence
|
||||
*
|
||||
* @param seq scan target
|
||||
* @return offset between current position and next instance of target. -1 if not found.
|
||||
*/
|
||||
public func nextIndexOf(_ seq: String) -> Int {
|
||||
// doesn't handle scanning for surrogates
|
||||
if(seq.isEmpty){return -1}
|
||||
let startChar : UnicodeScalar = seq.unicodeScalar(0)
|
||||
for var offset in pos..<length {
|
||||
// scan to first instance of startchar:
|
||||
if (startChar != input.unicodeScalar(offset)){
|
||||
offset+=1
|
||||
while(offset < length && startChar != input.unicodeScalar(offset)) { offset+=1 }
|
||||
}
|
||||
var i = offset + 1;
|
||||
let last = i + seq.unicodeScalars.count-1;
|
||||
if (offset < length && last <= length)
|
||||
{
|
||||
var j = 1;
|
||||
while i < last && seq.unicodeScalar(j) == input.unicodeScalar(i) {
|
||||
j+=1
|
||||
i+=1;
|
||||
}
|
||||
// found full sequence
|
||||
if (i == last){
|
||||
return offset - pos;
|
||||
}
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
public func consumeTo(_ c : UnicodeScalar) -> String {
|
||||
let offset = nextIndexOf(c);
|
||||
if (offset != -1) {
|
||||
let consumed = cacheString(pos, offset);
|
||||
pos += offset;
|
||||
return consumed;
|
||||
} else {
|
||||
return consumeToEnd();
|
||||
}
|
||||
}
|
||||
|
||||
public func consumeTo(_ seq: String) -> String {
|
||||
let offset = nextIndexOf(seq);
|
||||
if (offset != -1) {
|
||||
let consumed = cacheString(pos, offset);
|
||||
pos += offset;
|
||||
return consumed;
|
||||
} else {
|
||||
return consumeToEnd();
|
||||
}
|
||||
}
|
||||
|
||||
public func consumeToAny(_ chars: UnicodeScalar...)->String {
|
||||
return consumeToAny(chars)
|
||||
}
|
||||
public func consumeToAny(_ chars: [UnicodeScalar])->String {
|
||||
let start : Int = pos;
|
||||
let remaining : Int = length;
|
||||
let val = input;
|
||||
|
||||
OUTER: while (pos < remaining) {
|
||||
for c in chars {
|
||||
if (val.unicodeScalar(pos) == c){
|
||||
break OUTER;
|
||||
}
|
||||
}
|
||||
pos += 1;
|
||||
}
|
||||
|
||||
return pos > start ? cacheString(start, pos-start) : "";
|
||||
}
|
||||
|
||||
|
||||
public func consumeToAnySorted(_ chars: UnicodeScalar...)->String {
|
||||
return consumeToAnySorted(chars)
|
||||
}
|
||||
public func consumeToAnySorted(_ chars: [UnicodeScalar])->String {
|
||||
let start = pos;
|
||||
let remaining = length;
|
||||
let val = input;
|
||||
|
||||
while (pos < remaining) {
|
||||
if (chars.binarySearch(chars, val.unicodeScalar(pos)) >= 0){
|
||||
break;
|
||||
}
|
||||
pos += 1;
|
||||
}
|
||||
|
||||
return pos > start ? cacheString(start, pos-start) : "";
|
||||
}
|
||||
|
||||
|
||||
|
||||
public func consumeData() -> String {
|
||||
// &, <, null
|
||||
let start = pos;
|
||||
let remaining = length;
|
||||
let val = input;
|
||||
|
||||
while (pos < remaining) {
|
||||
let c : UnicodeScalar = val.unicodeScalar(pos);
|
||||
if (c == "&" || c == "<" || c == TokeniserStateVars.nullScalr){
|
||||
break;
|
||||
}
|
||||
pos += 1;
|
||||
}
|
||||
|
||||
return pos > start ? cacheString(start, pos-start) : "";
|
||||
}
|
||||
|
||||
public func consumeTagName()-> String {
|
||||
// '\t', '\n', '\r', '\f', ' ', '/', '>', nullChar
|
||||
let start = pos;
|
||||
let remaining = length;
|
||||
let val = input;
|
||||
|
||||
while (pos < remaining) {
|
||||
let c : UnicodeScalar = val.unicodeScalar(pos)
|
||||
if (c == "\t" || c == "\n" || c == "\r" || c == UnicodeScalar.BackslashF || c == " " || c == "/" || c == ">" || c == TokeniserStateVars.nullScalr){
|
||||
break;
|
||||
}
|
||||
pos += 1;
|
||||
}
|
||||
return pos > start ? cacheString(start, pos-start) : "";
|
||||
}
|
||||
|
||||
|
||||
public func consumeToEnd()-> String {
|
||||
let data = cacheString(pos, length-pos);
|
||||
pos = length;
|
||||
return data;
|
||||
}
|
||||
|
||||
public func consumeLetterSequence()-> String {
|
||||
let start = pos;
|
||||
while (pos < length) {
|
||||
let c : UnicodeScalar = input.unicodeScalar(pos)
|
||||
if ((c >= "A" && c <= "Z") || (c >= "a" && c <= "z") || c.isMemberOfCharacterSet(CharacterSet.letters)){
|
||||
pos += 1;
|
||||
}else{
|
||||
break;
|
||||
}
|
||||
}
|
||||
return cacheString(start, pos - start);
|
||||
}
|
||||
|
||||
public func consumeLetterThenDigitSequence()-> String {
|
||||
let start = pos;
|
||||
while (pos < length) {
|
||||
let c = input.unicodeScalar(pos)
|
||||
if ((c >= "A" && c <= "Z") || (c >= "a" && c <= "z") || c.isMemberOfCharacterSet(CharacterSet.letters)){
|
||||
pos += 1;
|
||||
}else{
|
||||
break;
|
||||
}
|
||||
}
|
||||
while (!isEmpty()) {
|
||||
let c = input.unicodeScalar(pos)
|
||||
if (c >= "0" && c <= "9"){
|
||||
pos += 1;
|
||||
}else{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return cacheString(start, pos - start);
|
||||
}
|
||||
|
||||
public func consumeHexSequence()-> String {
|
||||
let start = pos;
|
||||
while (pos < length) {
|
||||
let c = input.unicodeScalar(pos)
|
||||
if ((c >= "0" && c <= "9") || (c >= "A" && c <= "F") || (c >= "a" && c <= "f")){
|
||||
pos+=1;
|
||||
}else{
|
||||
break;
|
||||
}
|
||||
}
|
||||
return cacheString(start, pos - start);
|
||||
}
|
||||
|
||||
public func consumeDigitSequence() -> String {
|
||||
let start = pos;
|
||||
while (pos < length) {
|
||||
let c = input.unicodeScalar(pos)
|
||||
if (c >= "0" && c <= "9"){
|
||||
pos+=1;
|
||||
}else{
|
||||
break;
|
||||
}
|
||||
}
|
||||
return cacheString(start, pos - start);
|
||||
}
|
||||
|
||||
public func matches(_ c: UnicodeScalar) -> Bool {
|
||||
return !isEmpty() && input.unicodeScalar(pos) == c;
|
||||
|
||||
}
|
||||
|
||||
public func matches(_ seq: String)-> Bool {
|
||||
let scanLength = seq.unicodeScalars.count;
|
||||
if (scanLength > length - pos){
|
||||
return false;
|
||||
}
|
||||
|
||||
for offset in 0..<scanLength{
|
||||
if (seq.unicodeScalar(offset) != input.unicodeScalar(pos+offset)){
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public func matchesIgnoreCase(_ seq: String )->Bool {
|
||||
|
||||
let scanLength = seq.unicodeScalars.count;
|
||||
if(scanLength == 0){
|
||||
return false
|
||||
}
|
||||
if (scanLength > length - pos){
|
||||
return false;
|
||||
}
|
||||
|
||||
for offset in 0..<scanLength{
|
||||
let upScan : UnicodeScalar = seq.unicodeScalar(offset).uppercase
|
||||
let upTarget : UnicodeScalar = input.unicodeScalar(pos+offset).uppercase;
|
||||
if (upScan != upTarget){
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public func matchesAny(_ seq: UnicodeScalar...)->Bool {
|
||||
if (isEmpty()){
|
||||
return false;
|
||||
}
|
||||
|
||||
let c : UnicodeScalar = input.unicodeScalar(pos);
|
||||
for seek in seq {
|
||||
if (seek == c){
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public func matchesAnySorted(_ seq : [UnicodeScalar]) -> Bool {
|
||||
return !isEmpty() && seq.binarySearch(seq, input.unicodeScalar(pos)) >= 0;
|
||||
}
|
||||
|
||||
public func matchesLetter()-> Bool {
|
||||
if (isEmpty()){
|
||||
return false;
|
||||
}
|
||||
let c = input.unicodeScalar(pos);
|
||||
return (c >= "A" && c <= "Z") || (c >= "a" && c <= "z") || c.isMemberOfCharacterSet(CharacterSet.letters);
|
||||
}
|
||||
|
||||
public func matchesDigit()->Bool {
|
||||
if (isEmpty()){
|
||||
return false;
|
||||
}
|
||||
let c = input.unicodeScalar(pos)
|
||||
return (c >= "0" && c <= "9");
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
public func matchConsume(_ seq: String)->Bool {
|
||||
if (matches(seq)) {
|
||||
pos += seq.unicodeScalars.count;
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
public func matchConsumeIgnoreCase(_ seq: String)->Bool {
|
||||
if (matchesIgnoreCase(seq)) {
|
||||
pos += seq.unicodeScalars.count;
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public func containsIgnoreCase(_ seq: String )->Bool {
|
||||
// used to check presence of </title>, </style>. only finds consistent case.
|
||||
let loScan = seq.lowercased(with: Locale(identifier: "en"))
|
||||
let hiScan = seq.uppercased(with: Locale(identifier: "eng"))
|
||||
return (nextIndexOf(loScan) > -1) || (nextIndexOf(hiScan) > -1);
|
||||
}
|
||||
|
||||
|
||||
public func toString()->String {
|
||||
return input.string(pos, length - pos);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Caches short strings, as a flywheel pattern, to reduce GC load. Just for this doc, to prevent leaks.
|
||||
* <p />
|
||||
* Simplistic, and on hash collisions just falls back to creating a new string, vs a full HashMap with Entry list.
|
||||
* That saves both having to create objects as hash keys, and running through the entry list, at the expense of
|
||||
* some more duplicates.
|
||||
*/
|
||||
private func cacheString(_ start: Int, _ count: Int) -> String {
|
||||
let val = input;
|
||||
var cache : [String?] = stringCache;
|
||||
|
||||
// limit (no cache):
|
||||
if (count > CharacterReader.maxCacheLen){
|
||||
return val.string(start, count);
|
||||
}
|
||||
|
||||
// calculate hash:
|
||||
var hash : Int = 0;
|
||||
var offset = start;
|
||||
for _ in 0..<count{
|
||||
let ch = val.unicodeScalar(pos).value;
|
||||
hash = Int.addWithOverflow(Int.multiplyWithOverflow(31, hash).0, Int(ch)).0;
|
||||
offset+=1
|
||||
}
|
||||
|
||||
// get from cache
|
||||
hash = abs(hash)
|
||||
let i = hash % cache.count
|
||||
let index : Int = abs(i) //Int(hash & Int(cache.count) - 1);
|
||||
var cached = cache[index];
|
||||
|
||||
if (cached == nil)
|
||||
{ // miss, add
|
||||
cached = val.string(start, count);
|
||||
cache[Int(index)] = cached;
|
||||
} else { // hashcode hit, check equality
|
||||
if (rangeEquals(start, count, cached!)) { // hit
|
||||
return cached!;
|
||||
} else { // hashcode conflict
|
||||
cached = val.string(start, count);
|
||||
cache[index] = cached; // update the cache, as recently used strings are more likely to show up again
|
||||
}
|
||||
}
|
||||
return cached!;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the value of the provided range equals the string.
|
||||
*/
|
||||
public func rangeEquals(_ start: Int, _ count: Int, _ cached: String) -> Bool {
|
||||
if (count == cached.unicodeScalars.count)
|
||||
{
|
||||
var count = count
|
||||
let one = input;
|
||||
var i = start;
|
||||
var j = 0;
|
||||
while (count != 0) {
|
||||
count -= 1
|
||||
if (one.unicodeScalar(i) != cached.unicodeScalar(j) )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
j += 1
|
||||
i += 1
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,767 @@
|
|||
//
|
||||
// HtmlTreeBuilder.swift
|
||||
// SwiftSoup
|
||||
//
|
||||
// Created by Nabil Chatbi on 24/10/16.
|
||||
// Copyright © 2016 Nabil Chatbi.. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
/**
|
||||
* HTML Tree Builder; creates a DOM from Tokens.
|
||||
*/
|
||||
class HtmlTreeBuilder: TreeBuilder {
|
||||
// tag searches
|
||||
public static let TagsSearchInScope: [String] = ["applet", "caption", "html", "table", "td", "th", "marquee", "object"]
|
||||
private static let TagSearchList: [String] = ["ol", "ul"]
|
||||
private static let TagSearchButton: [String] = ["button"]
|
||||
private static let TagSearchTableScope: [String] = ["html", "table"]
|
||||
private static let TagSearchSelectScope: [String] = ["optgroup", "option"]
|
||||
private static let TagSearchEndTags: [String] = ["dd", "dt", "li", "option", "optgroup", "p", "rp", "rt"]
|
||||
private static let TagSearchSpecial: [String] = ["address", "applet", "area", "article", "aside", "base", "basefont", "bgsound",
|
||||
"blockquote", "body", "br", "button", "caption", "center", "col", "colgroup", "command", "dd",
|
||||
"details", "dir", "div", "dl", "dt", "embed", "fieldset", "figcaption", "figure", "footer", "form",
|
||||
"frame", "frameset", "h1", "h2", "h3", "h4", "h5", "h6", "head", "header", "hgroup", "hr", "html",
|
||||
"iframe", "img", "input", "isindex", "li", "link", "listing", "marquee", "menu", "meta", "nav",
|
||||
"noembed", "noframes", "noscript", "object", "ol", "p", "param", "plaintext", "pre", "script",
|
||||
"section", "select", "style", "summary", "table", "tbody", "td", "textarea", "tfoot", "th", "thead",
|
||||
"title", "tr", "ul", "wbr", "xmp"]
|
||||
|
||||
private var _state: HtmlTreeBuilderState = HtmlTreeBuilderState.Initial // the current state
|
||||
private var _originalState: HtmlTreeBuilderState = HtmlTreeBuilderState.Initial // original / marked state
|
||||
|
||||
private var baseUriSetFromDoc: Bool = false;
|
||||
private var headElement: Element? // the current head element
|
||||
private var formElement: FormElement? // the current form element
|
||||
private var contextElement: Element? // fragment parse context -- could be null even if fragment parsing
|
||||
private var formattingElements: Array<Element?> = Array<Element?>(); // active (open) formatting elements
|
||||
private var pendingTableCharacters: Array<String> = Array<String>(); // chars in table to be shifted out
|
||||
private var emptyEnd: Token.EndTag = Token.EndTag(); // reused empty end tag
|
||||
|
||||
private var _framesetOk: Bool = true; // if ok to go into frameset
|
||||
private var fosterInserts: Bool = false; // if next inserts should be fostered
|
||||
private var fragmentParsing: Bool = false; // if parsing a fragment of html
|
||||
|
||||
|
||||
public override init() {
|
||||
super.init()
|
||||
}
|
||||
|
||||
|
||||
public override func defaultSettings()->ParseSettings {
|
||||
return ParseSettings.htmlDefault;
|
||||
}
|
||||
|
||||
override func parse(_ input: String, _ baseUri: String, _ errors: ParseErrorList, _ settings: ParseSettings)throws->Document {
|
||||
_state = HtmlTreeBuilderState.Initial;
|
||||
baseUriSetFromDoc = false;
|
||||
return try super.parse(input, baseUri, errors, settings);
|
||||
}
|
||||
|
||||
func parseFragment(_ inputFragment: String, _ context: Element?, _ baseUri: String, _ errors: ParseErrorList, _ settings: ParseSettings)throws->Array<Node> {
|
||||
// context may be null
|
||||
_state = HtmlTreeBuilderState.Initial;
|
||||
initialiseParse(inputFragment, baseUri, errors, settings);
|
||||
contextElement = context;
|
||||
fragmentParsing = true;
|
||||
var root: Element? = nil;
|
||||
|
||||
if let context = context {
|
||||
if let d = context.ownerDocument() { // quirks setup:
|
||||
doc.quirksMode(d.quirksMode());
|
||||
}
|
||||
|
||||
// initialise the tokeniser state:
|
||||
let contextTag: String = context.tagName();
|
||||
if (StringUtil.inString(contextTag, haystack: "title", "textarea")){
|
||||
tokeniser.transition(TokeniserState.Rcdata);
|
||||
}else if (StringUtil.inString(contextTag, haystack: "iframe", "noembed", "noframes", "style", "xmp")){
|
||||
tokeniser.transition(TokeniserState.Rawtext);
|
||||
}else if (contextTag=="script"){
|
||||
tokeniser.transition(TokeniserState.ScriptData);
|
||||
}else if (contextTag==("noscript")){
|
||||
tokeniser.transition(TokeniserState.Data); // if scripting enabled, rawtext
|
||||
}else if (contextTag=="plaintext"){
|
||||
tokeniser.transition(TokeniserState.Data);
|
||||
}else{
|
||||
tokeniser.transition(TokeniserState.Data); // default
|
||||
}
|
||||
|
||||
root = try Element(Tag.valueOf("html", settings), baseUri);
|
||||
try Validate.notNull(obj:root);
|
||||
try doc.appendChild(root!);
|
||||
stack.append(root!);
|
||||
resetInsertionMode();
|
||||
|
||||
// setup form element to nearest form on context (up ancestor chain). ensures form controls are associated
|
||||
// with form correctly
|
||||
let contextChain: Elements = context.parents();
|
||||
contextChain.add(0, context);
|
||||
for parent:Element in contextChain.array() {
|
||||
if let x = (parent as? FormElement) {
|
||||
formElement = x;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
try runParser();
|
||||
if (context != nil && root != nil){
|
||||
return root!.getChildNodes();
|
||||
}else{
|
||||
return doc.getChildNodes();
|
||||
}
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
public override func process(_ token: Token)throws->Bool{
|
||||
currentToken = token;
|
||||
return try self._state.process(token, self);
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func process(_ token: Token, _ state: HtmlTreeBuilderState)throws->Bool {
|
||||
currentToken = token;
|
||||
return try state.process(token, self);
|
||||
}
|
||||
|
||||
func transition(_ state: HtmlTreeBuilderState) {
|
||||
self._state = state;
|
||||
}
|
||||
|
||||
func state()->HtmlTreeBuilderState {
|
||||
return _state;
|
||||
}
|
||||
|
||||
func markInsertionMode() {
|
||||
_originalState = _state;
|
||||
}
|
||||
|
||||
func originalState()->HtmlTreeBuilderState {
|
||||
return _originalState;
|
||||
}
|
||||
|
||||
func framesetOk(_ framesetOk: Bool) {
|
||||
self._framesetOk = framesetOk;
|
||||
}
|
||||
|
||||
func framesetOk()->Bool {
|
||||
return _framesetOk;
|
||||
}
|
||||
|
||||
func getDocument()->Document {
|
||||
return doc;
|
||||
}
|
||||
|
||||
func getBaseUri()->String {
|
||||
return baseUri;
|
||||
}
|
||||
|
||||
func maybeSetBaseUri(_ base: Element)throws {
|
||||
if (baseUriSetFromDoc){ // only listen to the first <base href> in parse
|
||||
return;
|
||||
}
|
||||
|
||||
let href: String = try base.absUrl("href");
|
||||
if (href.characters.count != 0) { // ignore <base target> etc
|
||||
baseUri = href;
|
||||
baseUriSetFromDoc = true;
|
||||
try doc.setBaseUri(href); // set on the doc so doc.createElement(Tag) will get updated base, and to update all descendants
|
||||
}
|
||||
}
|
||||
|
||||
func isFragmentParsing()->Bool {
|
||||
return fragmentParsing;
|
||||
}
|
||||
|
||||
func error(_ state: HtmlTreeBuilderState) {
|
||||
if (errors.canAddError() && currentToken != nil){
|
||||
errors.add(ParseError(reader.getPos(), "Unexpected token [%s] when in state [%s]", currentToken!.tokenType(), state.rawValue))
|
||||
}
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func insert(_ startTag: Token.StartTag)throws->Element {
|
||||
// handle empty unknown tags
|
||||
// when the spec expects an empty tag, will directly hit insertEmpty, so won't generate this fake end tag.
|
||||
if (startTag.isSelfClosing()) {
|
||||
let el: Element = try insertEmpty(startTag);
|
||||
stack.append(el);
|
||||
tokeniser.transition(TokeniserState.Data); // handles <script />, otherwise needs breakout steps from script data
|
||||
try tokeniser.emit(emptyEnd.reset().name(el.tagName())); // ensure we get out of whatever state we are in. emitted for yielded processing
|
||||
return el;
|
||||
}
|
||||
try Validate.notNull(obj: startTag._attributes)
|
||||
let el: Element = try Element(Tag.valueOf(startTag.name(), settings), baseUri, settings.normalizeAttributes(startTag._attributes));
|
||||
try insert(el);
|
||||
return el;
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func insertStartTag(_ startTagName: String)throws->Element {
|
||||
let el: Element = try Element(Tag.valueOf(startTagName, settings), baseUri);
|
||||
try insert(el);
|
||||
return el;
|
||||
}
|
||||
|
||||
func insert(_ el: Element)throws {
|
||||
try insertNode(el)
|
||||
stack.append(el)
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func insertEmpty(_ startTag: Token.StartTag)throws->Element {
|
||||
let tag: Tag = try Tag.valueOf(startTag.name(), settings)
|
||||
try Validate.notNull(obj: startTag._attributes)
|
||||
let el: Element = Element(tag, baseUri, startTag._attributes);
|
||||
try insertNode(el);
|
||||
if (startTag.isSelfClosing()) {
|
||||
if (tag.isKnownTag()) {
|
||||
if (tag.isSelfClosing()) {tokeniser.acknowledgeSelfClosingFlag()} // if not acked, promulagates error
|
||||
} else {
|
||||
// unknown tag, remember this is self closing for output
|
||||
tag.setSelfClosing();
|
||||
tokeniser.acknowledgeSelfClosingFlag(); // not an distinct error
|
||||
}
|
||||
}
|
||||
return el;
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func insertForm(_ startTag: Token.StartTag, _ onStack: Bool)throws->FormElement {
|
||||
let tag: Tag = try Tag.valueOf(startTag.name(), settings)
|
||||
try Validate.notNull(obj: startTag._attributes)
|
||||
let el: FormElement = FormElement(tag, baseUri, startTag._attributes);
|
||||
setFormElement(el);
|
||||
try insertNode(el);
|
||||
if (onStack){
|
||||
stack.append(el);
|
||||
}
|
||||
return el;
|
||||
}
|
||||
|
||||
func insert(_ commentToken: Token.Comment)throws {
|
||||
let comment: Comment = Comment(commentToken.getData(), baseUri);
|
||||
try insertNode(comment);
|
||||
}
|
||||
|
||||
func insert(_ characterToken: Token.Char)throws {
|
||||
var node: Node
|
||||
// characters in script and style go in as datanodes, not text nodes
|
||||
let tagName: String? = currentElement()?.tagName();
|
||||
if (tagName=="script" || tagName=="style"){
|
||||
try Validate.notNull(obj: characterToken.getData());
|
||||
node = DataNode(characterToken.getData()!, baseUri);
|
||||
}else{
|
||||
try Validate.notNull(obj: characterToken.getData());
|
||||
node = TextNode(characterToken.getData()!, baseUri);
|
||||
}
|
||||
try currentElement()?.appendChild(node); // doesn't use insertNode, because we don't foster these; and will always have a stack.
|
||||
}
|
||||
|
||||
private func insertNode(_ node: Node)throws {
|
||||
// if the stack hasn't been set up yet, elements (doctype, comments) go into the doc
|
||||
if (stack.count == 0){
|
||||
try doc.appendChild(node);
|
||||
}else if (isFosterInserts()){
|
||||
try insertInFosterParent(node);
|
||||
}else{
|
||||
try currentElement()?.appendChild(node)
|
||||
}
|
||||
|
||||
// connect form controls to their form element
|
||||
if let n = (node as? Element) {
|
||||
if(n.tag().isFormListed()){
|
||||
if ( formElement != nil){
|
||||
formElement!.addElement(n)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func pop()->Element {
|
||||
let size: Int = stack.count;
|
||||
return stack.remove(at: size-1);
|
||||
}
|
||||
|
||||
func push(_ element: Element) {
|
||||
stack.append(element);
|
||||
}
|
||||
|
||||
func getStack()->Array<Element> {
|
||||
return stack;
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func onStack(_ el: Element)->Bool {
|
||||
return isElementInQueue(stack, el);
|
||||
}
|
||||
|
||||
private func isElementInQueue(_ queue: Array<Element?>, _ element: Element?)->Bool {
|
||||
for pos in (0..<queue.count).reversed() {
|
||||
let next: Element? = queue[pos]
|
||||
if (next == element) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
func getFromStack(_ elName: String)->Element? {
|
||||
for pos in (0..<stack.count).reversed() {
|
||||
let next: Element = stack[pos]
|
||||
if next.nodeName() == elName {
|
||||
return next
|
||||
}
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func removeFromStack(_ el: Element)->Bool {
|
||||
for pos in (0..<stack.count).reversed() {
|
||||
let next: Element = stack[pos]
|
||||
if (next == el) {
|
||||
stack.remove(at: pos);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
func popStackToClose(_ elName: String) {
|
||||
for pos in (0..<stack.count).reversed() {
|
||||
let next: Element = stack[pos]
|
||||
stack.remove(at: pos);
|
||||
if (next.nodeName() == elName){
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func popStackToClose(_ elNames: String...) {
|
||||
popStackToClose(elNames)
|
||||
}
|
||||
func popStackToClose(_ elNames: [String]) {
|
||||
for pos in (0..<stack.count).reversed() {
|
||||
let next: Element = stack[pos]
|
||||
stack.remove(at: pos);
|
||||
if (StringUtil.inString(next.nodeName(),elNames)){
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func popStackToBefore(_ elName: String) {
|
||||
for pos in (0..<stack.count).reversed() {
|
||||
let next: Element = stack[pos]
|
||||
if (next.nodeName() == elName) {
|
||||
break;
|
||||
} else {
|
||||
stack.remove(at: pos);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func clearStackToTableContext() {
|
||||
clearStackToContext("table");
|
||||
}
|
||||
|
||||
func clearStackToTableBodyContext() {
|
||||
clearStackToContext("tbody", "tfoot", "thead");
|
||||
}
|
||||
|
||||
func clearStackToTableRowContext() {
|
||||
clearStackToContext("tr");
|
||||
}
|
||||
|
||||
private func clearStackToContext(_ nodeNames: String...) {
|
||||
clearStackToContext(nodeNames)
|
||||
}
|
||||
private func clearStackToContext(_ nodeNames: [String]) {
|
||||
for pos in (0..<stack.count).reversed() {
|
||||
let next: Element = stack[pos]
|
||||
if (StringUtil.inString(next.nodeName(), nodeNames) || next.nodeName()=="html"){
|
||||
break;
|
||||
}else{
|
||||
stack.remove(at: pos);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func aboveOnStack(_ el: Element)->Element? {
|
||||
//assert(onStack(el), "Invalid parameter")
|
||||
onStack(el)
|
||||
for pos in (0..<stack.count).reversed() {
|
||||
let next: Element = stack[pos]
|
||||
if (next == el) {
|
||||
return stack[pos-1]
|
||||
}
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
func insertOnStackAfter(_ after: Element, _ input: Element)throws {
|
||||
let i: Int = stack.lastIndexOf(after);
|
||||
try Validate.isTrue(val: i != -1);
|
||||
stack.insert(input, at: i + 1 );
|
||||
}
|
||||
|
||||
func replaceOnStack(_ out: Element, _ input: Element)throws {
|
||||
try stack = replaceInQueue(stack, out, input);
|
||||
}
|
||||
|
||||
private func replaceInQueue(_ queue: Array<Element>, _ out: Element, _ input: Element)throws->Array<Element> {
|
||||
var queue = queue
|
||||
let i: Int = queue.lastIndexOf(out);
|
||||
try Validate.isTrue(val: i != -1);
|
||||
queue[i] = input
|
||||
return queue
|
||||
}
|
||||
|
||||
func resetInsertionMode() {
|
||||
var last = false;
|
||||
for pos in (0..<stack.count).reversed() {
|
||||
var node: Element = stack[pos]
|
||||
if (pos == 0) {
|
||||
last = true;
|
||||
//Validate node
|
||||
node = contextElement!;
|
||||
}
|
||||
let name: String = node.nodeName();
|
||||
if ("select".equals(name)) {
|
||||
transition(HtmlTreeBuilderState.InSelect);
|
||||
break; // frag
|
||||
} else if (("td".equals(name) || "th".equals(name) && !last)) {
|
||||
transition(HtmlTreeBuilderState.InCell);
|
||||
break;
|
||||
} else if ("tr".equals(name)) {
|
||||
transition(HtmlTreeBuilderState.InRow);
|
||||
break;
|
||||
} else if ("tbody".equals(name) || "thead".equals(name) || "tfoot".equals(name)) {
|
||||
transition(HtmlTreeBuilderState.InTableBody);
|
||||
break;
|
||||
} else if ("caption".equals(name)) {
|
||||
transition(HtmlTreeBuilderState.InCaption);
|
||||
break;
|
||||
} else if ("colgroup".equals(name)) {
|
||||
transition(HtmlTreeBuilderState.InColumnGroup);
|
||||
break; // frag
|
||||
} else if ("table".equals(name)) {
|
||||
transition(HtmlTreeBuilderState.InTable);
|
||||
break;
|
||||
} else if ("head".equals(name)) {
|
||||
transition(HtmlTreeBuilderState.InBody);
|
||||
break; // frag
|
||||
} else if ("body".equals(name)) {
|
||||
transition(HtmlTreeBuilderState.InBody);
|
||||
break;
|
||||
} else if ("frameset".equals(name)) {
|
||||
transition(HtmlTreeBuilderState.InFrameset);
|
||||
break; // frag
|
||||
} else if ("html".equals(name)) {
|
||||
transition(HtmlTreeBuilderState.BeforeHead);
|
||||
break; // frag
|
||||
} else if (last) {
|
||||
transition(HtmlTreeBuilderState.InBody);
|
||||
break; // frag
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// todo: tidy up in specific scope methods
|
||||
private var specificScopeTarget: [String?] = [nil]
|
||||
|
||||
private func inSpecificScope(_ targetName: String, _ baseTypes: [String], _ extraTypes: [String]?)throws->Bool {
|
||||
specificScopeTarget[0] = targetName;
|
||||
return try inSpecificScope(specificScopeTarget, baseTypes, extraTypes);
|
||||
}
|
||||
|
||||
private func inSpecificScope(_ targetNames: [String?], _ baseTypes: [String] , _ extraTypes: [String]?)throws->Bool {
|
||||
for pos in (0..<stack.count).reversed() {
|
||||
let el: Element = stack[pos]
|
||||
let elName: String = el.nodeName();
|
||||
if (StringUtil.inString(elName, targetNames)){
|
||||
return true;
|
||||
}
|
||||
if (StringUtil.inString(elName, baseTypes)){
|
||||
return false;
|
||||
}
|
||||
if (extraTypes != nil && StringUtil.inString(elName, extraTypes!)){
|
||||
return false;
|
||||
}
|
||||
}
|
||||
try Validate.fail(msg: "Should not be reachable");
|
||||
return false;
|
||||
}
|
||||
|
||||
func inScope(_ targetNames: [String])throws->Bool {
|
||||
return try inSpecificScope(targetNames, HtmlTreeBuilder.TagsSearchInScope, nil);
|
||||
}
|
||||
|
||||
func inScope(_ targetName: String)throws->Bool {
|
||||
return try inScope(targetName, nil);
|
||||
}
|
||||
|
||||
func inScope(_ targetName: String, _ extras: [String]?)throws->Bool {
|
||||
return try inSpecificScope(targetName, HtmlTreeBuilder.TagsSearchInScope, extras);
|
||||
// todo: in mathml namespace: mi, mo, mn, ms, mtext annotation-xml
|
||||
// todo: in svg namespace: forignOjbect, desc, title
|
||||
}
|
||||
|
||||
func inListItemScope(_ targetName: String)throws->Bool {
|
||||
return try inScope(targetName, HtmlTreeBuilder.TagSearchList);
|
||||
}
|
||||
|
||||
func inButtonScope(_ targetName: String)throws->Bool {
|
||||
return try inScope(targetName, HtmlTreeBuilder.TagSearchButton);
|
||||
}
|
||||
|
||||
func inTableScope(_ targetName: String)throws->Bool {
|
||||
return try inSpecificScope(targetName, HtmlTreeBuilder.TagSearchTableScope, nil);
|
||||
}
|
||||
|
||||
func inSelectScope(_ targetName: String)throws->Bool {
|
||||
for pos in (0..<stack.count).reversed() {
|
||||
let el: Element = stack[pos]
|
||||
let elName: String = el.nodeName();
|
||||
if (elName.equals(targetName)){
|
||||
return true;
|
||||
}
|
||||
if (!StringUtil.inString(elName, HtmlTreeBuilder.TagSearchSelectScope)){ // all elements except
|
||||
return false;
|
||||
}
|
||||
}
|
||||
try Validate.fail(msg: "Should not be reachable");
|
||||
return false;
|
||||
}
|
||||
|
||||
func setHeadElement(_ headElement: Element) {
|
||||
self.headElement = headElement;
|
||||
}
|
||||
|
||||
func getHeadElement()->Element? {
|
||||
return headElement;
|
||||
}
|
||||
|
||||
func isFosterInserts()->Bool {
|
||||
return fosterInserts;
|
||||
}
|
||||
|
||||
func setFosterInserts(_ fosterInserts: Bool) {
|
||||
self.fosterInserts = fosterInserts;
|
||||
}
|
||||
|
||||
func getFormElement()->FormElement? {
|
||||
return formElement;
|
||||
}
|
||||
|
||||
func setFormElement(_ formElement: FormElement?) {
|
||||
self.formElement = formElement;
|
||||
}
|
||||
|
||||
func newPendingTableCharacters() {
|
||||
pendingTableCharacters = Array<String>();
|
||||
}
|
||||
|
||||
func getPendingTableCharacters()->Array<String> {
|
||||
return pendingTableCharacters;
|
||||
}
|
||||
|
||||
func setPendingTableCharacters(_ pendingTableCharacters: Array<String>) {
|
||||
self.pendingTableCharacters = pendingTableCharacters;
|
||||
}
|
||||
|
||||
/**
|
||||
11.2.5.2 Closing elements that have implied end tags<p/>
|
||||
When the steps below require the UA to generate implied end tags, then, while the current node is a dd element, a
|
||||
dt element, an li element, an option element, an optgroup element, a p element, an rp element, or an rt element,
|
||||
the UA must pop the current node off the stack of open elements.
|
||||
|
||||
@param excludeTag If a step requires the UA to generate implied end tags but lists an element to exclude from the
|
||||
process, then the UA must perform the above steps as if that element was not in the above list.
|
||||
*/
|
||||
|
||||
func generateImpliedEndTags(_ excludeTag: String?) {
|
||||
|
||||
while ((excludeTag != nil && !currentElement()!.nodeName().equals(excludeTag!)) &&
|
||||
StringUtil.inString(currentElement()!.nodeName(), HtmlTreeBuilder.TagSearchEndTags)){
|
||||
pop();
|
||||
}
|
||||
}
|
||||
|
||||
func generateImpliedEndTags() {
|
||||
generateImpliedEndTags(nil);
|
||||
}
|
||||
|
||||
func isSpecial(_ el: Element)->Bool {
|
||||
// todo: mathml's mi, mo, mn
|
||||
// todo: svg's foreigObject, desc, title
|
||||
let name: String = el.nodeName();
|
||||
return StringUtil.inString(name, HtmlTreeBuilder.TagSearchSpecial);
|
||||
}
|
||||
|
||||
func lastFormattingElement()->Element? {
|
||||
return formattingElements.count > 0 ? formattingElements[formattingElements.count-1] : nil;
|
||||
}
|
||||
|
||||
func removeLastFormattingElement()->Element? {
|
||||
let size: Int = formattingElements.count
|
||||
if (size > 0){
|
||||
return formattingElements.remove(at: size-1);
|
||||
}else{
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// active formatting elements
|
||||
func pushActiveFormattingElements(_ input: Element) {
|
||||
var numSeen: Int = 0;
|
||||
for pos in (0..<formattingElements.count).reversed() {
|
||||
let el: Element? = formattingElements[pos]
|
||||
if (el == nil){ // marker
|
||||
break;
|
||||
}
|
||||
|
||||
if (isSameFormattingElement(input, el!)){
|
||||
numSeen += 1;
|
||||
}
|
||||
|
||||
if (numSeen == 3) {
|
||||
formattingElements.remove(at: pos);
|
||||
break;
|
||||
}
|
||||
}
|
||||
formattingElements.append(input);
|
||||
}
|
||||
|
||||
private func isSameFormattingElement(_ a: Element, _ b: Element)->Bool {
|
||||
// same if: same namespace, tag, and attributes. Element.equals only checks tag, might in future check children
|
||||
if(a.attributes == nil){
|
||||
return false
|
||||
}
|
||||
|
||||
return a.nodeName().equals(b.nodeName()) &&
|
||||
// a.namespace().equals(b.namespace()) &&
|
||||
a.getAttributes()!.equals(o: b.getAttributes());
|
||||
// todo: namespaces
|
||||
}
|
||||
|
||||
func reconstructFormattingElements()throws {
|
||||
let last: Element? = lastFormattingElement()
|
||||
if (last == nil || onStack(last!)){
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
var entry:Element? = last;
|
||||
let size:Int = formattingElements.count
|
||||
var pos:Int = size - 1;
|
||||
var skip: Bool = false;
|
||||
while (true) {
|
||||
if (pos == 0) { // step 4. if none before, skip to 8
|
||||
skip = true;
|
||||
break;
|
||||
}
|
||||
pos -= 1
|
||||
entry = formattingElements[pos] // step 5. one earlier than entry
|
||||
if (entry == nil || onStack(entry!)) // step 6 - neither marker nor on stack
|
||||
{break} // jump to 8, else continue back to 4
|
||||
}
|
||||
while(true) {
|
||||
if (!skip) // step 7: on later than entry
|
||||
{
|
||||
pos += 1
|
||||
entry = formattingElements[pos]
|
||||
}
|
||||
try Validate.notNull(obj: entry); // should not occur, as we break at last element
|
||||
|
||||
// 8. create new element from element, 9 insert into current node, onto stack
|
||||
skip = false; // can only skip increment from 4.
|
||||
let newEl: Element = try insertStartTag(entry!.nodeName()); // todo: avoid fostering here?
|
||||
// newEl.namespace(entry.namespace()); // todo: namespaces
|
||||
newEl.getAttributes()?.addAll(incoming: entry!.getAttributes());
|
||||
|
||||
// 10. replace entry with new entry
|
||||
formattingElements[pos] = newEl
|
||||
|
||||
// 11
|
||||
if (pos == size-1) // if not last entry in list, jump to 7
|
||||
{break}
|
||||
}
|
||||
}
|
||||
|
||||
func clearFormattingElementsToLastMarker() {
|
||||
while (!formattingElements.isEmpty) {
|
||||
let el: Element? = removeLastFormattingElement();
|
||||
if (el == nil){
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func removeFromActiveFormattingElements(_ el: Element?) {
|
||||
for pos in (0..<formattingElements.count).reversed() {
|
||||
let next: Element? = formattingElements[pos]
|
||||
if (next == el) {
|
||||
formattingElements.remove(at: pos);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func isInActiveFormattingElements(_ el: Element)->Bool {
|
||||
return isElementInQueue(formattingElements, el);
|
||||
}
|
||||
|
||||
|
||||
func getActiveFormattingElement(_ nodeName: String)->Element? {
|
||||
for pos in (0..<formattingElements.count).reversed() {
|
||||
let next: Element? = formattingElements[pos]
|
||||
if (next == nil){ // scope marker
|
||||
break;
|
||||
}else if (next!.nodeName().equals(nodeName)){
|
||||
return next;
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
func replaceActiveFormattingElement(_ out: Element, _ input: Element)throws {
|
||||
try formattingElements = replaceInQueue(formattingElements as! Array<Element>, out, input);//todo: testare as! non è bello
|
||||
}
|
||||
|
||||
func insertMarkerToFormattingElements() {
|
||||
formattingElements.append(nil);
|
||||
}
|
||||
|
||||
func insertInFosterParent(_ input: Node)throws {
|
||||
let fosterParent: Element?
|
||||
let lastTable: Element? = getFromStack("table");
|
||||
var isLastTableParent: Bool = false;
|
||||
if let lastTable = lastTable {
|
||||
if (lastTable.parent() != nil) {
|
||||
fosterParent = lastTable.parent()!;
|
||||
isLastTableParent = true;
|
||||
} else{
|
||||
fosterParent = aboveOnStack(lastTable)
|
||||
}
|
||||
} else { // no table == frag
|
||||
fosterParent = stack[0]
|
||||
}
|
||||
|
||||
if (isLastTableParent) {
|
||||
try Validate.notNull(obj: lastTable); // last table cannot be null by this point.
|
||||
try lastTable!.before(input);
|
||||
}
|
||||
else{
|
||||
try fosterParent?.appendChild(input)
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,48 @@
|
|||
//
|
||||
// ParseError.swift
|
||||
// SwiftSoup
|
||||
//
|
||||
// Created by Nabil Chatbi on 19/10/16.
|
||||
// Copyright © 2016 Nabil Chatbi.. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
/**
|
||||
* A Parse Error records an error in the input HTML that occurs in either the tokenisation or the tree building phase.
|
||||
*/
|
||||
open class ParseError {
|
||||
private let pos : Int;
|
||||
private let errorMsg : String;
|
||||
|
||||
init(_ pos: Int, _ errorMsg: String) {
|
||||
self.pos = pos;
|
||||
self.errorMsg = errorMsg;
|
||||
}
|
||||
|
||||
init(_ pos: Int, _ errorFormat: String, _ args_ : String...) {
|
||||
self.errorMsg = String(format:errorFormat,args_)
|
||||
self.pos = pos;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the error message.
|
||||
* @return the error message.
|
||||
*/
|
||||
open func getErrorMessage()->String {
|
||||
return errorMsg;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the offset of the error.
|
||||
* @return error offset within input
|
||||
*/
|
||||
open func getPosition()->Int {
|
||||
return pos;
|
||||
}
|
||||
|
||||
|
||||
open func toString()->String {
|
||||
return "\(pos): " + errorMsg;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
//
|
||||
// ParseErrorList.swift
|
||||
// SwiftSoup
|
||||
//
|
||||
// Created by Nabil Chatbi on 19/10/16.
|
||||
// Copyright © 2016 Nabil Chatbi.. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
public class ParseErrorList
|
||||
{
|
||||
private static let INITIAL_CAPACITY : Int = 16
|
||||
private let maxSize : Int
|
||||
private let initialCapacity : Int
|
||||
private var array : Array<ParseError?> = Array<ParseError>()
|
||||
|
||||
init(_ initialCapacity: Int, _ maxSize: Int) {
|
||||
self.maxSize = maxSize;
|
||||
self.initialCapacity = initialCapacity
|
||||
array = Array(repeating: nil, count: maxSize)
|
||||
}
|
||||
|
||||
func canAddError()->Bool {
|
||||
return array.count < maxSize;
|
||||
}
|
||||
|
||||
func getMaxSize()->Int {
|
||||
return maxSize;
|
||||
}
|
||||
|
||||
static func noTracking()->ParseErrorList {
|
||||
return ParseErrorList(0, 0);
|
||||
}
|
||||
|
||||
static func tracking(_ maxSize: Int)->ParseErrorList {
|
||||
return ParseErrorList(INITIAL_CAPACITY, maxSize);
|
||||
}
|
||||
|
||||
// // you need to provide the Equatable functionality
|
||||
// static func ==(leftFoo: Foo, rightFoo: Foo) -> Bool {
|
||||
// return ObjectIdentifier(leftFoo) == ObjectIdentifier(rightFoo)
|
||||
// }
|
||||
|
||||
open func add(_ e: ParseError) {
|
||||
array.append(e)
|
||||
}
|
||||
|
||||
open func add(_ index: Int, _ element: ParseError){
|
||||
array.insert(element, at: index)
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
//
|
||||
// ParseSettings.swift
|
||||
// SwiftSoup
|
||||
//
|
||||
// Created by Nabil Chatbi on 14/10/16.
|
||||
// Copyright © 2016 Nabil Chatbi.. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
open class ParseSettings
|
||||
{
|
||||
/**
|
||||
* HTML default settings: both tag and attribute names are lower-cased during parsing.
|
||||
*/
|
||||
public static let htmlDefault : ParseSettings = ParseSettings(false, false)
|
||||
/**
|
||||
* Preserve both tag and attribute case.
|
||||
*/
|
||||
public static let preserveCase : ParseSettings = ParseSettings(true, true)
|
||||
|
||||
|
||||
private let preserveTagCase : Bool;
|
||||
private let preserveAttributeCase : Bool;
|
||||
|
||||
|
||||
/**
|
||||
* Define parse settings.
|
||||
* @param tag preserve tag case?
|
||||
* @param attribute preserve attribute name case?
|
||||
*/
|
||||
public init(_ tag: Bool, _ attribute: Bool) {
|
||||
preserveTagCase = tag;
|
||||
preserveAttributeCase = attribute;
|
||||
}
|
||||
|
||||
open func normalizeTag(_ name: String)->String {
|
||||
var name = name.trim();
|
||||
if (!preserveTagCase){
|
||||
name = name.lowercased();
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
open func normalizeAttribute(_ name: String)->String {
|
||||
var name = name.trim();
|
||||
if (!preserveAttributeCase){
|
||||
name = name.lowercased();
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
open func normalizeAttributes(_ attributes: Attributes)throws ->Attributes {
|
||||
if (!preserveAttributeCase) {
|
||||
for attr in attributes.iterator()
|
||||
{
|
||||
try attr.setKey(key: attr.getKey().lowercased());
|
||||
}
|
||||
}
|
||||
return attributes;
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,203 @@
|
|||
//
|
||||
// Parser.swift
|
||||
// SwifSoup
|
||||
//
|
||||
// Created by Nabil Chatbi on 29/09/16.
|
||||
// Copyright © 2016 Nabil Chatbi.. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
|
||||
/**
|
||||
* Parses HTML into a {@link org.jsoup.nodes.Document}. Generally best to use one of the more convenient parse methods
|
||||
* in {@link org.jsoup.Jsoup}.
|
||||
*/
|
||||
public class Parser
|
||||
{
|
||||
private static let DEFAULT_MAX_ERRORS: Int = 0; // by default, error tracking is disabled.
|
||||
|
||||
private var _treeBuilder: TreeBuilder
|
||||
private var _maxErrors: Int = DEFAULT_MAX_ERRORS
|
||||
private var _errors: ParseErrorList = ParseErrorList(16,16)
|
||||
private var _settings: ParseSettings
|
||||
|
||||
/**
|
||||
* Create a new Parser, using the specified TreeBuilder
|
||||
* @param treeBuilder TreeBuilder to use to parse input into Documents.
|
||||
*/
|
||||
init(_ treeBuilder: TreeBuilder) {
|
||||
self._treeBuilder = treeBuilder;
|
||||
_settings = treeBuilder.defaultSettings();
|
||||
}
|
||||
|
||||
public func parseInput(_ html: String, _ baseUri: String)throws->Document {
|
||||
_errors = isTrackErrors() ? ParseErrorList.tracking(_maxErrors) : ParseErrorList.noTracking();
|
||||
return try _treeBuilder.parse(html, baseUri, _errors, _settings);
|
||||
}
|
||||
|
||||
// gets & sets
|
||||
/**
|
||||
* Get the TreeBuilder currently in use.
|
||||
* @return current TreeBuilder.
|
||||
*/
|
||||
public func getTreeBuilder()->TreeBuilder {
|
||||
return _treeBuilder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the TreeBuilder used when parsing content.
|
||||
* @param treeBuilder current TreeBuilder
|
||||
* @return this, for chaining
|
||||
*/
|
||||
@discardableResult
|
||||
public func setTreeBuilder(_ treeBuilder: TreeBuilder)->Parser {
|
||||
self._treeBuilder = treeBuilder;
|
||||
return self;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if parse error tracking is enabled.
|
||||
* @return current track error state.
|
||||
*/
|
||||
public func isTrackErrors()->Bool {
|
||||
return _maxErrors > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable or disable parse error tracking for the next parse.
|
||||
* @param maxErrors the maximum number of errors to track. Set to 0 to disable.
|
||||
* @return this, for chaining
|
||||
*/
|
||||
@discardableResult
|
||||
public func setTrackErrors(_ maxErrors: Int)->Parser {
|
||||
self._maxErrors = maxErrors;
|
||||
return self;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the parse errors, if any, from the last parse.
|
||||
* @return list of parse errors, up to the size of the maximum errors tracked.
|
||||
*/
|
||||
public func getErrors()->ParseErrorList {
|
||||
return _errors;
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
public func settings(_ settings: ParseSettings)->Parser {
|
||||
self._settings = settings;
|
||||
return self;
|
||||
}
|
||||
|
||||
public func settings()->ParseSettings {
|
||||
return _settings;
|
||||
}
|
||||
|
||||
// static parse functions below
|
||||
/**
|
||||
* Parse HTML into a Document.
|
||||
*
|
||||
* @param html HTML to parse
|
||||
* @param baseUri base URI of document (i.e. original fetch location), for resolving relative URLs.
|
||||
*
|
||||
* @return parsed Document
|
||||
*/
|
||||
public static func parse(_ html: String, _ baseUri: String)throws->Document {
|
||||
let treeBuilder: TreeBuilder = HtmlTreeBuilder();
|
||||
return try treeBuilder.parse(html, baseUri, ParseErrorList.noTracking(), treeBuilder.defaultSettings());
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a fragment of HTML into a list of nodes. The context element, if supplied, supplies parsing context.
|
||||
*
|
||||
* @param fragmentHtml the fragment of HTML to parse
|
||||
* @param context (optional) the element that this HTML fragment is being parsed for (i.e. for inner HTML). This
|
||||
* provides stack context (for implicit element creation).
|
||||
* @param baseUri base URI of document (i.e. original fetch location), for resolving relative URLs.
|
||||
*
|
||||
* @return list of nodes parsed from the input HTML. Note that the context element, if supplied, is not modified.
|
||||
*/
|
||||
public static func parseFragment(_ fragmentHtml: String, _ context: Element?, _ baseUri: String)throws->Array<Node> {
|
||||
let treeBuilder = HtmlTreeBuilder();
|
||||
return try treeBuilder.parseFragment(fragmentHtml, context, baseUri, ParseErrorList.noTracking(), treeBuilder.defaultSettings());
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a fragment of XML into a list of nodes.
|
||||
*
|
||||
* @param fragmentXml the fragment of XML to parse
|
||||
* @param baseUri base URI of document (i.e. original fetch location), for resolving relative URLs.
|
||||
* @return list of nodes parsed from the input XML.
|
||||
*/
|
||||
public static func parseXmlFragment(_ fragmentXml: String, _ baseUri: String)throws->Array<Node> {
|
||||
let treeBuilder: XmlTreeBuilder = XmlTreeBuilder();
|
||||
return try treeBuilder.parseFragment(fragmentXml, baseUri, ParseErrorList.noTracking(), treeBuilder.defaultSettings());
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a fragment of HTML into the {@code body} of a Document.
|
||||
*
|
||||
* @param bodyHtml fragment of HTML
|
||||
* @param baseUri base URI of document (i.e. original fetch location), for resolving relative URLs.
|
||||
*
|
||||
* @return Document, with empty head, and HTML parsed into body
|
||||
*/
|
||||
public static func parseBodyFragment(_ bodyHtml: String, _ baseUri: String)throws->Document {
|
||||
let doc: Document = Document.createShell(baseUri);
|
||||
if let body: Element = doc.body()
|
||||
{
|
||||
let nodeList: Array<Node> = try parseFragment(bodyHtml, body, baseUri);
|
||||
//var nodes: [Node] = nodeList.toArray(Node[nodeList.size()]); // the node list gets modified when re-parented
|
||||
for i in (nodeList.count - 1)...1
|
||||
{
|
||||
try nodeList[i].remove();
|
||||
}
|
||||
for node:Node in nodeList {
|
||||
try body.appendChild(node);
|
||||
}
|
||||
}
|
||||
return doc;
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility method to unescape HTML entities from a string
|
||||
* @param string HTML escaped string
|
||||
* @param inAttribute if the string is to be escaped in strict mode (as attributes are)
|
||||
* @return an unescaped string
|
||||
*/
|
||||
public static func unescapeEntities(_ string: String, _ inAttribute: Bool)throws->String {
|
||||
let tokeniser: Tokeniser = Tokeniser(CharacterReader(string), ParseErrorList.noTracking());
|
||||
return try tokeniser.unescapeEntities(inAttribute);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param bodyHtml HTML to parse
|
||||
* @param baseUri baseUri base URI of document (i.e. original fetch location), for resolving relative URLs.
|
||||
*
|
||||
* @return parsed Document
|
||||
* @deprecated Use {@link #parseBodyFragment} or {@link #parseFragment} instead.
|
||||
*/
|
||||
public static func parseBodyFragmentRelaxed(_ bodyHtml: String, _ baseUri: String)throws->Document {
|
||||
return try parse(bodyHtml, baseUri);
|
||||
}
|
||||
|
||||
// builders
|
||||
|
||||
/**
|
||||
* Create a new HTML parser. This parser treats input as HTML5, and enforces the creation of a normalised document,
|
||||
* based on a knowledge of the semantics of the incoming tags.
|
||||
* @return a new HTML parser.
|
||||
*/
|
||||
public static func htmlParser()->Parser {
|
||||
return Parser(HtmlTreeBuilder());
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new XML parser. This parser assumes no knowledge of the incoming tags and does not treat it as HTML,
|
||||
* rather creates a simple tree directly from the input.
|
||||
* @return a new simple XML parser.
|
||||
*/
|
||||
public static func xmlParser()->Parser {
|
||||
return Parser(XmlTreeBuilder());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,356 @@
|
|||
//
|
||||
// Tag.swift
|
||||
// SwiftSoup
|
||||
//
|
||||
// Created by Nabil Chatbi on 15/10/16.
|
||||
// Copyright © 2016 Nabil Chatbi.. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
open class Tag : Hashable
|
||||
{
|
||||
// map of known tags
|
||||
static var tags: Dictionary<String, Tag> = {
|
||||
do{
|
||||
return try Tag.initializeMaps()
|
||||
}catch{
|
||||
preconditionFailure("This method must be overridden")
|
||||
}
|
||||
return Dictionary<String, Tag>()
|
||||
}()
|
||||
|
||||
fileprivate var _tagName : String;
|
||||
fileprivate var _isBlock : Bool = true; // block or inline
|
||||
fileprivate var _formatAsBlock : Bool = true; // should be formatted as a block
|
||||
fileprivate var _canContainBlock : Bool = true; // Can this tag hold block level tags?
|
||||
fileprivate var _canContainInline : Bool = true; // only pcdata if not
|
||||
fileprivate var _empty : Bool = false; // can hold nothing; e.g. img
|
||||
fileprivate var _selfClosing : Bool = false; // can self close (<foo />). used for unknown tags that self close, without forcing them as empty.
|
||||
fileprivate var _preserveWhitespace : Bool = false; // for pre, textarea, script etc
|
||||
fileprivate var _formList : Bool = false; // a control that appears in forms: input, textarea, output etc
|
||||
fileprivate var _formSubmit : Bool = false; // a control that can be submitted in a form: input etc
|
||||
|
||||
public init(_ tagName: String) {
|
||||
self._tagName = tagName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get this tag's name.
|
||||
*
|
||||
* @return the tag's name
|
||||
*/
|
||||
open func getName()->String {
|
||||
return self._tagName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a Tag by name. If not previously defined (unknown), returns a new generic tag, that can do anything.
|
||||
* <p>
|
||||
* Pre-defined tags (P, DIV etc) will be ==, but unknown tags are not registered and will only .equals().
|
||||
* </p>
|
||||
*
|
||||
* @param tagName Name of tag, e.g. "p". Case insensitive.
|
||||
* @param settings used to control tag name sensitivity
|
||||
* @return The tag, either defined or new generic.
|
||||
*/
|
||||
open static func valueOf(_ tagName: String, _ settings: ParseSettings)throws->Tag {
|
||||
var tagName = tagName
|
||||
var tag : Tag? = Tag.tags[tagName]
|
||||
|
||||
if (tag == nil) {
|
||||
tagName = settings.normalizeTag(tagName);
|
||||
try Validate.notEmpty(string: tagName);
|
||||
tag = Tag.tags[tagName]
|
||||
|
||||
if (tag == nil) {
|
||||
// not defined: create default; go anywhere, do anything! (incl be inside a <p>)
|
||||
tag = Tag(tagName);
|
||||
tag!._isBlock = false;
|
||||
tag!._canContainBlock = true;
|
||||
}
|
||||
}
|
||||
return tag!;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a Tag by name. If not previously defined (unknown), returns a new generic tag, that can do anything.
|
||||
* <p>
|
||||
* Pre-defined tags (P, DIV etc) will be ==, but unknown tags are not registered and will only .equals().
|
||||
* </p>
|
||||
*
|
||||
* @param tagName Name of tag, e.g. "p". <b>Case sensitive</b>.
|
||||
* @return The tag, either defined or new generic.
|
||||
*/
|
||||
open static func valueOf(_ tagName: String)throws->Tag {
|
||||
return try valueOf(tagName, ParseSettings.preserveCase);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets if this is a block tag.
|
||||
*
|
||||
* @return if block tag
|
||||
*/
|
||||
open func isBlock()->Bool {
|
||||
return _isBlock;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets if this tag should be formatted as a block (or as inline)
|
||||
*
|
||||
* @return if should be formatted as block or inline
|
||||
*/
|
||||
open func formatAsBlock()->Bool {
|
||||
return _formatAsBlock;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets if this tag can contain block tags.
|
||||
*
|
||||
* @return if tag can contain block tags
|
||||
*/
|
||||
open func canContainBlock()->Bool {
|
||||
return _canContainBlock;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets if this tag is an inline tag.
|
||||
*
|
||||
* @return if this tag is an inline tag.
|
||||
*/
|
||||
open func isInline()->Bool {
|
||||
return !_isBlock;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets if this tag is a data only tag.
|
||||
*
|
||||
* @return if this tag is a data only tag
|
||||
*/
|
||||
open func isData()->Bool {
|
||||
return !_canContainInline && !isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get if this is an empty tag
|
||||
*
|
||||
* @return if this is an empty tag
|
||||
*/
|
||||
open func isEmpty()->Bool {
|
||||
return _empty;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get if this tag is self closing.
|
||||
*
|
||||
* @return if this tag should be output as self closing.
|
||||
*/
|
||||
open func isSelfClosing()->Bool {
|
||||
return _empty || _selfClosing;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get if this is a pre-defined tag, or was auto created on parsing.
|
||||
*
|
||||
* @return if a known tag
|
||||
*/
|
||||
open func isKnownTag()->Bool {
|
||||
return Tag.tags[_tagName] != nil
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this tagname is a known tag.
|
||||
*
|
||||
* @param tagName name of tag
|
||||
* @return if known HTML tag
|
||||
*/
|
||||
open static func isKnownTag(_ tagName: String)->Bool {
|
||||
return Tag.tags[tagName] != nil
|
||||
}
|
||||
|
||||
/**
|
||||
* Get if this tag should preserve whitespace within child text nodes.
|
||||
*
|
||||
* @return if preserve whitepace
|
||||
*/
|
||||
public func preserveWhitespace()->Bool {
|
||||
return _preserveWhitespace;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get if this tag represents a control associated with a form. E.g. input, textarea, output
|
||||
* @return if associated with a form
|
||||
*/
|
||||
public func isFormListed()->Bool {
|
||||
return _formList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get if this tag represents an element that should be submitted with a form. E.g. input, option
|
||||
* @return if submittable with a form
|
||||
*/
|
||||
public func isFormSubmittable()->Bool {
|
||||
return _formSubmit;
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func setSelfClosing() ->Tag{
|
||||
_selfClosing = true;
|
||||
return self;
|
||||
}
|
||||
|
||||
/// Returns a Boolean value indicating whether two values are equal.
|
||||
///
|
||||
/// Equality is the inverse of inequality. For any values `a` and `b`,
|
||||
/// `a == b` implies that `a != b` is `false`.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - lhs: A value to compare.
|
||||
/// - rhs: Another value to compare.
|
||||
static public func ==(lhs: Tag, rhs: Tag) -> Bool
|
||||
{
|
||||
let this = lhs
|
||||
let o = rhs
|
||||
if (this === o) {return true;}
|
||||
if (type(of:this) != type(of:o)) {return false;}
|
||||
|
||||
let tag : Tag = o ;
|
||||
|
||||
if (lhs._tagName != tag._tagName) {return false;}
|
||||
if (lhs._canContainBlock != tag._canContainBlock) {return false;}
|
||||
if (lhs._canContainInline != tag._canContainInline) {return false;}
|
||||
if (lhs._empty != tag._empty) {return false;}
|
||||
if (lhs._formatAsBlock != tag._formatAsBlock) {return false;}
|
||||
if (lhs._isBlock != tag._isBlock) {return false;}
|
||||
if (lhs._preserveWhitespace != tag._preserveWhitespace) {return false;}
|
||||
if (lhs._selfClosing != tag._selfClosing) {return false;}
|
||||
if (lhs._formList != tag._formList) {return false;}
|
||||
return lhs._formSubmit == tag._formSubmit;
|
||||
}
|
||||
|
||||
public func equals(_ tag : Tag)->Bool
|
||||
{
|
||||
return self == tag
|
||||
}
|
||||
|
||||
/// The hash value.
|
||||
///
|
||||
/// Hash values are not guaranteed to be equal across different executions of
|
||||
/// your program. Do not save hash values to use during a future execution.
|
||||
public var hashValue: Int
|
||||
{
|
||||
var result : Int = _tagName.hashValue
|
||||
result = Int.addWithOverflow(Int.multiplyWithOverflow(31, result).0, _isBlock ? 1 : 0).0
|
||||
result = Int.addWithOverflow(Int.multiplyWithOverflow(31, result).0, _formatAsBlock ? 1 : 0).0
|
||||
result = Int.addWithOverflow(Int.multiplyWithOverflow(31, result).0, _canContainBlock ? 1 : 0).0
|
||||
result = Int.addWithOverflow(Int.multiplyWithOverflow(31, result).0, _canContainInline ? 1 : 0).0
|
||||
result = Int.addWithOverflow(Int.multiplyWithOverflow(31, result).0, _empty ? 1 : 0).0
|
||||
result = Int.addWithOverflow(Int.multiplyWithOverflow(31, result).0, _selfClosing ? 1 : 0).0
|
||||
result = Int.addWithOverflow(Int.multiplyWithOverflow(31, result).0, _preserveWhitespace ? 1 : 0).0
|
||||
result = Int.addWithOverflow(Int.multiplyWithOverflow(31, result).0, _formList ? 1 : 0).0
|
||||
result = Int.addWithOverflow(Int.multiplyWithOverflow(31, result).0, _formSubmit ? 1 : 0).0
|
||||
return result
|
||||
}
|
||||
|
||||
|
||||
open func toString()->String {
|
||||
return _tagName;
|
||||
}
|
||||
|
||||
// internal static initialisers:
|
||||
// prepped from http://www.w3.org/TR/REC-html40/sgml/dtd.html and other sources
|
||||
private static let blockTags : [String] = [
|
||||
"html", "head", "body", "frameset", "script", "noscript", "style", "meta", "link", "title", "frame",
|
||||
"noframes", "section", "nav", "aside", "hgroup", "header", "footer", "p", "h1", "h2", "h3", "h4", "h5", "h6",
|
||||
"ul", "ol", "pre", "div", "blockquote", "hr", "address", "figure", "figcaption", "form", "fieldset", "ins",
|
||||
"del", "s", "dl", "dt", "dd", "li", "table", "caption", "thead", "tfoot", "tbody", "colgroup", "col", "tr", "th",
|
||||
"td", "video", "audio", "canvas", "details", "menu", "plaintext", "template", "article", "main",
|
||||
"svg", "math"
|
||||
]
|
||||
private static let inlineTags : [String] = [
|
||||
"object", "base", "font", "tt", "i", "b", "u", "big", "small", "em", "strong", "dfn", "code", "samp", "kbd",
|
||||
"var", "cite", "abbr", "time", "acronym", "mark", "ruby", "rt", "rp", "a", "img", "br", "wbr", "map", "q",
|
||||
"sub", "sup", "bdo", "iframe", "embed", "span", "input", "select", "textarea", "label", "button", "optgroup",
|
||||
"option", "legend", "datalist", "keygen", "output", "progress", "meter", "area", "param", "source", "track",
|
||||
"summary", "command", "device", "area", "basefont", "bgsound", "menuitem", "param", "source", "track",
|
||||
"data", "bdi"
|
||||
]
|
||||
private static let emptyTags : [String] = [
|
||||
"meta", "link", "base", "frame", "img", "br", "wbr", "embed", "hr", "input", "keygen", "col", "command",
|
||||
"device", "area", "basefont", "bgsound", "menuitem", "param", "source", "track"
|
||||
]
|
||||
private static let formatAsInlineTags : [String] = [
|
||||
"title", "a", "p", "h1", "h2", "h3", "h4", "h5", "h6", "pre", "address", "li", "th", "td", "script", "style",
|
||||
"ins", "del", "s"
|
||||
]
|
||||
private static let preserveWhitespaceTags : [String] = [
|
||||
"pre", "plaintext", "title", "textarea"
|
||||
// script is not here as it is a data node, which always preserve whitespace
|
||||
]
|
||||
// todo: I think we just need submit tags, and can scrub listed
|
||||
private static let formListedTags : [String] = [
|
||||
"button", "fieldset", "input", "keygen", "object", "output", "select", "textarea"
|
||||
]
|
||||
private static let formSubmitTags : [String] = [
|
||||
"input", "keygen", "object", "select", "textarea"
|
||||
]
|
||||
|
||||
|
||||
static private func initializeMaps()throws->Dictionary<String, Tag>
|
||||
{
|
||||
var dict = Dictionary<String, Tag>()
|
||||
|
||||
// creates
|
||||
for tagName in blockTags {
|
||||
let tag = Tag(tagName);
|
||||
dict[tag._tagName] = tag;
|
||||
}
|
||||
for tagName in inlineTags {
|
||||
let tag = Tag(tagName);
|
||||
tag._isBlock = false;
|
||||
tag._canContainBlock = false;
|
||||
tag._formatAsBlock = false;
|
||||
dict[tag._tagName] = tag;
|
||||
}
|
||||
|
||||
// mods:
|
||||
for tagName in emptyTags {
|
||||
let tag = dict[tagName]
|
||||
try Validate.notNull(obj: tag);
|
||||
tag?._canContainBlock = false;
|
||||
tag?._canContainInline = false;
|
||||
tag?._empty = true;
|
||||
}
|
||||
|
||||
for tagName in formatAsInlineTags {
|
||||
let tag = dict[tagName]
|
||||
try Validate.notNull(obj: tag);
|
||||
tag?._formatAsBlock = false;
|
||||
}
|
||||
|
||||
for tagName in preserveWhitespaceTags {
|
||||
let tag = dict[tagName]
|
||||
try Validate.notNull(obj: tag);
|
||||
tag?._preserveWhitespace = true;
|
||||
}
|
||||
|
||||
for tagName in formListedTags {
|
||||
let tag = dict[tagName]
|
||||
try Validate.notNull(obj: tag);
|
||||
tag?._formList = true;
|
||||
}
|
||||
|
||||
for tagName in formSubmitTags {
|
||||
let tag = dict[tagName]
|
||||
try Validate.notNull(obj: tag);
|
||||
tag?._formSubmit = true;
|
||||
}
|
||||
return dict
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,387 @@
|
|||
//
|
||||
// Token.swift
|
||||
// SwiftSoup
|
||||
//
|
||||
// Created by Nabil Chatbi on 18/10/16.
|
||||
// Copyright © 2016 Nabil Chatbi.. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
open class Token
|
||||
{
|
||||
var type : TokenType = TokenType.Doctype
|
||||
|
||||
private init() {
|
||||
}
|
||||
|
||||
func tokenType()->String
|
||||
{
|
||||
return String(describing: type(of: self))
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the data represent by this token, for reuse. Prevents the need to create transfer objects for every
|
||||
* piece of data, which immediately get GCed.
|
||||
*/
|
||||
@discardableResult
|
||||
public func reset()->Token
|
||||
{
|
||||
preconditionFailure("This method must be overridden")
|
||||
}
|
||||
|
||||
static func reset(_ sb: StringBuilder) {
|
||||
sb.clear()
|
||||
}
|
||||
|
||||
open func toString()throws->String {
|
||||
return String(describing: type(of: self))
|
||||
}
|
||||
|
||||
final class Doctype : Token {
|
||||
let name: StringBuilder = StringBuilder();
|
||||
let publicIdentifier: StringBuilder = StringBuilder();
|
||||
let systemIdentifier : StringBuilder = StringBuilder();
|
||||
var forceQuirks : Bool = false;
|
||||
|
||||
override init() {
|
||||
super.init()
|
||||
type = TokenType.Doctype;
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
override func reset()->Token {
|
||||
Token.reset(name);
|
||||
Token.reset(publicIdentifier);
|
||||
Token.reset(systemIdentifier);
|
||||
forceQuirks = false;
|
||||
return self;
|
||||
}
|
||||
|
||||
func getName()->String {
|
||||
return name.toString();
|
||||
}
|
||||
|
||||
func getPublicIdentifier()->String {
|
||||
return publicIdentifier.toString();
|
||||
}
|
||||
|
||||
open func getSystemIdentifier()->String {
|
||||
return systemIdentifier.toString();
|
||||
}
|
||||
|
||||
open func isForceQuirks()->Bool {
|
||||
return forceQuirks;
|
||||
}
|
||||
}
|
||||
|
||||
class Tag : Token {
|
||||
public var _tagName: String?;
|
||||
public var _normalName: String?; // lc version of tag name, for case insensitive tree build
|
||||
private var _pendingAttributeName: String?; // attribute names are generally caught in one hop, not accumulated
|
||||
private let _pendingAttributeValue : StringBuilder = StringBuilder(); // but values are accumulated, from e.g. & in hrefs
|
||||
private var _pendingAttributeValueS: String?; // try to get attr vals in one shot, vs Builder
|
||||
private var _hasEmptyAttributeValue : Bool = false; // distinguish boolean attribute from empty string value
|
||||
private var _hasPendingAttributeValue: Bool = false;
|
||||
public var _selfClosing : Bool = false;
|
||||
// start tags get attributes on construction. End tags get attributes on first new attribute (but only for parser convenience, not used).
|
||||
public var _attributes : Attributes = Attributes();
|
||||
|
||||
override init() {
|
||||
super.init()
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
override func reset()->Tag {
|
||||
_tagName = nil;
|
||||
_normalName = nil;
|
||||
_pendingAttributeName = nil;
|
||||
Token.reset(_pendingAttributeValue);
|
||||
_pendingAttributeValueS = nil;
|
||||
_hasEmptyAttributeValue = false;
|
||||
_hasPendingAttributeValue = false;
|
||||
_selfClosing = false;
|
||||
_attributes = Attributes();
|
||||
return self;
|
||||
}
|
||||
|
||||
func newAttribute()throws {
|
||||
// if (_attributes == nil){
|
||||
// _attributes = Attributes();
|
||||
// }
|
||||
|
||||
if (_pendingAttributeName != nil) {
|
||||
var attribute : Attribute
|
||||
if (_hasPendingAttributeValue){
|
||||
attribute = try Attribute(key: _pendingAttributeName!,value: _pendingAttributeValue.length > 0 ? _pendingAttributeValue.toString() : _pendingAttributeValueS!);
|
||||
}else if (_hasEmptyAttributeValue){
|
||||
attribute = try Attribute(key: _pendingAttributeName!, value: "");
|
||||
}else{
|
||||
attribute = try BooleanAttribute(key: _pendingAttributeName!);
|
||||
}
|
||||
_attributes.put(attribute: attribute);
|
||||
}
|
||||
_pendingAttributeName = nil;
|
||||
_hasEmptyAttributeValue = false;
|
||||
_hasPendingAttributeValue = false;
|
||||
Token.reset(_pendingAttributeValue);
|
||||
_pendingAttributeValueS = nil;
|
||||
}
|
||||
|
||||
func finaliseTag()throws {
|
||||
// finalises for emit
|
||||
if (_pendingAttributeName != nil) {
|
||||
// todo: check if attribute name exists; if so, drop and error
|
||||
try newAttribute();
|
||||
}
|
||||
}
|
||||
|
||||
func name()throws->String { // preserves case, for input into Tag.valueOf (which may drop case)
|
||||
try Validate.isFalse(val: _tagName == nil || _tagName!.unicodeScalars.count == 0);
|
||||
return _tagName!;
|
||||
}
|
||||
|
||||
func normalName()->String? { // loses case, used in tree building for working out where in tree it should go
|
||||
return _normalName;
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func name(_ name: String)->Tag {
|
||||
_tagName = name;
|
||||
_normalName = name.lowercased();
|
||||
return self;
|
||||
}
|
||||
|
||||
func isSelfClosing()->Bool {
|
||||
return _selfClosing;
|
||||
}
|
||||
|
||||
func getAttributes()->Attributes {
|
||||
return _attributes;
|
||||
}
|
||||
|
||||
// these appenders are rarely hit in not null state-- caused by null chars.
|
||||
func appendTagName(_ append: String) {
|
||||
_tagName = _tagName == nil ? append : _tagName!.appending(append)
|
||||
_normalName = _tagName?.lowercased();
|
||||
}
|
||||
|
||||
func appendTagName(_ append : UnicodeScalar) {
|
||||
appendTagName("\(append)");
|
||||
}
|
||||
|
||||
func appendAttributeName(_ append: String) {
|
||||
_pendingAttributeName = _pendingAttributeName == nil ? append : _pendingAttributeName?.appending(append);
|
||||
}
|
||||
|
||||
func appendAttributeName(_ append: UnicodeScalar) {
|
||||
appendAttributeName("\(append)")
|
||||
}
|
||||
|
||||
func appendAttributeValue(_ append: String) {
|
||||
ensureAttributeValue();
|
||||
if (_pendingAttributeValue.length == 0) {
|
||||
_pendingAttributeValueS = append;
|
||||
} else {
|
||||
_pendingAttributeValue.append(append);
|
||||
}
|
||||
}
|
||||
|
||||
func appendAttributeValue(_ append: UnicodeScalar) {
|
||||
ensureAttributeValue();
|
||||
_pendingAttributeValue.appendCodePoint(append)
|
||||
}
|
||||
|
||||
func appendAttributeValue(_ append: [UnicodeScalar]) {
|
||||
ensureAttributeValue();
|
||||
_pendingAttributeValue.appendCodePoints(append);
|
||||
}
|
||||
|
||||
func appendAttributeValue(_ appendCodepoints: [Int]) {
|
||||
ensureAttributeValue();
|
||||
for codepoint in appendCodepoints {
|
||||
_pendingAttributeValue.appendCodePoint(UnicodeScalar(codepoint)!);
|
||||
}
|
||||
}
|
||||
|
||||
func setEmptyAttributeValue() {
|
||||
_hasEmptyAttributeValue = true;
|
||||
}
|
||||
|
||||
private func ensureAttributeValue() {
|
||||
_hasPendingAttributeValue = true;
|
||||
// if on second hit, we'll need to move to the builder
|
||||
if (_pendingAttributeValueS != nil) {
|
||||
_pendingAttributeValue.append(_pendingAttributeValueS!);
|
||||
_pendingAttributeValueS = nil;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final class StartTag : Tag {
|
||||
override init() {
|
||||
super.init();
|
||||
_attributes = Attributes()
|
||||
type = TokenType.StartTag
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
override func reset()->Tag {
|
||||
super.reset()
|
||||
_attributes = Attributes();
|
||||
// todo - would prefer these to be null, but need to check Element assertions
|
||||
return self;
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func nameAttr(_ name: String, _ attributes: Attributes)->StartTag {
|
||||
self._tagName = name;
|
||||
self._attributes = attributes;
|
||||
_normalName = _tagName?.lowercased();
|
||||
return self;
|
||||
}
|
||||
|
||||
open override func toString()throws->String {
|
||||
if (_attributes.size() > 0){
|
||||
return try "<" + (name()) + " " + (_attributes.toString()) + ">";
|
||||
}else{
|
||||
return try "<" + name() + ">";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final class EndTag : Tag{
|
||||
override init() {
|
||||
super.init();
|
||||
type = TokenType.EndTag;
|
||||
}
|
||||
|
||||
|
||||
open override func toString()throws->String {
|
||||
return "</" + (try name()) + ">";
|
||||
}
|
||||
}
|
||||
|
||||
final class Comment : Token {
|
||||
let data : StringBuilder = StringBuilder();
|
||||
var bogus : Bool = false;
|
||||
|
||||
@discardableResult
|
||||
override func reset()->Token {
|
||||
Token.reset(data);
|
||||
bogus = false;
|
||||
return self;
|
||||
}
|
||||
|
||||
override init() {
|
||||
super.init()
|
||||
type = TokenType.Comment;
|
||||
}
|
||||
|
||||
func getData()->String {
|
||||
return data.toString();
|
||||
}
|
||||
|
||||
|
||||
open override func toString()throws->String {
|
||||
return "<!--" + getData() + "-->";
|
||||
}
|
||||
}
|
||||
|
||||
final class Char : Token {
|
||||
public var data : String?;
|
||||
|
||||
override init() {
|
||||
super.init()
|
||||
type = TokenType.Char
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
override func reset()->Token {
|
||||
data = nil;
|
||||
return self;
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func data(_ data: String)->Char {
|
||||
self.data = data;
|
||||
return self;
|
||||
}
|
||||
|
||||
func getData()->String? {
|
||||
return data;
|
||||
}
|
||||
|
||||
|
||||
open override func toString()throws->String {
|
||||
try Validate.notNull(obj: data)
|
||||
return getData()!;
|
||||
}
|
||||
}
|
||||
|
||||
final class EOF : Token {
|
||||
override init() {
|
||||
super.init()
|
||||
type = Token.TokenType.EOF;
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
override func reset()->Token {
|
||||
return self;
|
||||
}
|
||||
}
|
||||
|
||||
func isDoctype()->Bool {
|
||||
return type == TokenType.Doctype;
|
||||
}
|
||||
|
||||
func asDoctype()->Doctype {
|
||||
return self as! Doctype;
|
||||
}
|
||||
|
||||
func isStartTag()->Bool {
|
||||
return type == TokenType.StartTag;
|
||||
}
|
||||
|
||||
func asStartTag()->StartTag {
|
||||
return self as! StartTag;
|
||||
}
|
||||
|
||||
func isEndTag()->Bool {
|
||||
return type == TokenType.EndTag;
|
||||
}
|
||||
|
||||
func asEndTag()->EndTag {
|
||||
return self as! EndTag;
|
||||
}
|
||||
|
||||
func isComment()->Bool {
|
||||
return type == TokenType.Comment;
|
||||
}
|
||||
|
||||
func asComment()->Comment {
|
||||
return self as! Comment;
|
||||
}
|
||||
|
||||
func isCharacter()->Bool {
|
||||
return type == TokenType.Char;
|
||||
}
|
||||
|
||||
func asCharacter()->Char {
|
||||
return self as! Char;
|
||||
}
|
||||
|
||||
func isEOF()->Bool {
|
||||
return type == TokenType.EOF;
|
||||
}
|
||||
|
||||
|
||||
public enum TokenType {
|
||||
case Doctype
|
||||
case StartTag
|
||||
case EndTag
|
||||
case Comment
|
||||
case Char
|
||||
case EOF
|
||||
}
|
||||
}
|
|
@ -0,0 +1,434 @@
|
|||
//
|
||||
// TokenQueue.swift
|
||||
// SwiftSoup
|
||||
//
|
||||
// Created by Nabil Chatbi on 13/10/16.
|
||||
// Copyright © 2016 Nabil Chatbi.. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
open class TokenQueue
|
||||
{
|
||||
private var queue : String;
|
||||
private var pos : Int = 0;
|
||||
|
||||
private static let ESC : Character = "\\"; // escape char for chomp balanced.
|
||||
|
||||
/**
|
||||
Create a new TokenQueue.
|
||||
@param data string of data to back queue.
|
||||
*/
|
||||
public init (_ data: String) {
|
||||
queue = data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the queue empty?
|
||||
* @return true if no data left in queue.
|
||||
*/
|
||||
open func isEmpty()->Bool {
|
||||
return remainingLength() == 0;
|
||||
}
|
||||
|
||||
private func remainingLength()->Int {
|
||||
return queue.characters.count - pos;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves but does not remove the first character from the queue.
|
||||
* @return First character, or 0 if empty.
|
||||
*/
|
||||
open func peek()-> Character {
|
||||
return isEmpty() ? Character(UnicodeScalar(0)) : queue[pos];
|
||||
}
|
||||
|
||||
/**
|
||||
Add a character to the start of the queue (will be the next character retrieved).
|
||||
@param c character to add
|
||||
*/
|
||||
open func addFirst(_ c: Character) {
|
||||
addFirst(String(c));
|
||||
}
|
||||
|
||||
/**
|
||||
Add a string to the start of the queue.
|
||||
@param seq string to add.
|
||||
*/
|
||||
open func addFirst(_ seq: String) {
|
||||
// not very performant, but an edge case
|
||||
queue = seq + queue.substring(pos);
|
||||
pos = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests if the next characters on the queue match the sequence. Case insensitive.
|
||||
* @param seq String to check queue for.
|
||||
* @return true if the next characters match.
|
||||
*/
|
||||
open func matches(_ seq: String)->Bool {
|
||||
return queue.regionMatches(true, pos, seq, 0, seq.characters.count);
|
||||
}
|
||||
|
||||
/**
|
||||
* Case sensitive match test.
|
||||
* @param seq string to case sensitively check for
|
||||
* @return true if matched, false if not
|
||||
*/
|
||||
open func matchesCS(_ seq: String)->Bool {
|
||||
return queue.startsWith(seq, pos);
|
||||
}
|
||||
|
||||
/**
|
||||
Tests if the next characters match any of the sequences. Case insensitive.
|
||||
@param seq list of strings to case insensitively check for
|
||||
@return true of any matched, false if none did
|
||||
*/
|
||||
open func matchesAny(_ seq:[String])->Bool {
|
||||
for s in seq {
|
||||
if (matches(s)){
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
open func matchesAny(_ seq: String...)->Bool {
|
||||
return matchesAny(seq)
|
||||
}
|
||||
|
||||
open func matchesAny(_ seq: Character...)->Bool {
|
||||
if (isEmpty()){
|
||||
return false;
|
||||
}
|
||||
|
||||
for c in seq {
|
||||
if (queue[pos] as Character == c){
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
open func matchesStartTag()->Bool {
|
||||
// micro opt for matching "<x"
|
||||
return (remainingLength() >= 2 && queue[pos] as Character == "<" && Character.isLetter(queue.charAt(pos+1)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests if the queue matches the sequence (as with match), and if they do, removes the matched string from the
|
||||
* queue.
|
||||
* @param seq String to search for, and if found, remove from queue.
|
||||
* @return true if found and removed, false if not found.
|
||||
*/
|
||||
@discardableResult
|
||||
open func matchChomp(_ seq: String)->Bool {
|
||||
if (matches(seq)) {
|
||||
pos += seq.characters.count;
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Tests if queue starts with a whitespace character.
|
||||
@return if starts with whitespace
|
||||
*/
|
||||
open func matchesWhitespace()->Bool {
|
||||
return !isEmpty() && StringUtil.isWhitespace(queue.charAt(pos));
|
||||
}
|
||||
|
||||
/**
|
||||
Test if the queue matches a word character (letter or digit).
|
||||
@return if matches a word character
|
||||
*/
|
||||
open func matchesWord()->Bool {
|
||||
return !isEmpty() && (Character.isLetterOrDigit(queue.charAt(pos)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Drops the next character off the queue.
|
||||
*/
|
||||
open func advance() {
|
||||
|
||||
if (!isEmpty()) {pos+=1}
|
||||
}
|
||||
|
||||
/**
|
||||
* Consume one character off queue.
|
||||
* @return first character on queue.
|
||||
*/
|
||||
open func consume()->Character {
|
||||
let i = pos
|
||||
pos+=1
|
||||
return queue.charAt(i)
|
||||
}
|
||||
|
||||
/**
|
||||
* Consumes the supplied sequence of the queue. If the queue does not start with the supplied sequence, will
|
||||
* throw an illegal state exception -- but you should be running match() against that condition.
|
||||
<p>
|
||||
Case insensitive.
|
||||
* @param seq sequence to remove from head of queue.
|
||||
*/
|
||||
open func consume(_ seq: String)throws {
|
||||
if (!matches(seq)){
|
||||
//throw new IllegalStateException("Queue did not match expected sequence");
|
||||
throw Exception.Error(type: ExceptionType.IllegalArgumentException, Message: "Queue did not match expected sequence")
|
||||
}
|
||||
let len = seq.characters.count;
|
||||
if (len > remainingLength()){
|
||||
//throw new IllegalStateException("Queue not long enough to consume sequence");
|
||||
throw Exception.Error(type: ExceptionType.IllegalArgumentException, Message: "Queue not long enough to consume sequence")
|
||||
}
|
||||
|
||||
pos += len;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pulls a string off the queue, up to but exclusive of the match sequence, or to the queue running out.
|
||||
* @param seq String to end on (and not include in return, but leave on queue). <b>Case sensitive.</b>
|
||||
* @return The matched data consumed from queue.
|
||||
*/
|
||||
@discardableResult
|
||||
open func consumeTo(_ seq: String)->String {
|
||||
let offset = queue.indexOf(seq, pos);
|
||||
if (offset != -1) {
|
||||
let consumed = queue.substring(pos, offset-pos);
|
||||
pos += consumed.characters.count;
|
||||
return consumed;
|
||||
} else {
|
||||
//return remainder();
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
open func consumeToIgnoreCase(_ seq: String)->String {
|
||||
let start = pos;
|
||||
let first = seq.substring(0, 1);
|
||||
let canScan = first.lowercased() == first.uppercased() // if first is not cased, use index of
|
||||
while (!isEmpty()) {
|
||||
if (matches(seq)){
|
||||
break
|
||||
}
|
||||
if (canScan)
|
||||
{
|
||||
let skip = queue.indexOf(first, pos) - pos;
|
||||
if (skip == 0){ // this char is the skip char, but not match, so force advance of pos
|
||||
pos+=1;
|
||||
}else if (skip < 0){ // no chance of finding, grab to end
|
||||
pos = queue.characters.count;
|
||||
}else{
|
||||
pos += skip;
|
||||
}
|
||||
} else{
|
||||
pos+=1;
|
||||
}
|
||||
}
|
||||
|
||||
return queue.substring(start, pos-start);
|
||||
}
|
||||
|
||||
/**
|
||||
Consumes to the first sequence provided, or to the end of the queue. Leaves the terminator on the queue.
|
||||
@param seq any number of terminators to consume to. <b>Case insensitive.</b>
|
||||
@return consumed string
|
||||
*/
|
||||
// todo: method name. not good that consumeTo cares for case, and consume to any doesn't. And the only use for this
|
||||
// is is a case sensitive time...
|
||||
open func consumeToAny(_ seq: String...)->String {
|
||||
return consumeToAny(seq)
|
||||
}
|
||||
open func consumeToAny(_ seq: [String])->String {
|
||||
let start = pos;
|
||||
while (!isEmpty() && !matchesAny(seq)) {
|
||||
pos+=1;
|
||||
}
|
||||
|
||||
return queue.substring(start, pos-start);
|
||||
}
|
||||
/**
|
||||
* Pulls a string off the queue (like consumeTo), and then pulls off the matched string (but does not return it).
|
||||
* <p>
|
||||
* If the queue runs out of characters before finding the seq, will return as much as it can (and queue will go
|
||||
* isEmpty() == true).
|
||||
* @param seq String to match up to, and not include in return, and to pull off queue. <b>Case sensitive.</b>
|
||||
* @return Data matched from queue.
|
||||
*/
|
||||
open func chompTo(_ seq: String)->String {
|
||||
let data = consumeTo(seq);
|
||||
matchChomp(seq);
|
||||
return data;
|
||||
}
|
||||
|
||||
open func chompToIgnoreCase(_ seq: String)->String {
|
||||
let data = consumeToIgnoreCase(seq); // case insensitive scan
|
||||
matchChomp(seq);
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pulls a balanced string off the queue. E.g. if queue is "(one (two) three) four", (,) will return "one (two) three",
|
||||
* and leave " four" on the queue. Unbalanced openers and closers can quoted (with ' or ") or escaped (with \). Those escapes will be left
|
||||
* in the returned string, which is suitable for regexes (where we need to preserve the escape), but unsuitable for
|
||||
* contains text strings; use unescape for that.
|
||||
* @param open opener
|
||||
* @param close closer
|
||||
* @return data matched from the queue
|
||||
*/
|
||||
open func chompBalanced(_ open:Character, _ close: Character)->String {
|
||||
var start = -1;
|
||||
var end = -1;
|
||||
var depth = 0;
|
||||
var last : Character = Character(UnicodeScalar(0));
|
||||
var inQuote = false;
|
||||
|
||||
repeat {
|
||||
if (isEmpty()){break;}
|
||||
let c = consume();
|
||||
if (last.unicodeScalar.value == 0 || last != TokenQueue.ESC) {
|
||||
if ((c=="'" || c=="\"") && c != open){
|
||||
inQuote = !inQuote;
|
||||
}
|
||||
if (inQuote){
|
||||
continue;
|
||||
}
|
||||
if (c==open) {
|
||||
depth+=1;
|
||||
if (start == -1){
|
||||
start = pos;
|
||||
}
|
||||
}
|
||||
else if (c==close){
|
||||
depth-=1;
|
||||
}
|
||||
}
|
||||
|
||||
if (depth > 0 && last.unicodeScalar.value != 0){
|
||||
end = pos; // don't include the outer match pair in the return
|
||||
}
|
||||
last = c;
|
||||
} while (depth > 0);
|
||||
return (end >= 0) ? queue.substring(start, end-start) : "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Unescaped a \ escaped string.
|
||||
* @param in backslash escaped string
|
||||
* @return unescaped string
|
||||
*/
|
||||
open static func unescape(_ input: String)->String {
|
||||
let out = StringBuilder();
|
||||
var last = Character(UnicodeScalar(0))
|
||||
for c in input.characters
|
||||
{
|
||||
if (c == ESC) {
|
||||
if (last.unicodeScalar.value != 0 && last == TokenQueue.ESC){
|
||||
out.append(c);
|
||||
}
|
||||
}
|
||||
else{
|
||||
out.append(c);
|
||||
}
|
||||
last = c;
|
||||
}
|
||||
return out.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Pulls the next run of whitespace characters of the queue.
|
||||
* @return Whether consuming whitespace or not
|
||||
*/
|
||||
@discardableResult
|
||||
open func consumeWhitespace()->Bool {
|
||||
var seen = false;
|
||||
while (matchesWhitespace()) {
|
||||
pos+=1;
|
||||
seen = true;
|
||||
}
|
||||
return seen;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the next run of word type (letter or digit) off the queue.
|
||||
* @return String of word characters from queue, or empty string if none.
|
||||
*/
|
||||
@discardableResult
|
||||
open func consumeWord()->String {
|
||||
let start = pos;
|
||||
while (matchesWord()){
|
||||
pos+=1;
|
||||
}
|
||||
return queue.substring(start, pos-start);
|
||||
}
|
||||
|
||||
/**
|
||||
* Consume an tag name off the queue (word or :, _, -)
|
||||
*
|
||||
* @return tag name
|
||||
*/
|
||||
open func consumeTagName()->String {
|
||||
let start = pos;
|
||||
while (!isEmpty() && (matchesWord() || matchesAny(":", "_", "-"))){
|
||||
pos+=1;
|
||||
}
|
||||
|
||||
return queue.substring(start, pos-start);
|
||||
}
|
||||
|
||||
/**
|
||||
* Consume a CSS element selector (tag name, but | instead of : for namespaces (or *| for wildcard namespace), to not conflict with :pseudo selects).
|
||||
*
|
||||
* @return tag name
|
||||
*/
|
||||
open func consumeElementSelector()->String {
|
||||
let start = pos;
|
||||
while (!isEmpty() && (matchesWord() || matchesAny("*|","|", "_", "-"))){
|
||||
pos+=1;
|
||||
}
|
||||
|
||||
return queue.substring(start, pos-start);
|
||||
}
|
||||
|
||||
/**
|
||||
Consume a CSS identifier (ID or class) off the queue (letter, digit, -, _)
|
||||
http://www.w3.org/TR/CSS2/syndata.html#value-def-identifier
|
||||
@return identifier
|
||||
*/
|
||||
open func consumeCssIdentifier()->String {
|
||||
let start = pos;
|
||||
while (!isEmpty() && (matchesWord() || matchesAny("-", "_"))){
|
||||
pos+=1;
|
||||
}
|
||||
|
||||
return queue.substring(start, pos-start);
|
||||
}
|
||||
|
||||
/**
|
||||
Consume an attribute key off the queue (letter, digit, -, _, :")
|
||||
@return attribute key
|
||||
*/
|
||||
open func consumeAttributeKey()->String {
|
||||
let start = pos;
|
||||
while (!isEmpty() && (matchesWord() || matchesAny("-", "_", ":"))){
|
||||
pos+=1;
|
||||
}
|
||||
|
||||
return queue.substring(start, pos-start);
|
||||
}
|
||||
|
||||
/**
|
||||
Consume and return whatever is left on the queue.
|
||||
@return remained of queue.
|
||||
*/
|
||||
open func remainder()->String {
|
||||
let remainder = queue.substring(pos, queue.characters.count-pos);
|
||||
pos = queue.characters.count;
|
||||
return remainder;
|
||||
}
|
||||
|
||||
|
||||
open func toString()->String {
|
||||
return queue.substring(pos);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,320 @@
|
|||
//
|
||||
// Tokeniser.swift
|
||||
// SwiftSoup
|
||||
//
|
||||
// Created by Nabil Chatbi on 19/10/16.
|
||||
// Copyright © 2016 Nabil Chatbi.. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
final class Tokeniser
|
||||
{
|
||||
static let replacementChar : UnicodeScalar = "\u{FFFD}" // replaces null character
|
||||
private static let notCharRefCharsSorted : [UnicodeScalar] = ["\t", "\n", "\r",UnicodeScalar.BackslashF, " ", "<", "&"].sorted()
|
||||
|
||||
private let reader : CharacterReader; // html input
|
||||
private let errors : ParseErrorList?; // errors found while tokenising
|
||||
|
||||
private var state: TokeniserState = TokeniserState.Data; // current tokenisation state
|
||||
private var emitPending: Token? ; // the token we are about to emit on next read
|
||||
private var isEmitPending : Bool = false;
|
||||
private var charsString : String? = nil; // characters pending an emit. Will fall to charsBuilder if more than one
|
||||
private let charsBuilder : StringBuilder = StringBuilder(1024); // buffers characters to output as one token, if more than one emit per read
|
||||
let dataBuffer : StringBuilder = StringBuilder(1024); // buffers data looking for </script>
|
||||
|
||||
var tagPending : Token.Tag = Token.Tag() // tag we are building up
|
||||
let startPending : Token.StartTag = Token.StartTag();
|
||||
let endPending: Token.EndTag = Token.EndTag();
|
||||
let charPending: Token.Char = Token.Char();
|
||||
let doctypePending: Token.Doctype = Token.Doctype(); // doctype building up
|
||||
let commentPending: Token.Comment = Token.Comment(); // comment building up
|
||||
private var lastStartTag: String? // the last start tag emitted, to test appropriate end tag
|
||||
private var selfClosingFlagAcknowledged: Bool = true;
|
||||
|
||||
|
||||
init(_ reader: CharacterReader, _ errors: ParseErrorList?) {
|
||||
self.reader = reader;
|
||||
self.errors = errors;
|
||||
}
|
||||
|
||||
func read()throws->Token {
|
||||
if (!selfClosingFlagAcknowledged) {
|
||||
error("Self closing flag not acknowledged");
|
||||
selfClosingFlagAcknowledged = true;
|
||||
}
|
||||
|
||||
while (!isEmitPending){
|
||||
try state.read(self, reader);
|
||||
}
|
||||
|
||||
// if emit is pending, a non-character token was found: return any chars in buffer, and leave token for next read:
|
||||
if (charsBuilder.length > 0) {
|
||||
let str: String = charsBuilder.toString();
|
||||
charsBuilder.clear()
|
||||
charsString = nil
|
||||
return charPending.data(str)
|
||||
} else if (charsString != nil) {
|
||||
let token : Token = charPending.data(charsString!);
|
||||
charsString = nil;
|
||||
return token;
|
||||
} else {
|
||||
isEmitPending = false;
|
||||
return emitPending!;
|
||||
}
|
||||
}
|
||||
|
||||
func emit(_ token: Token)throws {
|
||||
try Validate.isFalse(val: isEmitPending, msg: "There is an unread token pending!");
|
||||
|
||||
emitPending = token;
|
||||
isEmitPending = true;
|
||||
|
||||
if (token.type == Token.TokenType.StartTag) {
|
||||
let startTag : Token.StartTag = token as! Token.StartTag;
|
||||
lastStartTag = startTag._tagName!;
|
||||
if (startTag._selfClosing){
|
||||
selfClosingFlagAcknowledged = false;
|
||||
}
|
||||
} else if (token.type == Token.TokenType.EndTag) {
|
||||
let endTag : Token.EndTag = token as! Token.EndTag;
|
||||
if (endTag._attributes.size() != 0){
|
||||
error("Attributes incorrectly present on end tag");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func emit(_ str: String ) {
|
||||
// buffer strings up until last string token found, to emit only one token for a run of character refs etc.
|
||||
// does not set isEmitPending; read checks that
|
||||
if (charsString == nil) {
|
||||
charsString = str;
|
||||
}
|
||||
else {
|
||||
if (charsBuilder.length == 0) { // switching to string builder as more than one emit before read
|
||||
charsBuilder.append(charsString!);
|
||||
}
|
||||
charsBuilder.append(str);
|
||||
}
|
||||
}
|
||||
|
||||
func emit(_ chars: [UnicodeScalar]) {
|
||||
emit(String(chars.map{Character($0)}))
|
||||
}
|
||||
|
||||
// func emit(_ codepoints: [Int]) {
|
||||
// emit(String(codepoints, 0, codepoints.length));
|
||||
// }
|
||||
|
||||
func emit(_ c: UnicodeScalar) {
|
||||
emit(String(c));
|
||||
}
|
||||
|
||||
func getState()->TokeniserState {
|
||||
return state;
|
||||
}
|
||||
|
||||
func transition(_ state: TokeniserState) {
|
||||
self.state = state;
|
||||
}
|
||||
|
||||
func advanceTransition(_ state: TokeniserState) {
|
||||
reader.advance();
|
||||
self.state = state;
|
||||
}
|
||||
|
||||
func acknowledgeSelfClosingFlag() {
|
||||
selfClosingFlagAcknowledged = true;
|
||||
}
|
||||
|
||||
private var codepointHolder: [UnicodeScalar] = [UnicodeScalar(0)!]; // holder to not have to keep creating arrays
|
||||
private var multipointHolder: [UnicodeScalar] = [UnicodeScalar(0)!,UnicodeScalar(0)!];
|
||||
|
||||
func consumeCharacterReference(_ additionalAllowedCharacter: UnicodeScalar?, _ inAttribute: Bool)throws->[UnicodeScalar]? {
|
||||
if (reader.isEmpty()){
|
||||
return nil;
|
||||
}
|
||||
if (additionalAllowedCharacter != nil && additionalAllowedCharacter == reader.current()){
|
||||
return nil;
|
||||
}
|
||||
if (reader.matchesAnySorted(Tokeniser.notCharRefCharsSorted)){
|
||||
return nil;
|
||||
}
|
||||
|
||||
var codeRef: [UnicodeScalar] = codepointHolder;
|
||||
reader.markPos();
|
||||
if (reader.matchConsume("#")) { // numbered
|
||||
let isHexMode: Bool = reader.matchConsumeIgnoreCase("X");
|
||||
let numRef: String = isHexMode ? reader.consumeHexSequence() : reader.consumeDigitSequence();
|
||||
if (numRef.unicodeScalars.count == 0) { // didn't match anything
|
||||
characterReferenceError("numeric reference with no numerals");
|
||||
reader.rewindToMark();
|
||||
return nil;
|
||||
}
|
||||
if (!reader.matchConsume(";")){
|
||||
characterReferenceError("missing semicolon"); // missing semi
|
||||
}
|
||||
var charval : Int = -1;
|
||||
|
||||
let base: Int = isHexMode ? 16 : 10;
|
||||
if let num = Int(numRef,radix: base)
|
||||
{
|
||||
charval = num
|
||||
}
|
||||
|
||||
|
||||
if (charval == -1 || (charval >= 0xD800 && charval <= 0xDFFF) || charval > 0x10FFFF) {
|
||||
characterReferenceError("character outside of valid range");
|
||||
codeRef[0] = Tokeniser.replacementChar;
|
||||
return codeRef;
|
||||
} else {
|
||||
// todo: implement number replacement table
|
||||
// todo: check for extra illegal unicode points as parse errors
|
||||
codeRef[0] = UnicodeScalar(charval)!;
|
||||
return codeRef;
|
||||
}
|
||||
} else { // named
|
||||
// get as many letters as possible, and look for matching entities.
|
||||
let nameRef : String = reader.consumeLetterThenDigitSequence();
|
||||
let looksLegit: Bool = reader.matches(";");
|
||||
// found if a base named entity without a ;, or an extended entity with the ;.
|
||||
let found: Bool = (Entities.isBaseNamedEntity(nameRef) || (Entities.isNamedEntity(nameRef) && looksLegit));
|
||||
|
||||
if (!found) {
|
||||
reader.rewindToMark();
|
||||
if (looksLegit){ // named with semicolon
|
||||
characterReferenceError(String(format:"invalid named referenece '%@'", nameRef));
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
if (inAttribute && (reader.matchesLetter() || reader.matchesDigit() || reader.matchesAny("=", "-", "_"))) {
|
||||
// don't want that to match
|
||||
reader.rewindToMark();
|
||||
return nil;
|
||||
}
|
||||
if (!reader.matchConsume(";")){
|
||||
characterReferenceError("missing semicolon"); // missing semi
|
||||
}
|
||||
let numChars: Int = Entities.codepointsForName(nameRef, codepoints: &multipointHolder);
|
||||
if (numChars == 1) {
|
||||
codeRef[0] = multipointHolder[0];
|
||||
return codeRef;
|
||||
} else if (numChars == 2) {
|
||||
return multipointHolder;
|
||||
} else {
|
||||
try Validate.fail(msg: "Unexpected characters returned for " + nameRef);
|
||||
return multipointHolder;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func createTagPending(_ start: Bool)->Token.Tag {
|
||||
tagPending = start ? startPending.reset() : endPending.reset();
|
||||
return tagPending;
|
||||
}
|
||||
|
||||
func emitTagPending()throws {
|
||||
try tagPending.finaliseTag();
|
||||
try emit(tagPending);
|
||||
}
|
||||
|
||||
func createCommentPending() {
|
||||
commentPending.reset();
|
||||
}
|
||||
|
||||
func emitCommentPending()throws {
|
||||
try emit(commentPending);
|
||||
}
|
||||
|
||||
func createDoctypePending() {
|
||||
doctypePending.reset();
|
||||
}
|
||||
|
||||
func emitDoctypePending()throws {
|
||||
try emit(doctypePending);
|
||||
}
|
||||
|
||||
func createTempBuffer() {
|
||||
Token.reset(dataBuffer);
|
||||
}
|
||||
|
||||
func isAppropriateEndTagToken()throws->Bool {
|
||||
if(lastStartTag != nil){
|
||||
let s = try tagPending.name()
|
||||
return s.equalsIgnoreCase(string: lastStartTag!)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func appropriateEndTagName()->String? {
|
||||
if (lastStartTag == nil){
|
||||
return nil;
|
||||
}
|
||||
return lastStartTag;
|
||||
}
|
||||
|
||||
func error(_ state: TokeniserState) {
|
||||
if (errors != nil && errors!.canAddError()){
|
||||
errors?.add(ParseError(reader.getPos(), "Unexpected character '%@' in input state [%@]", String(reader.current()), state.description));
|
||||
}
|
||||
}
|
||||
|
||||
func eofError(_ state: TokeniserState) {
|
||||
if (errors != nil && errors!.canAddError()){
|
||||
errors?.add(ParseError(reader.getPos(), "Unexpectedly reached end of file (EOF) in input state [%@]", state.description));
|
||||
}
|
||||
}
|
||||
|
||||
private func characterReferenceError(_ message: String) {
|
||||
if (errors != nil && errors!.canAddError()){
|
||||
errors?.add(ParseError(reader.getPos(), "Invalid character reference: %@", message));
|
||||
}
|
||||
}
|
||||
|
||||
private func error(_ errorMsg: String) {
|
||||
if (errors != nil && errors!.canAddError()){
|
||||
errors?.add(ParseError(reader.getPos(), errorMsg));
|
||||
}
|
||||
}
|
||||
|
||||
func currentNodeInHtmlNS()->Bool {
|
||||
// todo: implement namespaces correctly
|
||||
return true;
|
||||
// Element currentNode = currentNode();
|
||||
// return currentNode != null && currentNode.namespace().equals("HTML");
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility method to consume reader and unescape entities found within.
|
||||
* @param inAttribute
|
||||
* @return unescaped string from reader
|
||||
*/
|
||||
func unescapeEntities(_ inAttribute: Bool)throws->String {
|
||||
let builder : StringBuilder = StringBuilder();
|
||||
while (!reader.isEmpty()) {
|
||||
builder.append(reader.consumeTo("&"));
|
||||
if (reader.matches("&")) {
|
||||
reader.consume();
|
||||
if let c = try consumeCharacterReference(nil, inAttribute)
|
||||
{
|
||||
if (c.count==0){
|
||||
builder.append("&");
|
||||
}else {
|
||||
builder.appendCodePoint(c[0]);
|
||||
if (c.count == 2){
|
||||
builder.appendCodePoint(c[1]);
|
||||
}
|
||||
}
|
||||
}else {
|
||||
builder.append("&");
|
||||
}
|
||||
}
|
||||
}
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,101 @@
|
|||
//
|
||||
// TreeBuilder.swift
|
||||
// SwiftSoup
|
||||
//
|
||||
// Created by Nabil Chatbi on 24/10/16.
|
||||
// Copyright © 2016 Nabil Chatbi.. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
public class TreeBuilder {
|
||||
public var reader: CharacterReader
|
||||
var tokeniser: Tokeniser
|
||||
public var doc: Document; // current doc we are building into
|
||||
public var stack: Array<Element>; // the stack of open elements
|
||||
public var baseUri: String; // current base uri, for creating new elements
|
||||
public var currentToken: Token?; // currentToken is used only for error tracking.
|
||||
public var errors: ParseErrorList; // null when not tracking errors
|
||||
public var settings: ParseSettings;
|
||||
|
||||
private let start: Token.StartTag = Token.StartTag(); // start tag to process
|
||||
private let end: Token.EndTag = Token.EndTag();
|
||||
|
||||
public func defaultSettings()->ParseSettings{preconditionFailure("This method must be overridden")}
|
||||
|
||||
|
||||
public init() {
|
||||
doc = Document("");
|
||||
reader = CharacterReader("")
|
||||
tokeniser = Tokeniser(reader,nil)
|
||||
stack = Array<Element>()
|
||||
baseUri = ""
|
||||
errors = ParseErrorList(0,0)
|
||||
settings = ParseSettings(false,false)
|
||||
}
|
||||
|
||||
public func initialiseParse(_ input: String, _ baseUri: String, _ errors: ParseErrorList, _ settings: ParseSettings) {
|
||||
doc = Document(baseUri);
|
||||
self.settings = settings;
|
||||
reader = CharacterReader(input);
|
||||
self.errors = errors;
|
||||
tokeniser = Tokeniser(reader, errors);
|
||||
stack = Array<Element>();
|
||||
self.baseUri = baseUri;
|
||||
}
|
||||
|
||||
|
||||
func parse(_ input: String, _ baseUri: String, _ errors: ParseErrorList, _ settings: ParseSettings)throws->Document {
|
||||
initialiseParse(input, baseUri, errors, settings);
|
||||
try runParser();
|
||||
return doc;
|
||||
}
|
||||
|
||||
public func runParser()throws {
|
||||
while (true) {
|
||||
let token: Token = try tokeniser.read();
|
||||
try process(token);
|
||||
token.reset();
|
||||
|
||||
if (token.type == Token.TokenType.EOF){
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
public func process(_ token: Token)throws->Bool{preconditionFailure("This method must be overridden")}
|
||||
|
||||
@discardableResult
|
||||
public func processStartTag(_ name: String)throws->Bool {
|
||||
if (currentToken === start) { // don't recycle an in-use token
|
||||
return try process(Token.StartTag().name(name));
|
||||
}
|
||||
return try process(start.reset().name(name));
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
public func processStartTag(_ name: String, _ attrs: Attributes)throws->Bool {
|
||||
if (currentToken === start) { // don't recycle an in-use token
|
||||
return try process(Token.StartTag().nameAttr(name, attrs));
|
||||
}
|
||||
start.reset();
|
||||
start.nameAttr(name, attrs);
|
||||
return try process(start);
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
public func processEndTag(_ name: String)throws->Bool {
|
||||
if (currentToken === end) { // don't recycle an in-use token
|
||||
return try process(Token.EndTag().name(name));
|
||||
}
|
||||
|
||||
return try process(end.reset().name(name));
|
||||
}
|
||||
|
||||
|
||||
public func currentElement()->Element? {
|
||||
let size: Int = stack.count
|
||||
return size > 0 ? stack[size-1] : nil
|
||||
}
|
||||
}
|
|
@ -0,0 +1,152 @@
|
|||
//
|
||||
// XmlTreeBuilder.swift
|
||||
// SwiftSoup
|
||||
//
|
||||
// Created by Nabil Chatbi on 14/10/16.
|
||||
// Copyright © 2016 Nabil Chatbi.. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
/**
|
||||
* Use the {@code XmlTreeBuilder} when you want to parse XML without any of the HTML DOM rules being applied to the
|
||||
* document.
|
||||
* <p>Usage example: {@code Document xmlDoc = Jsoup.parse(html, baseUrl, Parser.xmlParser());}</p>
|
||||
*
|
||||
*/
|
||||
public class XmlTreeBuilder : TreeBuilder {
|
||||
|
||||
public override init(){
|
||||
super.init()
|
||||
}
|
||||
|
||||
|
||||
public override func defaultSettings()->ParseSettings {
|
||||
return ParseSettings.preserveCase;
|
||||
}
|
||||
|
||||
|
||||
public func parse(_ input: String, _ baseUri: String)throws->Document {
|
||||
return try parse(input, baseUri, ParseErrorList.noTracking(), ParseSettings.preserveCase);
|
||||
}
|
||||
|
||||
override public func initialiseParse(_ input: String, _ baseUri: String, _ errors: ParseErrorList, _ settings: ParseSettings) {
|
||||
super.initialiseParse(input, baseUri, errors, settings);
|
||||
stack.append(doc); // place the document onto the stack. differs from HtmlTreeBuilder (not on stack)
|
||||
doc.outputSettings().syntax(syntax: OutputSettings.Syntax.xml);
|
||||
}
|
||||
|
||||
|
||||
override public func process(_ token: Token)throws->Bool {
|
||||
// start tag, end tag, doctype, comment, character, eof
|
||||
switch (token.type) {
|
||||
case .StartTag:
|
||||
try insert(token.asStartTag());
|
||||
break;
|
||||
case .EndTag:
|
||||
try popStackToClose(token.asEndTag());
|
||||
break;
|
||||
case .Comment:
|
||||
try insert(token.asComment());
|
||||
break;
|
||||
case .Char:
|
||||
try insert(token.asCharacter());
|
||||
break;
|
||||
case .Doctype:
|
||||
try insert(token.asDoctype());
|
||||
break;
|
||||
case .EOF: // could put some normalisation here if desired
|
||||
break;
|
||||
// default:
|
||||
// try Validate.fail(msg: "Unexpected token type: " + token.tokenType());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private func insertNode(_ node: Node)throws {
|
||||
try currentElement()?.appendChild(node);
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func insert(_ startTag: Token.StartTag)throws->Element {
|
||||
let tag: Tag = try Tag.valueOf(startTag.name(), settings);
|
||||
// todo: wonder if for xml parsing, should treat all tags as unknown? because it's not html.
|
||||
let el: Element = try Element(tag, baseUri, settings.normalizeAttributes(startTag._attributes));
|
||||
try insertNode(el);
|
||||
if (startTag.isSelfClosing()) {
|
||||
tokeniser.acknowledgeSelfClosingFlag();
|
||||
if (!tag.isKnownTag()) // unknown tag, remember this is self closing for output. see above.
|
||||
{
|
||||
tag.setSelfClosing();
|
||||
}
|
||||
} else {
|
||||
stack.append(el);
|
||||
}
|
||||
return el;
|
||||
}
|
||||
|
||||
func insert(_ commentToken: Token.Comment)throws {
|
||||
let comment: Comment = Comment(commentToken.getData(), baseUri);
|
||||
var insert: Node = comment;
|
||||
if (commentToken.bogus) { // xml declarations are emitted as bogus comments (which is right for html, but not xml)
|
||||
// so we do a bit of a hack and parse the data as an element to pull the attributes out
|
||||
let data: String = comment.getData();
|
||||
if (data.characters.count > 1 && (data.startsWith("!") || data.startsWith("?"))) {
|
||||
let doc: Document = try SwiftSoup.parse("<" + data.substring(1, data.characters.count - 2) + ">", baseUri, Parser.xmlParser());
|
||||
let el: Element = doc.child(0);
|
||||
insert = XmlDeclaration(settings.normalizeTag(el.tagName()), comment.getBaseUri(), data.startsWith("!"));
|
||||
insert.getAttributes()?.addAll(incoming: el.getAttributes());
|
||||
}
|
||||
}
|
||||
try insertNode(insert);
|
||||
}
|
||||
|
||||
func insert(_ characterToken: Token.Char)throws {
|
||||
let node: Node = TextNode(characterToken.getData()!, baseUri);
|
||||
try insertNode(node);
|
||||
}
|
||||
|
||||
func insert(_ d: Token.Doctype)throws {
|
||||
let doctypeNode: DocumentType = DocumentType(settings.normalizeTag(d.getName()), d.getPublicIdentifier(), d.getSystemIdentifier(), baseUri);
|
||||
try insertNode(doctypeNode);
|
||||
}
|
||||
|
||||
/**
|
||||
* If the stack contains an element with this tag's name, pop up the stack to remove the first occurrence. If not
|
||||
* found, skips.
|
||||
*
|
||||
* @param endTag
|
||||
*/
|
||||
private func popStackToClose(_ endTag: Token.EndTag)throws {
|
||||
let elName: String = try endTag.name();
|
||||
var firstFound: Element? = nil;
|
||||
|
||||
for pos in (0..<stack.count).reversed()
|
||||
{
|
||||
let next: Element = stack[pos]
|
||||
if (next.nodeName().equals(elName)) {
|
||||
firstFound = next;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (firstFound == nil){
|
||||
return; // not found, skip
|
||||
}
|
||||
|
||||
|
||||
for pos in (0..<stack.count).reversed()
|
||||
{
|
||||
let next: Element = stack[pos]
|
||||
stack.remove(at: pos);
|
||||
if (next == firstFound!){
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func parseFragment(_ inputFragment: String, _ baseUri: String, _ errors: ParseErrorList, _ settings: ParseSettings)throws->Array<Node> {
|
||||
initialiseParse(inputFragment, baseUri, errors, settings);
|
||||
try runParser();
|
||||
return doc.getChildNodes();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,160 @@
|
|||
//
|
||||
// Cleaner.swift
|
||||
// SwiftSoup
|
||||
//
|
||||
// Created by Nabil Chatbi on 15/10/16.
|
||||
// Copyright © 2016 Nabil Chatbi.. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
open class Cleaner
|
||||
{
|
||||
fileprivate let whitelist : Whitelist;
|
||||
|
||||
/**
|
||||
Create a new cleaner, that sanitizes documents using the supplied whitelist.
|
||||
@param whitelist white-list to clean with
|
||||
*/
|
||||
public init(_ whitelist: Whitelist) {
|
||||
self.whitelist = whitelist;
|
||||
}
|
||||
|
||||
/**
|
||||
Creates a new, clean document, from the original dirty document, containing only elements allowed by the whitelist.
|
||||
The original document is not modified. Only elements from the dirt document's <code>body</code> are used.
|
||||
@param dirtyDocument Untrusted base document to clean.
|
||||
@return cleaned document.
|
||||
*/
|
||||
public func clean(_ dirtyDocument: Document)throws->Document {
|
||||
//Validate.notNull(dirtyDocument);
|
||||
let clean: Document = Document.createShell(dirtyDocument.getBaseUri());
|
||||
if (dirtyDocument.body() != nil && clean.body() != nil) // frameset documents won't have a body. the clean doc will have empty body.
|
||||
{
|
||||
try copySafeNodes(dirtyDocument.body()!, clean.body()!);
|
||||
}
|
||||
return clean;
|
||||
}
|
||||
|
||||
/**
|
||||
Determines if the input document is valid, against the whitelist. It is considered valid if all the tags and attributes
|
||||
in the input HTML are allowed by the whitelist.
|
||||
<p>
|
||||
This method can be used as a validator for user input forms. An invalid document will still be cleaned successfully
|
||||
using the {@link #clean(Document)} document. If using as a validator, it is recommended to still clean the document
|
||||
to ensure enforced attributes are set correctly, and that the output is tidied.
|
||||
</p>
|
||||
@param dirtyDocument document to test
|
||||
@return true if no tags or attributes need to be removed; false if they do
|
||||
*/
|
||||
public func isValid(_ dirtyDocument: Document)throws->Bool {
|
||||
//Validate.notNull(dirtyDocument);
|
||||
let clean: Document = Document.createShell(dirtyDocument.getBaseUri());
|
||||
let numDiscarded: Int = try copySafeNodes(dirtyDocument.body()!, clean.body()!);
|
||||
return numDiscarded == 0;
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
fileprivate func copySafeNodes(_ source: Element, _ dest: Element)throws->Int {
|
||||
let cleaningVisitor: Cleaner.CleaningVisitor = Cleaner.CleaningVisitor(source, dest,self);
|
||||
let traversor: NodeTraversor = NodeTraversor(cleaningVisitor);
|
||||
try traversor.traverse(source);
|
||||
return cleaningVisitor.numDiscarded;
|
||||
}
|
||||
|
||||
fileprivate func createSafeElement(_ sourceEl: Element)throws->ElementMeta {
|
||||
let sourceTag: String = sourceEl.tagName();
|
||||
let destAttrs: Attributes = Attributes();
|
||||
let dest: Element = try Element(Tag.valueOf(sourceTag), sourceEl.getBaseUri(), destAttrs);
|
||||
var numDiscarded: Int = 0;
|
||||
|
||||
if let sourceAttrs = sourceEl.getAttributes()
|
||||
{
|
||||
for sourceAttr: Attribute in sourceAttrs
|
||||
{
|
||||
if (whitelist.isSafeAttribute(sourceTag, sourceEl, sourceAttr)){
|
||||
destAttrs.put(attribute: sourceAttr);
|
||||
}else{
|
||||
numDiscarded+=1;
|
||||
}
|
||||
}
|
||||
}
|
||||
let enforcedAttrs: Attributes = try whitelist.getEnforcedAttributes(sourceTag)
|
||||
destAttrs.addAll(incoming: enforcedAttrs);
|
||||
|
||||
return ElementMeta(dest, numDiscarded);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
extension Cleaner
|
||||
{
|
||||
fileprivate final class CleaningVisitor : NodeVisitor
|
||||
{
|
||||
var numDiscarded: Int = 0;
|
||||
let root: Element;
|
||||
var destination: Element? ; // current element to append nodes to
|
||||
|
||||
private weak var cleaner : Cleaner?;
|
||||
|
||||
public init(_ root: Element, _ destination: Element, _ cleaner : Cleaner) {
|
||||
self.root = root;
|
||||
self.destination = destination;
|
||||
}
|
||||
|
||||
public func head(_ source: Node, _ depth: Int)throws {
|
||||
if let sourceEl = (source as? Element) {
|
||||
if (cleaner!.whitelist.isSafeTag(sourceEl.tagName())) { // safe, clone and copy safe attrs
|
||||
let meta: Cleaner.ElementMeta = try cleaner!.createSafeElement(sourceEl);
|
||||
let destChild: Element = meta.el;
|
||||
try destination?.appendChild(destChild);
|
||||
|
||||
numDiscarded += meta.numAttribsDiscarded;
|
||||
destination = destChild;
|
||||
} else if (source != root) { // not a safe tag, so don't add. don't count root against discarded.
|
||||
numDiscarded+=1;
|
||||
}
|
||||
} else if let sourceText = (source as? TextNode) {
|
||||
let destText: TextNode = TextNode(sourceText.getWholeText(), source.getBaseUri());
|
||||
try destination?.appendChild(destText);
|
||||
}
|
||||
else if let sourceData = (source as? DataNode)
|
||||
{
|
||||
if sourceData.parent() != nil && cleaner!.whitelist.isSafeTag(sourceData.parent()!.nodeName())
|
||||
{
|
||||
//let sourceData: DataNode = (DataNode) source;
|
||||
let destData: DataNode = DataNode(sourceData.getWholeData(), source.getBaseUri());
|
||||
try destination?.appendChild(destData);
|
||||
}
|
||||
} else { // else, we don't care about comments, xml proc instructions, etc
|
||||
numDiscarded+=1;
|
||||
}
|
||||
}
|
||||
|
||||
public func tail(_ source: Node, _ depth: Int)throws
|
||||
{
|
||||
if let x = (source as? Element)
|
||||
{
|
||||
if cleaner!.whitelist.isSafeTag(x.nodeName())
|
||||
{
|
||||
// would have descended, so pop destination stack
|
||||
destination = destination?.parent()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Cleaner
|
||||
{
|
||||
fileprivate struct ElementMeta {
|
||||
let el: Element ;
|
||||
let numAttribsDiscarded: Int;
|
||||
|
||||
init(_ el: Element, _ numAttribsDiscarded: Int) {
|
||||
self.el = el;
|
||||
self.numAttribsDiscarded = numAttribsDiscarded;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,596 @@
|
|||
//
|
||||
// Whitelist.swift
|
||||
// SwiftSoup
|
||||
//
|
||||
// Created by Nabil Chatbi on 14/10/16.
|
||||
// Copyright © 2016 Nabil Chatbi.. All rights reserved.
|
||||
//
|
||||
|
||||
/*
|
||||
Thank you to Ryan Grove (wonko.com) for the Ruby HTML cleaner http://github.com/rgrove/sanitize/, which inspired
|
||||
this whitelist configuration, and the initial defaults.
|
||||
*/
|
||||
|
||||
/**
|
||||
Whitelists define what HTML (elements and attributes) to allow through the cleaner. Everything else is removed.
|
||||
<p>
|
||||
Start with one of the defaults:
|
||||
</p>
|
||||
<ul>
|
||||
<li>{@link #none}
|
||||
<li>{@link #simpleText}
|
||||
<li>{@link #basic}
|
||||
<li>{@link #basicWithImages}
|
||||
<li>{@link #relaxed}
|
||||
</ul>
|
||||
<p>
|
||||
If you need to allow more through (please be careful!), tweak a base whitelist with:
|
||||
</p>
|
||||
<ul>
|
||||
<li>{@link #addTags}
|
||||
<li>{@link #addAttributes}
|
||||
<li>{@link #addEnforcedAttribute}
|
||||
<li>{@link #addProtocols}
|
||||
</ul>
|
||||
<p>
|
||||
You can remove any setting from an existing whitelist with:
|
||||
</p>
|
||||
<ul>
|
||||
<li>{@link #removeTags}
|
||||
<li>{@link #removeAttributes}
|
||||
<li>{@link #removeEnforcedAttribute}
|
||||
<li>{@link #removeProtocols}
|
||||
</ul>
|
||||
|
||||
<p>
|
||||
The cleaner and these whitelists assume that you want to clean a <code>body</code> fragment of HTML (to add user
|
||||
supplied HTML into a templated page), and not to clean a full HTML document. If the latter is the case, either wrap the
|
||||
document HTML around the cleaned body HTML, or create a whitelist that allows <code>html</code> and <code>head</code>
|
||||
elements as appropriate.
|
||||
</p>
|
||||
<p>
|
||||
If you are going to extend a whitelist, please be very careful. Make sure you understand what attributes may lead to
|
||||
XSS attack vectors. URL attributes are particularly vulnerable and require careful validation. See
|
||||
http://ha.ckers.org/xss.html for some XSS attack examples.
|
||||
</p>
|
||||
*/
|
||||
|
||||
import Foundation
|
||||
|
||||
|
||||
public class Whitelist {
|
||||
private var tagNames : Set<TagName>; // tags allowed, lower case. e.g. [p, br, span]
|
||||
private var attributes : Dictionary<TagName, Set<AttributeKey>>; // tag -> attribute[]. allowed attributes [href] for a tag.
|
||||
private var enforcedAttributes : Dictionary<TagName, Dictionary<AttributeKey, AttributeValue>>; // always set these attribute values
|
||||
private var protocols : Dictionary<TagName, Dictionary<AttributeKey, Set<Protocol>>>; // allowed URL protocols for attributes
|
||||
private var preserveRelativeLinks : Bool ; // option to preserve relative links
|
||||
|
||||
|
||||
/**
|
||||
This whitelist allows only text nodes: all HTML will be stripped.
|
||||
|
||||
@return whitelist
|
||||
*/
|
||||
public static func none()->Whitelist {
|
||||
return Whitelist();
|
||||
}
|
||||
|
||||
/**
|
||||
This whitelist allows only simple text formatting: <code>b, em, i, strong, u</code>. All other HTML (tags and
|
||||
attributes) will be removed.
|
||||
|
||||
@return whitelist
|
||||
*/
|
||||
public static func simpleText()throws ->Whitelist {
|
||||
return try Whitelist().addTags("b", "em", "i", "strong", "u")
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
Create a new, empty whitelist. Generally it will be better to start with a default prepared whitelist instead.
|
||||
|
||||
@see #basic()
|
||||
@see #basicWithImages()
|
||||
@see #simpleText()
|
||||
@see #relaxed()
|
||||
*/
|
||||
init() {
|
||||
tagNames = Set<TagName>();
|
||||
attributes = Dictionary<TagName, Set<AttributeKey>>();
|
||||
enforcedAttributes = Dictionary<TagName, Dictionary<AttributeKey, AttributeValue>>();
|
||||
protocols = Dictionary<TagName, Dictionary<AttributeKey, Set<Protocol>>>();
|
||||
preserveRelativeLinks = false;
|
||||
}
|
||||
|
||||
/**
|
||||
Add a list of allowed elements to a whitelist. (If a tag is not allowed, it will be removed from the HTML.)
|
||||
|
||||
@param tags tag names to allow
|
||||
@return this (for chaining)
|
||||
*/
|
||||
@discardableResult
|
||||
open func addTags(_ tags: String...)throws ->Whitelist {
|
||||
for tagName in tags
|
||||
{
|
||||
try Validate.notEmpty(string: tagName);
|
||||
tagNames.insert(TagName.valueOf(tagName))
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
/**
|
||||
Remove a list of allowed elements from a whitelist. (If a tag is not allowed, it will be removed from the HTML.)
|
||||
|
||||
@param tags tag names to disallow
|
||||
@return this (for chaining)
|
||||
*/
|
||||
@discardableResult
|
||||
open func removeTags(_ tags: String...)throws ->Whitelist {
|
||||
try Validate.notNull(obj: tags as AnyObject?);
|
||||
|
||||
for tag in tags {
|
||||
try Validate.notEmpty(string: tag);
|
||||
let tagName : TagName = TagName.valueOf(tag);
|
||||
|
||||
if(tagNames.contains(tagName)) { // Only look in sub-maps if tag was allowed
|
||||
tagNames.remove(tagName)
|
||||
attributes.removeValue(forKey: tagName)
|
||||
enforcedAttributes.removeValue(forKey: tagName)
|
||||
protocols.removeValue(forKey: tagName)
|
||||
}
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
/**
|
||||
Add a list of allowed attributes to a tag. (If an attribute is not allowed on an element, it will be removed.)
|
||||
<p>
|
||||
E.g.: <code>addAttributes("a", "href", "class")</code> allows <code>href</code> and <code>class</code> attributes
|
||||
on <code>a</code> tags.
|
||||
</p>
|
||||
<p>
|
||||
To make an attribute valid for <b>all tags</b>, use the pseudo tag <code>:all</code>, e.g.
|
||||
<code>addAttributes(":all", "class")</code>.
|
||||
</p>
|
||||
|
||||
@param tag The tag the attributes are for. The tag will be added to the allowed tag list if necessary.
|
||||
@param keys List of valid attributes for the tag
|
||||
@return this (for chaining)
|
||||
*/
|
||||
@discardableResult
|
||||
open func addAttributes(_ tag: String, _ keys: String...)throws->Whitelist {
|
||||
try Validate.notEmpty(string: tag);
|
||||
try Validate.isTrue(val: keys.count > 0, msg: "No attributes supplied.");
|
||||
|
||||
let tagName = TagName.valueOf(tag);
|
||||
if (!tagNames.contains(tagName)){
|
||||
tagNames.insert(tagName);
|
||||
}
|
||||
var attributeSet = Set<AttributeKey>();
|
||||
for key in keys
|
||||
{
|
||||
try Validate.notEmpty(string: key);
|
||||
attributeSet.insert(AttributeKey.valueOf(key));
|
||||
}
|
||||
|
||||
if var currentSet = attributes[tagName]
|
||||
{
|
||||
for at in attributeSet{
|
||||
currentSet.insert(at)
|
||||
}
|
||||
attributes[tagName] = currentSet
|
||||
} else {
|
||||
attributes[tagName] = attributeSet
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
/**
|
||||
Remove a list of allowed attributes from a tag. (If an attribute is not allowed on an element, it will be removed.)
|
||||
<p>
|
||||
E.g.: <code>removeAttributes("a", "href", "class")</code> disallows <code>href</code> and <code>class</code>
|
||||
attributes on <code>a</code> tags.
|
||||
</p>
|
||||
<p>
|
||||
To make an attribute invalid for <b>all tags</b>, use the pseudo tag <code>:all</code>, e.g.
|
||||
<code>removeAttributes(":all", "class")</code>.
|
||||
</p>
|
||||
|
||||
@param tag The tag the attributes are for.
|
||||
@param keys List of invalid attributes for the tag
|
||||
@return this (for chaining)
|
||||
*/
|
||||
@discardableResult
|
||||
open func removeAttributes(_ tag: String, _ keys: String...)throws->Whitelist {
|
||||
try Validate.notEmpty(string: tag);
|
||||
try Validate.isTrue(val: keys.count > 0, msg: "No attributes supplied.");
|
||||
|
||||
let tagName : TagName = TagName.valueOf(tag);
|
||||
var attributeSet = Set<AttributeKey>();
|
||||
for key in keys {
|
||||
try Validate.notEmpty(string: key);
|
||||
attributeSet.insert(AttributeKey.valueOf(key));
|
||||
}
|
||||
|
||||
|
||||
if(tagNames.contains(tagName)) { // Only look in sub-maps if tag was allowed
|
||||
if var currentSet = attributes[tagName]
|
||||
{
|
||||
for l in attributeSet
|
||||
{
|
||||
currentSet.remove(l)
|
||||
}
|
||||
attributes[tagName] = currentSet
|
||||
if(currentSet.isEmpty){ // Remove tag from attribute map if no attributes are allowed for tag
|
||||
attributes.removeValue(forKey: tagName)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
if(tag == ":all"){ // Attribute needs to be removed from all individually set tags
|
||||
for name in attributes.keys
|
||||
{
|
||||
var currentSet : Set<AttributeKey> = attributes[name]!;
|
||||
for l in attributeSet{
|
||||
currentSet.remove(l)
|
||||
}
|
||||
attributes[name] = currentSet
|
||||
if(currentSet.isEmpty){ // Remove tag from attribute map if no attributes are allowed for tag
|
||||
attributes.removeValue(forKey: name)
|
||||
}
|
||||
}
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
/**
|
||||
Add an enforced attribute to a tag. An enforced attribute will always be added to the element. If the element
|
||||
already has the attribute set, it will be overridden.
|
||||
<p>
|
||||
E.g.: <code>addEnforcedAttribute("a", "rel", "nofollow")</code> will make all <code>a</code> tags output as
|
||||
<code><a href="..." rel="nofollow"></code>
|
||||
</p>
|
||||
|
||||
@param tag The tag the enforced attribute is for. The tag will be added to the allowed tag list if necessary.
|
||||
@param key The attribute key
|
||||
@param value The enforced attribute value
|
||||
@return this (for chaining)
|
||||
*/
|
||||
@discardableResult
|
||||
open func addEnforcedAttribute(_ tag: String, _ key: String, _ value: String)throws->Whitelist {
|
||||
try Validate.notEmpty(string: tag);
|
||||
try Validate.notEmpty(string: key);
|
||||
try Validate.notEmpty(string: value);
|
||||
|
||||
let tagName : TagName = TagName.valueOf(tag);
|
||||
if (!tagNames.contains(tagName)){
|
||||
tagNames.insert(tagName);
|
||||
}
|
||||
let attrKey : AttributeKey = AttributeKey.valueOf(key);
|
||||
let attrVal : AttributeValue = AttributeValue.valueOf(value);
|
||||
|
||||
if (enforcedAttributes[tagName] != nil) {
|
||||
enforcedAttributes[tagName]?[attrKey] = attrVal
|
||||
} else {
|
||||
var attrMap : Dictionary<AttributeKey, AttributeValue> = Dictionary<AttributeKey, AttributeValue>();
|
||||
attrMap[attrKey] = attrVal
|
||||
enforcedAttributes[tagName] = attrMap
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
/**
|
||||
Remove a previously configured enforced attribute from a tag.
|
||||
|
||||
@param tag The tag the enforced attribute is for.
|
||||
@param key The attribute key
|
||||
@return this (for chaining)
|
||||
*/
|
||||
@discardableResult
|
||||
open func removeEnforcedAttribute(_ tag: String, _ key: String)throws->Whitelist {
|
||||
try Validate.notEmpty(string: tag);
|
||||
try Validate.notEmpty(string: key);
|
||||
|
||||
let tagName : TagName = TagName.valueOf(tag);
|
||||
if(tagNames.contains(tagName) && (enforcedAttributes[tagName] != nil)) {
|
||||
let attrKey : AttributeKey = AttributeKey.valueOf(key);
|
||||
var attrMap : Dictionary<AttributeKey, AttributeValue> = enforcedAttributes[tagName]!;
|
||||
attrMap.removeValue(forKey: attrKey)
|
||||
enforcedAttributes[tagName] = attrMap
|
||||
|
||||
if(attrMap.isEmpty){ // Remove tag from enforced attribute map if no enforced attributes are present
|
||||
enforcedAttributes.removeValue(forKey: tagName);
|
||||
}
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure this Whitelist to preserve relative links in an element's URL attribute, or convert them to absolute
|
||||
* links. By default, this is <b>false</b>: URLs will be made absolute (e.g. start with an allowed protocol, like
|
||||
* e.g. {@code http://}.
|
||||
* <p>
|
||||
* Note that when handling relative links, the input document must have an appropriate {@code base URI} set when
|
||||
* parsing, so that the link's protocol can be confirmed. Regardless of the setting of the {@code preserve relative
|
||||
* links} option, the link must be resolvable against the base URI to an allowed protocol; otherwise the attribute
|
||||
* will be removed.
|
||||
* </p>
|
||||
*
|
||||
* @param preserve {@code true} to allow relative links, {@code false} (default) to deny
|
||||
* @return this Whitelist, for chaining.
|
||||
* @see #addProtocols
|
||||
*/
|
||||
@discardableResult
|
||||
open func preserveRelativeLinks(_ preserve: Bool)->Whitelist {
|
||||
preserveRelativeLinks = preserve;
|
||||
return self;
|
||||
}
|
||||
|
||||
/**
|
||||
Add allowed URL protocols for an element's URL attribute. This restricts the possible values of the attribute to
|
||||
URLs with the defined protocol.
|
||||
<p>
|
||||
E.g.: <code>addProtocols("a", "href", "ftp", "http", "https")</code>
|
||||
</p>
|
||||
<p>
|
||||
To allow a link to an in-page URL anchor (i.e. <code><a href="#anchor"></code>, add a <code>#</code>:<br>
|
||||
E.g.: <code>addProtocols("a", "href", "#")</code>
|
||||
</p>
|
||||
|
||||
@param tag Tag the URL protocol is for
|
||||
@param key Attribute key
|
||||
@param protocols List of valid protocols
|
||||
@return this, for chaining
|
||||
*/
|
||||
@discardableResult
|
||||
open func addProtocols(_ tag: String, _ key: String, _ protocols: String...)throws->Whitelist {
|
||||
try Validate.notEmpty(string: tag);
|
||||
try Validate.notEmpty(string: key);
|
||||
try Validate.notNull(obj: protocols as AnyObject?);
|
||||
|
||||
let tagName : TagName = TagName.valueOf(tag);
|
||||
let attrKey : AttributeKey = AttributeKey.valueOf(key);
|
||||
var attrMap : Dictionary<AttributeKey, Set<Protocol>>;
|
||||
var protSet : Set<Protocol>;
|
||||
|
||||
if (self.protocols[tagName] != nil) {
|
||||
attrMap = self.protocols[tagName]!;
|
||||
} else {
|
||||
attrMap = Dictionary<AttributeKey, Set<Protocol>>();
|
||||
self.protocols[tagName] = attrMap;
|
||||
}
|
||||
|
||||
if (attrMap[attrKey] != nil) {
|
||||
protSet = attrMap[attrKey]!;
|
||||
} else {
|
||||
protSet = Set<Protocol>();
|
||||
attrMap[attrKey] = protSet
|
||||
self.protocols[tagName] = attrMap;
|
||||
}
|
||||
for ptl in protocols
|
||||
{
|
||||
try Validate.notEmpty(string: ptl);
|
||||
let prot : Protocol = Protocol.valueOf(ptl);
|
||||
protSet.insert(prot);
|
||||
}
|
||||
attrMap[attrKey] = protSet
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
/**
|
||||
Remove allowed URL protocols for an element's URL attribute.
|
||||
<p>
|
||||
E.g.: <code>removeProtocols("a", "href", "ftp")</code>
|
||||
</p>
|
||||
|
||||
@param tag Tag the URL protocol is for
|
||||
@param key Attribute key
|
||||
@param protocols List of invalid protocols
|
||||
@return this, for chaining
|
||||
*/
|
||||
@discardableResult
|
||||
open func removeProtocols(_ tag: String, _ key: String, _ protocols: String...)throws->Whitelist {
|
||||
try Validate.notEmpty(string: tag);
|
||||
try Validate.notEmpty(string: key);
|
||||
|
||||
let tagName : TagName = TagName.valueOf(tag);
|
||||
let attrKey : AttributeKey = AttributeKey.valueOf(key);
|
||||
|
||||
if(self.protocols[tagName] != nil) {
|
||||
var attrMap : Dictionary<AttributeKey, Set<Protocol>>= self.protocols[tagName]!;
|
||||
if(attrMap[attrKey] != nil) {
|
||||
var protSet : Set<Protocol> = attrMap[attrKey]!;
|
||||
for ptl in protocols
|
||||
{
|
||||
try Validate.notEmpty(string: ptl);
|
||||
let prot : Protocol = Protocol.valueOf(ptl);
|
||||
protSet.remove(prot);
|
||||
}
|
||||
attrMap[attrKey] = protSet
|
||||
|
||||
if(protSet.isEmpty) { // Remove protocol set if empty
|
||||
attrMap.removeValue(forKey: attrKey)
|
||||
if(attrMap.isEmpty){ // Remove entry for tag if empty
|
||||
self.protocols.removeValue(forKey: tagName)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
self.protocols[tagName] = attrMap
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
/**
|
||||
* Test if the supplied tag is allowed by this whitelist
|
||||
* @param tag test tag
|
||||
* @return true if allowed
|
||||
*/
|
||||
public func isSafeTag(_ tag: String)->Bool {
|
||||
return tagNames.contains(TagName.valueOf(tag));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test if the supplied attribute is allowed by this whitelist for this tag
|
||||
* @param tagName tag to consider allowing the attribute in
|
||||
* @param el element under test, to confirm protocol
|
||||
* @param attr attribute under test
|
||||
* @return true if allowed
|
||||
*/
|
||||
public func isSafeAttribute(_ tagName: String, _ el: Element, _ attr: Attribute)->Bool {
|
||||
let tag : TagName = TagName.valueOf(tagName);
|
||||
let key : AttributeKey = AttributeKey.valueOf(attr.getKey());
|
||||
|
||||
if (attributes[tag] != nil) {
|
||||
if (attributes[tag]?.contains(key))! {
|
||||
if (protocols[tag] != nil) {
|
||||
//var attrProts : Dictionary<AttributeKey, Set<Protocol>>
|
||||
_ = protocols[tag]!;
|
||||
// ok if not defined protocol; otherwise test
|
||||
// return !(attrProts[key] != nil) || testValidProtocol(el, attr, attrProts[key]);
|
||||
} else { // attribute found, no protocols defined, so OK
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
// no attributes defined for tag, try :all tag
|
||||
return !(tagName == ":all") && isSafeAttribute(":all", el, attr);
|
||||
}
|
||||
|
||||
private func testValidProtocol(_ el: Element, _ attr: Attribute, _ protocols: Set<Protocol>)throws->Bool {
|
||||
// try to resolve relative urls to abs, and optionally update the attribute so output html has abs.
|
||||
// rels without a baseuri get removed
|
||||
var value : String = try el.absUrl(attr.getKey());
|
||||
if (value.characters.count == 0){
|
||||
value = attr.getValue(); // if it could not be made abs, run as-is to allow custom unknown protocols
|
||||
if (!preserveRelativeLinks){
|
||||
attr.setValue(value: value);
|
||||
}
|
||||
|
||||
for ptl in protocols
|
||||
{
|
||||
var prot : String = ptl.toString();
|
||||
|
||||
if (prot=="#") { // allows anchor links
|
||||
if (isValidAnchor(value)) {
|
||||
return true;
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
prot += ":";
|
||||
|
||||
if (value.lowercased().hasPrefix(prot)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private func isValidAnchor(_ value: String)->Bool
|
||||
{
|
||||
return value.startsWith("#") && !(Pattern(".*\\s.*").matcher(in: value).count > 0);
|
||||
}
|
||||
|
||||
public func getEnforcedAttributes(_ tagName: String)throws->Attributes {
|
||||
let attrs: Attributes = Attributes();
|
||||
let tag: TagName = TagName.valueOf(tagName);
|
||||
if let keyVals: Dictionary<AttributeKey, AttributeValue> = enforcedAttributes[tag]
|
||||
{
|
||||
for entry in keyVals
|
||||
{
|
||||
try attrs.put(entry.key.toString(), entry.value.toString());
|
||||
}
|
||||
}
|
||||
return attrs;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
// named types for config. All just hold strings, but here for my sanity.
|
||||
|
||||
open class TagName : TypedValue {
|
||||
override init(_ value: String) {
|
||||
super.init(value)
|
||||
}
|
||||
|
||||
static func valueOf(_ value: String)->TagName{
|
||||
return TagName(value);
|
||||
}
|
||||
}
|
||||
|
||||
open class AttributeKey : TypedValue {
|
||||
override init(_ value: String) {
|
||||
super.init(value);
|
||||
}
|
||||
|
||||
static func valueOf(_ value: String)->AttributeKey {
|
||||
return AttributeKey(value);
|
||||
}
|
||||
}
|
||||
|
||||
open class AttributeValue : TypedValue {
|
||||
override init(_ value: String) {
|
||||
super.init(value);
|
||||
}
|
||||
|
||||
static func valueOf(_ value: String)->AttributeValue {
|
||||
return AttributeValue(value);
|
||||
}
|
||||
}
|
||||
|
||||
open class Protocol : TypedValue {
|
||||
override init(_ value: String) {
|
||||
super.init(value);
|
||||
}
|
||||
|
||||
static func valueOf(_ value: String)->Protocol {
|
||||
return Protocol(value);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
open class TypedValue
|
||||
{
|
||||
fileprivate let value : String;
|
||||
|
||||
init(_ value: String) {
|
||||
self.value = value;
|
||||
}
|
||||
|
||||
public func toString()->String {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
extension TypedValue: Hashable {
|
||||
public var hashValue: Int {
|
||||
let prime = 31;
|
||||
var result = 1;
|
||||
result = Int.addWithOverflow(Int.multiplyWithOverflow(prime,result).0, value.hash).0
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
public func == (lhs: TypedValue, rhs: TypedValue) -> Bool
|
||||
{
|
||||
if(lhs === rhs){return true}
|
||||
return lhs.value == rhs.value
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
//
|
||||
// Collector.swift
|
||||
// SwiftSoup
|
||||
//
|
||||
// Created by Nabil Chatbi on 22/10/16.
|
||||
// Copyright © 2016 Nabil Chatbi.. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
/**
|
||||
* Collects a list of elements that match the supplied criteria.
|
||||
*
|
||||
* @author Jonathan Hedley
|
||||
*/
|
||||
open class Collector {
|
||||
|
||||
private init() {
|
||||
}
|
||||
|
||||
/**
|
||||
Build a list of elements, by visiting root and every descendant of root, and testing it against the evaluator.
|
||||
@param eval Evaluator to test elements against
|
||||
@param root root of tree to descend
|
||||
@return list of matches; empty if none
|
||||
*/
|
||||
open static func collect (_ eval: Evaluator, _ root: Element)throws->Elements {
|
||||
let elements : Elements = Elements()
|
||||
try NodeTraversor(Accumulator(root, elements, eval)).traverse(root)
|
||||
return elements;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private final class Accumulator : NodeVisitor {
|
||||
private let root: Element;
|
||||
private let elements: Elements;
|
||||
private let eval: Evaluator;
|
||||
|
||||
init(_ root: Element, _ elements: Elements, _ eval: Evaluator) {
|
||||
self.root = root;
|
||||
self.elements = elements;
|
||||
self.eval = eval;
|
||||
}
|
||||
|
||||
open func head(_ node: Node, _ depth: Int) {
|
||||
if let el = node as? Element
|
||||
{
|
||||
do{
|
||||
if (try eval.matches(root, el)){
|
||||
elements.add(el);
|
||||
}
|
||||
}catch{}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
open func tail(_ node: Node, _ depth: Int) {
|
||||
// void
|
||||
}
|
||||
}
|
|
@ -0,0 +1,123 @@
|
|||
//
|
||||
// CombiningEvaluator.swift
|
||||
// SwiftSoup
|
||||
//
|
||||
// Created by Nabil Chatbi on 23/10/16.
|
||||
// Copyright © 2016 Nabil Chatbi.. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
/**
|
||||
* Base combining (and, or) evaluator.
|
||||
*/
|
||||
public class CombiningEvaluator : Evaluator {
|
||||
|
||||
public private(set) var evaluators: Array<Evaluator>;
|
||||
var num : Int = 0;
|
||||
|
||||
public override init() {
|
||||
evaluators = Array<Evaluator>();
|
||||
super.init()
|
||||
}
|
||||
|
||||
public init(_ evaluators: Array<Evaluator>) {
|
||||
self.evaluators = evaluators
|
||||
super.init()
|
||||
updateNumEvaluators()
|
||||
}
|
||||
|
||||
func rightMostEvaluator()->Evaluator? {
|
||||
return num > 0 && evaluators.count > 0 ? evaluators[num - 1] : nil
|
||||
}
|
||||
|
||||
func replaceRightMostEvaluator(_ replacement: Evaluator) {
|
||||
evaluators[num - 1] = replacement
|
||||
}
|
||||
|
||||
func updateNumEvaluators() {
|
||||
// used so we don't need to bash on size() for every match test
|
||||
num = evaluators.count
|
||||
}
|
||||
|
||||
public final class And : CombiningEvaluator {
|
||||
public override init(_ evaluators: Array<Evaluator>) {
|
||||
super.init(evaluators);
|
||||
}
|
||||
|
||||
public override init(_ evaluators: Evaluator...) {
|
||||
super.init(evaluators);
|
||||
}
|
||||
|
||||
public override func matches(_ root: Element, _ node: Element)->Bool {
|
||||
for i in 0..<num
|
||||
{
|
||||
let s = evaluators[i]
|
||||
do{
|
||||
if (try !s.matches(root, node)){
|
||||
return false;
|
||||
}
|
||||
}catch{}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public override func toString()->String {
|
||||
let ar : [String] = evaluators.map { String($0.toString()) }
|
||||
return StringUtil.join(ar, sep: " ");
|
||||
}
|
||||
}
|
||||
|
||||
public final class Or : CombiningEvaluator {
|
||||
/**
|
||||
* Create a new Or evaluator. The initial evaluators are ANDed together and used as the first clause of the OR.
|
||||
* @param evaluators initial OR clause (these are wrapped into an AND evaluator).
|
||||
*/
|
||||
public override init(_ evaluators: Array<Evaluator>) {
|
||||
super.init();
|
||||
if (num > 1){
|
||||
self.evaluators.append(And(evaluators));
|
||||
}else{ // 0 or 1
|
||||
self.evaluators.append(contentsOf: evaluators);
|
||||
}
|
||||
updateNumEvaluators();
|
||||
}
|
||||
|
||||
override init(_ evaluators: Evaluator...) {
|
||||
super.init();
|
||||
if (num > 1){
|
||||
self.evaluators.append(And(evaluators));
|
||||
}else{ // 0 or 1
|
||||
self.evaluators.append(contentsOf: evaluators);
|
||||
}
|
||||
updateNumEvaluators();
|
||||
}
|
||||
|
||||
override init() {
|
||||
super.init();
|
||||
}
|
||||
|
||||
public func add(_ e: Evaluator) {
|
||||
evaluators.append(e);
|
||||
updateNumEvaluators();
|
||||
}
|
||||
|
||||
public override func matches(_ root: Element, _ node: Element)->Bool {
|
||||
for i in 0..<num
|
||||
{
|
||||
let s : Evaluator = evaluators[i]
|
||||
do{
|
||||
if (try s.matches(root, node)){
|
||||
return true;
|
||||
}
|
||||
}catch{}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public override func toString()->String {
|
||||
return ":or\(evaluators.map{String($0.toString())})"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,578 @@
|
|||
//
|
||||
// Elements.swift
|
||||
// SwiftSoup
|
||||
//
|
||||
// Created by Nabil Chatbi on 20/10/16.
|
||||
// Copyright © 2016 Nabil Chatbi.. All rights reserved.
|
||||
//
|
||||
/**
|
||||
A list of {@link Element}s, with methods that act on every element in the list.
|
||||
<p>
|
||||
To get an {@code Elements} object, use the {@link Element#select(String)} method.
|
||||
</p>
|
||||
*/
|
||||
|
||||
import Foundation
|
||||
|
||||
//open typealias Elements = Array<Element>
|
||||
//typealias E = Element
|
||||
open class Elements: NSCopying
|
||||
{
|
||||
fileprivate var this : Array<Element> = Array<Element>()
|
||||
|
||||
public init() {
|
||||
}
|
||||
public init(_ a: Array<Element>){
|
||||
this = a
|
||||
}
|
||||
public init(_ a: OrderedSet<Element>){
|
||||
this.append(contentsOf: a)
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a deep copy of these elements.
|
||||
* @return a deep copy
|
||||
*/
|
||||
public func copy(with zone: NSZone? = nil) -> Any
|
||||
{
|
||||
let clone: Elements = Elements()
|
||||
for e: Element in this{
|
||||
clone.add(e.copy() as! Element)
|
||||
}
|
||||
return clone;
|
||||
}
|
||||
|
||||
// attribute methods
|
||||
/**
|
||||
Get an attribute value from the first matched element that has the attribute.
|
||||
@param attributeKey The attribute key.
|
||||
@return The attribute value from the first matched element that has the attribute.. If no elements were matched (isEmpty() == true),
|
||||
or if the no elements have the attribute, returns empty string.
|
||||
@see #hasAttr(String)
|
||||
*/
|
||||
open func attr(_ attributeKey: String)throws->String {
|
||||
for element in this {
|
||||
if (element.hasAttr(attributeKey)){
|
||||
return try element.attr(attributeKey)
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
/**
|
||||
Checks if any of the matched elements have this attribute set.
|
||||
@param attributeKey attribute key
|
||||
@return true if any of the elements have the attribute; false if none do.
|
||||
*/
|
||||
open func hasAttr(_ attributeKey: String)->Bool {
|
||||
for element in this {
|
||||
if element.hasAttr(attributeKey) {return true}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set an attribute on all matched elements.
|
||||
* @param attributeKey attribute key
|
||||
* @param attributeValue attribute value
|
||||
* @return this
|
||||
*/
|
||||
@discardableResult
|
||||
open func attr(_ attributeKey: String, _ attributeValue: String)throws->Elements {
|
||||
for element in this {
|
||||
try element.attr(attributeKey, attributeValue);
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove an attribute from every matched element.
|
||||
* @param attributeKey The attribute to remove.
|
||||
* @return this (for chaining)
|
||||
*/
|
||||
@discardableResult
|
||||
open func removeAttr(_ attributeKey: String)throws->Elements {
|
||||
for element in this {
|
||||
try element.removeAttr(attributeKey);
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
/**
|
||||
Add the class name to every matched element's {@code class} attribute.
|
||||
@param className class name to add
|
||||
@return this
|
||||
*/
|
||||
@discardableResult
|
||||
open func addClass(_ className: String)throws->Elements {
|
||||
for element in this {
|
||||
try element.addClass(className);
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
/**
|
||||
Remove the class name from every matched element's {@code class} attribute, if present.
|
||||
@param className class name to remove
|
||||
@return this
|
||||
*/
|
||||
@discardableResult
|
||||
open func removeClass(_ className: String)throws->Elements {
|
||||
for element: Element in this {
|
||||
try element.removeClass(className);
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
/**
|
||||
Toggle the class name on every matched element's {@code class} attribute.
|
||||
@param className class name to add if missing, or remove if present, from every element.
|
||||
@return this
|
||||
*/
|
||||
@discardableResult
|
||||
open func toggleClass(_ className: String)throws->Elements {
|
||||
for element: Element in this {
|
||||
try element.toggleClass(className);
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
/**
|
||||
Determine if any of the matched elements have this class name set in their {@code class} attribute.
|
||||
@param className class name to check for
|
||||
@return true if any do, false if none do
|
||||
*/
|
||||
|
||||
open func hasClass(_ className: String)->Bool {
|
||||
for element: Element in this {
|
||||
if (element.hasClass(className)){
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the form element's value of the first matched element.
|
||||
* @return The form element's value, or empty if not set.
|
||||
* @see Element#val()
|
||||
*/
|
||||
open func val()throws->String {
|
||||
if (size() > 0){
|
||||
return try first()!.val();
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the form element's value in each of the matched elements.
|
||||
* @param value The value to set into each matched element
|
||||
* @return this (for chaining)
|
||||
*/
|
||||
@discardableResult
|
||||
open func val(_ value: String)throws->Elements {
|
||||
for element: Element in this{
|
||||
try element.val(value);
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the combined text of all the matched elements.
|
||||
* <p>
|
||||
* Note that it is possible to get repeats if the matched elements contain both parent elements and their own
|
||||
* children, as the Element.text() method returns the combined text of a parent and all its children.
|
||||
* @return string of all text: unescaped and no HTML.
|
||||
* @see Element#text()
|
||||
*/
|
||||
open func text()throws->String {
|
||||
let sb: StringBuilder = StringBuilder();
|
||||
for element: Element in this
|
||||
{
|
||||
if (sb.length != 0){
|
||||
sb.append(" ")
|
||||
}
|
||||
sb.append(try element.text());
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
open func hasText()->Bool {
|
||||
for element:Element in this
|
||||
{
|
||||
if (element.hasText()){
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the combined inner HTML of all matched elements.
|
||||
* @return string of all element's inner HTML.
|
||||
* @see #text()
|
||||
* @see #outerHtml()
|
||||
*/
|
||||
open func html()throws->String {
|
||||
let sb: StringBuilder = StringBuilder();
|
||||
for element: Element in this {
|
||||
if (sb.length != 0){
|
||||
sb.append("\n")
|
||||
}
|
||||
sb.append(try element.html());
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the combined outer HTML of all matched elements.
|
||||
* @return string of all element's outer HTML.
|
||||
* @see #text()
|
||||
* @see #html()
|
||||
*/
|
||||
open func outerHtml()throws->String {
|
||||
let sb: StringBuilder = StringBuilder();
|
||||
for element in this
|
||||
{
|
||||
if (sb.length != 0){
|
||||
sb.append("\n")
|
||||
}
|
||||
sb.append(try element.outerHtml());
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the combined outer HTML of all matched elements. Alias of {@link #outerHtml()}.
|
||||
* @return string of all element's outer HTML.
|
||||
* @see #text()
|
||||
* @see #html()
|
||||
*/
|
||||
|
||||
open func toString()throws->String {
|
||||
return try outerHtml();
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the tag name of each matched element. For example, to change each {@code <i>} to a {@code <em>}, do
|
||||
* {@code doc.select("i").tagName("em");}
|
||||
* @param tagName the new tag name
|
||||
* @return this, for chaining
|
||||
* @see Element#tagName(String)
|
||||
*/
|
||||
@discardableResult
|
||||
open func tagName(_ tagName: String)throws->Elements {
|
||||
for element: Element in this {
|
||||
try element.tagName(tagName);
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the inner HTML of each matched element.
|
||||
* @param html HTML to parse and set into each matched element.
|
||||
* @return this, for chaining
|
||||
* @see Element#html(String)
|
||||
*/
|
||||
@discardableResult
|
||||
open func html(_ html: String)throws->Elements {
|
||||
for element: Element in this {
|
||||
try element.html(html);
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the supplied HTML to the start of each matched element's inner HTML.
|
||||
* @param html HTML to add inside each element, before the existing HTML
|
||||
* @return this, for chaining
|
||||
* @see Element#prepend(String)
|
||||
*/
|
||||
@discardableResult
|
||||
open func prepend(_ html: String)throws->Elements {
|
||||
for element: Element in this {
|
||||
try element.prepend(html);
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the supplied HTML to the end of each matched element's inner HTML.
|
||||
* @param html HTML to add inside each element, after the existing HTML
|
||||
* @return this, for chaining
|
||||
* @see Element#append(String)
|
||||
*/
|
||||
@discardableResult
|
||||
open func append(_ html: String)throws->Elements {
|
||||
for element: Element in this {
|
||||
try element.append(html);
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert the supplied HTML before each matched element's outer HTML.
|
||||
* @param html HTML to insert before each element
|
||||
* @return this, for chaining
|
||||
* @see Element#before(String)
|
||||
*/
|
||||
@discardableResult
|
||||
open func before(_ html: String)throws->Elements {
|
||||
for element: Element in this {
|
||||
try element.before(html);
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert the supplied HTML after each matched element's outer HTML.
|
||||
* @param html HTML to insert after each element
|
||||
* @return this, for chaining
|
||||
* @see Element#after(String)
|
||||
*/
|
||||
@discardableResult
|
||||
open func after(_ html: String)throws->Elements {
|
||||
for element: Element in this {
|
||||
try element.after(html);
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
/**
|
||||
Wrap the supplied HTML around each matched elements. For example, with HTML
|
||||
{@code <p><b>This</b> is <b>Jsoup</b></p>},
|
||||
<code>doc.select("b").wrap("<i></i>");</code>
|
||||
becomes {@code <p><i><b>This</b></i> is <i><b>jsoup</b></i></p>}
|
||||
@param html HTML to wrap around each element, e.g. {@code <div class="head"></div>}. Can be arbitrarily deep.
|
||||
@return this (for chaining)
|
||||
@see Element#wrap
|
||||
*/
|
||||
@discardableResult
|
||||
open func wrap(_ html: String)throws->Elements {
|
||||
try Validate.notEmpty(string: html);
|
||||
for element: Element in this {
|
||||
try element.wrap(html);
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the matched elements from the DOM, and moves their children up into their parents. This has the effect of
|
||||
* dropping the elements but keeping their children.
|
||||
* <p>
|
||||
* This is useful for e.g removing unwanted formatting elements but keeping their contents.
|
||||
* </p>
|
||||
*
|
||||
* E.g. with HTML: <p>{@code <div><font>One</font> <font><a href="/">Two</a></font></div>}</p>
|
||||
* <p>{@code doc.select("font").unwrap();}</p>
|
||||
* <p>HTML = {@code <div>One <a href="/">Two</a></div>}</p>
|
||||
*
|
||||
* @return this (for chaining)
|
||||
* @see Node#unwrap
|
||||
*/
|
||||
@discardableResult
|
||||
open func unwrap()throws->Elements {
|
||||
for element: Element in this {
|
||||
try element.unwrap();
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
/**
|
||||
* Empty (remove all child nodes from) each matched element. This is similar to setting the inner HTML of each
|
||||
* element to nothing.
|
||||
* <p>
|
||||
* E.g. HTML: {@code <div><p>Hello <b>there</b></p> <p>now</p></div>}<br>
|
||||
* <code>doc.select("p").empty();</code><br>
|
||||
* HTML = {@code <div><p></p> <p></p></div>}
|
||||
* @return this, for chaining
|
||||
* @see Element#empty()
|
||||
* @see #remove()
|
||||
*/
|
||||
@discardableResult
|
||||
open func empty()->Elements {
|
||||
for element: Element in this {
|
||||
element.empty();
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove each matched element from the DOM. This is similar to setting the outer HTML of each element to nothing.
|
||||
* <p>
|
||||
* E.g. HTML: {@code <div><p>Hello</p> <p>there</p> <img /></div>}<br>
|
||||
* <code>doc.select("p").remove();</code><br>
|
||||
* HTML = {@code <div> <img /></div>}
|
||||
* <p>
|
||||
* Note that this method should not be used to clean user-submitted HTML; rather, use {@link org.jsoup.safety.Cleaner} to clean HTML.
|
||||
* @return this, for chaining
|
||||
* @see Element#empty()
|
||||
* @see #empty()
|
||||
*/
|
||||
@discardableResult
|
||||
open func remove()throws->Elements {
|
||||
for element in this
|
||||
{
|
||||
try element.remove();
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
// filters
|
||||
|
||||
/**
|
||||
* Find matching elements within this element list.
|
||||
* @param query A {@link Selector} query
|
||||
* @return the filtered list of elements, or an empty list if none match.
|
||||
*/
|
||||
open func select(_ query: String)throws->Elements {
|
||||
return try Selector.select(query, this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove elements from this list that match the {@link Selector} query.
|
||||
* <p>
|
||||
* E.g. HTML: {@code <div class=logo>One</div> <div>Two</div>}<br>
|
||||
* <code>Elements divs = doc.select("div").not(".logo");</code><br>
|
||||
* Result: {@code divs: [<div>Two</div>]}
|
||||
* <p>
|
||||
* @param query the selector query whose results should be removed from these elements
|
||||
* @return a new elements list that contains only the filtered results
|
||||
*/
|
||||
open func not(_ query: String)throws->Elements {
|
||||
let out: Elements = try Selector.select(query, this);
|
||||
return Selector.filterOut(this, out.this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the <i>nth</i> matched element as an Elements object.
|
||||
* <p>
|
||||
* See also {@link #get(int)} to retrieve an Element.
|
||||
* @param index the (zero-based) index of the element in the list to retain
|
||||
* @return Elements containing only the specified element, or, if that element did not exist, an empty list.
|
||||
*/
|
||||
open func eq(_ index: Int)->Elements {
|
||||
return size() > index ? Elements([get(index)]) : Elements();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test if any of the matched elements match the supplied query.
|
||||
* @param query A selector
|
||||
* @return true if at least one element in the list matches the query.
|
||||
*/
|
||||
open func `is`(_ query: String)throws->Bool {
|
||||
let children: Elements = try select(query);
|
||||
return !children.isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all of the parents and ancestor elements of the matched elements.
|
||||
* @return all of the parents and ancestor elements of the matched elements
|
||||
*/
|
||||
|
||||
open func parents()->Elements {
|
||||
let combo: OrderedSet<Element> = OrderedSet<Element>();
|
||||
for e:Element in this
|
||||
{
|
||||
combo.append(contentsOf: e.parents().array());
|
||||
}
|
||||
return Elements(combo);
|
||||
}
|
||||
|
||||
// list-like methods
|
||||
/**
|
||||
Get the first matched element.
|
||||
@return The first matched element, or <code>null</code> if contents is empty.
|
||||
*/
|
||||
open func first()->Element? {
|
||||
return isEmpty() ? nil : get(0);
|
||||
}
|
||||
|
||||
open func isEmpty()->Bool{
|
||||
return array().count == 0
|
||||
}
|
||||
|
||||
open func size()->Int{
|
||||
return array().count
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
Get the last matched element.
|
||||
@return The last matched element, or <code>null</code> if contents is empty.
|
||||
*/
|
||||
open func last()->Element? {
|
||||
return isEmpty() ? nil : get(size() - 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform a depth-first traversal on each of the selected elements.
|
||||
* @param nodeVisitor the visitor callbacks to perform on each node
|
||||
* @return this, for chaining
|
||||
*/
|
||||
@discardableResult
|
||||
open func traverse(_ nodeVisitor: NodeVisitor)throws->Elements {
|
||||
let traversor: NodeTraversor = NodeTraversor(nodeVisitor)
|
||||
for el:Element in this {
|
||||
try traversor.traverse(el);
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the {@link FormElement} forms from the selected elements, if any.
|
||||
* @return a list of {@link FormElement}s pulled from the matched elements. The list will be empty if the elements contain
|
||||
* no forms.
|
||||
*/
|
||||
open func forms()->Array<FormElement> {
|
||||
var forms: Array<FormElement> = Array<FormElement>();
|
||||
for el:Element in this
|
||||
{
|
||||
if let el = el as? FormElement
|
||||
{
|
||||
forms.append(el)
|
||||
}
|
||||
}
|
||||
return forms;
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends the specified element to the end of this list.
|
||||
*
|
||||
* @param e element to be appended to this list
|
||||
* @return <tt>true</tt> (as specified by {@link Collection#add})
|
||||
*/
|
||||
open func add(_ e: Element) {
|
||||
this.append(e)
|
||||
}
|
||||
|
||||
open func add(_ index: Int, _ element: Element) {
|
||||
this.insert(element, at: index)
|
||||
}
|
||||
|
||||
open func get(_ i :Int)->Element{
|
||||
return this[i]
|
||||
}
|
||||
|
||||
open func array()->Array<Element>{
|
||||
return this
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
extension Elements: Equatable
|
||||
{
|
||||
/// Returns a Boolean value indicating whether two values are equal.
|
||||
///
|
||||
/// Equality is the inverse of inequality. For any values `a` and `b`,
|
||||
/// `a == b` implies that `a != b` is `false`.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - lhs: A value to compare.
|
||||
/// - rhs: Another value to compare.
|
||||
public static func ==(lhs: Elements, rhs: Elements) -> Bool
|
||||
{
|
||||
return lhs.this == rhs.this
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,733 @@
|
|||
//
|
||||
// Evaluator.swift
|
||||
// SwiftSoup
|
||||
//
|
||||
// Created by Nabil Chatbi on 22/10/16.
|
||||
// Copyright © 2016 Nabil Chatbi.. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
/**
|
||||
* Evaluates that an element matches the selector.
|
||||
*/
|
||||
public class Evaluator {
|
||||
init () {}
|
||||
|
||||
/**
|
||||
* Test if the element meets the evaluator's requirements.
|
||||
*
|
||||
* @param root Root of the matching subtree
|
||||
* @param element tested element
|
||||
* @return Returns <tt>true</tt> if the requirements are met or
|
||||
* <tt>false</tt> otherwise
|
||||
*/
|
||||
open func matches(_ root: Element, _ element: Element)throws->Bool{
|
||||
preconditionFailure("self method must be overridden")
|
||||
}
|
||||
|
||||
open func toString()->String
|
||||
{
|
||||
preconditionFailure("self method must be overridden")
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Evaluator for tag name
|
||||
*/
|
||||
public class Tag : Evaluator {
|
||||
private let tagName : String
|
||||
|
||||
public init(_ tagName: String) {
|
||||
self.tagName = tagName;
|
||||
}
|
||||
|
||||
|
||||
open override func matches(_ root: Element, _ element: Element)throws->Bool {
|
||||
return (element.tagName().equalsIgnoreCase(string: tagName));
|
||||
}
|
||||
|
||||
|
||||
open override func toString()->String {
|
||||
return String(tagName)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Evaluator for tag name that ends with
|
||||
*/
|
||||
public final class TagEndsWith : Evaluator {
|
||||
private let tagName : String
|
||||
|
||||
public init(_ tagName: String) {
|
||||
self.tagName = tagName;
|
||||
}
|
||||
|
||||
open override func matches(_ root: Element, _ element: Element)throws->Bool {
|
||||
return (element.tagName().hasSuffix(tagName));
|
||||
}
|
||||
|
||||
open override func toString()->String {
|
||||
return String(tagName);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluator for element id
|
||||
*/
|
||||
public final class Id : Evaluator {
|
||||
private let id : String
|
||||
|
||||
public init(_ id: String) {
|
||||
self.id = id;
|
||||
}
|
||||
|
||||
open override func matches(_ root: Element, _ element: Element)throws->Bool {
|
||||
return (id == element.id());
|
||||
}
|
||||
|
||||
open override func toString()->String {
|
||||
return "#\(id)"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluator for element class
|
||||
*/
|
||||
public final class Class : Evaluator {
|
||||
private let className : String
|
||||
|
||||
public init(_ className: String) {
|
||||
self.className = className;
|
||||
}
|
||||
|
||||
open override func matches(_ root: Element, _ element: Element)->Bool {
|
||||
return (element.hasClass(className));
|
||||
}
|
||||
|
||||
open override func toString()->String {
|
||||
return ".\(className)"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluator for attribute name matching
|
||||
*/
|
||||
public final class Attribute : Evaluator {
|
||||
private let key : String
|
||||
|
||||
public init(_ key: String) {
|
||||
self.key = key;
|
||||
}
|
||||
|
||||
open override func matches(_ root: Element, _ element: Element)throws->Bool {
|
||||
return element.hasAttr(key);
|
||||
}
|
||||
|
||||
open override func toString()->String {
|
||||
return "[\(key)]"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluator for attribute name prefix matching
|
||||
*/
|
||||
public final class AttributeStarting : Evaluator {
|
||||
private let keyPrefix : String
|
||||
|
||||
public init(_ keyPrefix: String)throws {
|
||||
try Validate.notEmpty(string: keyPrefix);
|
||||
self.keyPrefix = keyPrefix.lowercased()
|
||||
}
|
||||
|
||||
open override func matches(_ root: Element, _ element: Element)throws->Bool {
|
||||
if let values = element.getAttributes(){
|
||||
for attribute in values.iterator() {
|
||||
if (attribute.getKey().lowercased().hasPrefix(keyPrefix)){
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
open override func toString()->String {
|
||||
return "[^\(keyPrefix)]"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluator for attribute name/value matching
|
||||
*/
|
||||
public final class AttributeWithValue : AttributeKeyPair {
|
||||
public override init(_ key: String, _ value: String)throws {
|
||||
try super.init(key, value);
|
||||
}
|
||||
|
||||
|
||||
open override func matches(_ root: Element, _ element: Element)throws->Bool
|
||||
{
|
||||
if(element.hasAttr(key)){
|
||||
let s = try element.attr(key)
|
||||
return value.equalsIgnoreCase(string: s.trim())
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
open override func toString()->String {
|
||||
return "[\(key)=\(value)]"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluator for attribute name != value matching
|
||||
*/
|
||||
public final class AttributeWithValueNot : AttributeKeyPair {
|
||||
public override init(_ key: String, _ value: String)throws {
|
||||
try super.init(key, value);
|
||||
}
|
||||
|
||||
open override func matches(_ root: Element, _ element: Element)throws->Bool {
|
||||
let s = try element.attr(key)
|
||||
return !value.equalsIgnoreCase(string: s);
|
||||
}
|
||||
|
||||
open override func toString()->String {
|
||||
return "[\(key)!=\(value)]"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluator for attribute name/value matching (value prefix)
|
||||
*/
|
||||
public final class AttributeWithValueStarting : AttributeKeyPair {
|
||||
public override init(_ key: String, _ value: String)throws {
|
||||
try super.init(key, value);
|
||||
}
|
||||
|
||||
open override func matches(_ root: Element, _ element: Element)throws->Bool {
|
||||
if(element.hasAttr(key)){
|
||||
return try element.attr(key).lowercased().hasPrefix(value) // value is lower case already
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
open override func toString()->String {
|
||||
return "[\(key)^=\(value)]"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluator for attribute name/value matching (value ending)
|
||||
*/
|
||||
public final class AttributeWithValueEnding : AttributeKeyPair {
|
||||
public override init(_ key: String, _ value: String)throws {
|
||||
try super.init(key, value);
|
||||
}
|
||||
|
||||
open override func matches(_ root: Element, _ element: Element)throws->Bool {
|
||||
if(element.hasAttr(key)){
|
||||
return try element.attr(key).lowercased().hasSuffix(value) // value is lower case
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
open override func toString()->String {
|
||||
return "[\(key)$=\(value)]"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluator for attribute name/value matching (value containing)
|
||||
*/
|
||||
public final class AttributeWithValueContaining : AttributeKeyPair {
|
||||
public override init(_ key: String, _ value: String)throws {
|
||||
try super.init(key, value);
|
||||
}
|
||||
|
||||
open override func matches(_ root: Element, _ element: Element)throws->Bool {
|
||||
if(element.hasAttr(key)){
|
||||
return try element.attr(key).lowercased().contains(value) // value is lower case
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
open override func toString()->String {
|
||||
return "[\(key)*=\(value)]"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluator for attribute name/value matching (value regex matching)
|
||||
*/
|
||||
public final class AttributeWithValueMatching : Evaluator {
|
||||
let key : String;
|
||||
let pattern : Pattern;
|
||||
|
||||
public init(_ key: String, _ pattern: Pattern) {
|
||||
self.key = key.trim().lowercased();
|
||||
self.pattern = pattern;
|
||||
super.init()
|
||||
}
|
||||
|
||||
open override func matches(_ root: Element, _ element: Element)throws->Bool {
|
||||
if(element.hasAttr(key)){
|
||||
let s = try element.attr(key)
|
||||
return pattern.matcher(in:s).find();
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
open override func toString()->String {
|
||||
return "[\(key)~=\(pattern.toString())]"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Abstract evaluator for attribute name/value matching
|
||||
*/
|
||||
public class AttributeKeyPair : Evaluator {
|
||||
let key : String
|
||||
var value : String
|
||||
|
||||
public init(_ key: String, _ value2: String)throws {
|
||||
var value2 = value2
|
||||
try Validate.notEmpty(string: key);
|
||||
try Validate.notEmpty(string: value2);
|
||||
|
||||
self.key = key.trim().lowercased();
|
||||
if (value2.startsWith("\"") && value2.hasSuffix("\"") || value2.startsWith("'") && value2.hasSuffix("'"))
|
||||
{
|
||||
value2 = value2.substring(1, value2.characters.count-2);
|
||||
}
|
||||
self.value = value2.trim().lowercased();
|
||||
}
|
||||
|
||||
open override func matches(_ root: Element, _ element: Element)throws->Bool{
|
||||
preconditionFailure("self method must be overridden")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluator for any / all element matching
|
||||
*/
|
||||
public final class AllElements : Evaluator {
|
||||
|
||||
open override func matches(_ root: Element, _ element: Element)throws->Bool {
|
||||
return true;
|
||||
}
|
||||
|
||||
open override func toString()->String {
|
||||
return "*";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluator for matching by sibling index number (e {@literal <} idx)
|
||||
*/
|
||||
public final class IndexLessThan : IndexEvaluator {
|
||||
public override init(_ index: Int) {
|
||||
super.init(index);
|
||||
}
|
||||
|
||||
open override func matches(_ root: Element, _ element: Element)throws->Bool {
|
||||
return try element.elementSiblingIndex() < index;
|
||||
}
|
||||
|
||||
open override func toString()->String {
|
||||
return ":lt(\(index))"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluator for matching by sibling index number (e {@literal >} idx)
|
||||
*/
|
||||
public final class IndexGreaterThan : IndexEvaluator {
|
||||
public override init(_ index: Int) {
|
||||
super.init(index);
|
||||
}
|
||||
|
||||
open override func matches(_ root: Element, _ element: Element)throws->Bool {
|
||||
return try element.elementSiblingIndex() > index;
|
||||
}
|
||||
|
||||
open override func toString()->String {
|
||||
return ":gt(\(index))"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluator for matching by sibling index number (e = idx)
|
||||
*/
|
||||
public final class IndexEquals : IndexEvaluator {
|
||||
public override init(_ index: Int) {
|
||||
super.init(index);
|
||||
}
|
||||
|
||||
open override func matches(_ root: Element, _ element: Element)throws->Bool {
|
||||
return try element.elementSiblingIndex() == index;
|
||||
}
|
||||
|
||||
open override func toString()->String {
|
||||
return ":eq(\(index))"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluator for matching the last sibling (css :last-child)
|
||||
*/
|
||||
public final class IsLastChild : Evaluator {
|
||||
open override func matches(_ root: Element, _ element: Element)throws->Bool {
|
||||
|
||||
if let p = element.parent(){
|
||||
let i = try element.elementSiblingIndex()
|
||||
return !((p as? Document) != nil) && i == (p.getChildNodes().count - 1)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
open override func toString()->String {
|
||||
return ":last-child";
|
||||
}
|
||||
}
|
||||
|
||||
public final class IsFirstOfType : IsNthOfType {
|
||||
public init() {
|
||||
super.init(0,1);
|
||||
}
|
||||
open override func toString()->String {
|
||||
return ":first-of-type";
|
||||
}
|
||||
}
|
||||
|
||||
public final class IsLastOfType : IsNthLastOfType {
|
||||
public init() {
|
||||
super.init(0,1);
|
||||
}
|
||||
open override func toString()->String {
|
||||
return ":last-of-type";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public class CssNthEvaluator : Evaluator {
|
||||
public let a : Int
|
||||
public let b : Int
|
||||
|
||||
public init(_ a: Int, _ b: Int) {
|
||||
self.a = a;
|
||||
self.b = b;
|
||||
}
|
||||
public init(_ b: Int) {
|
||||
self.a = 0;
|
||||
self.b = b;
|
||||
}
|
||||
|
||||
open override func matches(_ root: Element, _ element: Element)throws->Bool {
|
||||
let p: Element? = element.parent();
|
||||
if (p == nil || (((p as? Document) != nil))) {return false}
|
||||
|
||||
let pos: Int = try calculatePosition(root, element)
|
||||
if (a == 0) {return pos == b}
|
||||
|
||||
return (pos-b)*a >= 0 && (pos-b)%a==0
|
||||
}
|
||||
|
||||
open override func toString()->String {
|
||||
if (a == 0){
|
||||
return ":\(getPseudoClass)(\(b))"
|
||||
}
|
||||
if (b == 0){
|
||||
return ":\(getPseudoClass)(\(a))"
|
||||
}
|
||||
return ":\(getPseudoClass)(\(a)\(b))"
|
||||
}
|
||||
|
||||
open func getPseudoClass()->String{
|
||||
preconditionFailure("self method must be overridden")
|
||||
}
|
||||
open func calculatePosition(_ root: Element, _ element: Element)throws->Int{
|
||||
preconditionFailure("self method must be overridden")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* css-compatible Evaluator for :eq (css :nth-child)
|
||||
*
|
||||
* @see IndexEquals
|
||||
*/
|
||||
public final class IsNthChild : CssNthEvaluator {
|
||||
|
||||
public override init(_ a: Int, _ b: Int) {
|
||||
super.init(a,b);
|
||||
}
|
||||
|
||||
open override func calculatePosition(_ root: Element, _ element: Element)throws->Int {
|
||||
return try element.elementSiblingIndex()+1;
|
||||
}
|
||||
|
||||
|
||||
open override func getPseudoClass()->String {
|
||||
return "nth-child";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* css pseudo class :nth-last-child)
|
||||
*
|
||||
* @see IndexEquals
|
||||
*/
|
||||
public final class IsNthLastChild : CssNthEvaluator {
|
||||
public override init(_ a: Int, _ b: Int) {
|
||||
super.init(a,b);
|
||||
}
|
||||
|
||||
open override func calculatePosition(_ root: Element, _ element: Element)throws->Int
|
||||
{
|
||||
var i = 0
|
||||
|
||||
if let l = element.parent(){
|
||||
i = l.children().array().count
|
||||
}
|
||||
return i - (try element.elementSiblingIndex())
|
||||
}
|
||||
|
||||
open override func getPseudoClass()->String {
|
||||
return "nth-last-child";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* css pseudo class nth-of-type
|
||||
*
|
||||
*/
|
||||
public class IsNthOfType : CssNthEvaluator {
|
||||
public override init(_ a: Int, _ b: Int) {
|
||||
super.init(a,b);
|
||||
}
|
||||
|
||||
open override func calculatePosition(_ root: Element, _ element: Element)->Int {
|
||||
var pos = 0;
|
||||
let family: Elements? = element.parent()?.children();
|
||||
if let array = family?.array(){
|
||||
for el in array
|
||||
{
|
||||
if (el.tag() == element.tag()) {pos+=1}
|
||||
if (el === element) {break}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return pos;
|
||||
}
|
||||
|
||||
open override func getPseudoClass()->String {
|
||||
return "nth-of-type";
|
||||
}
|
||||
}
|
||||
|
||||
public class IsNthLastOfType : CssNthEvaluator {
|
||||
|
||||
public override init(_ a: Int, _ b: Int) {
|
||||
super.init(a, b)
|
||||
}
|
||||
|
||||
open override func calculatePosition(_ root: Element, _ element: Element)throws->Int {
|
||||
var pos = 0;
|
||||
if let family = element.parent()?.children(){
|
||||
let x = try element.elementSiblingIndex()
|
||||
for i in x..<family.array().count {
|
||||
if (family.get(i).tag() == element.tag()){
|
||||
pos+=1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return pos;
|
||||
}
|
||||
|
||||
open override func getPseudoClass()->String {
|
||||
return "nth-last-of-type";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluator for matching the first sibling (css :first-child)
|
||||
*/
|
||||
public final class IsFirstChild : Evaluator {
|
||||
open override func matches(_ root: Element, _ element: Element)throws->Bool {
|
||||
let p = element.parent();
|
||||
if(p != nil && !(((p as? Document) != nil))){
|
||||
return (try element.elementSiblingIndex()) == 0;
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
open override func toString()->String {
|
||||
return ":first-child";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* css3 pseudo-class :root
|
||||
* @see <a href="http://www.w3.org/TR/selectors/#root-pseudo">:root selector</a>
|
||||
*
|
||||
*/
|
||||
public final class IsRoot : Evaluator {
|
||||
open override func matches(_ root: Element, _ element: Element)throws->Bool {
|
||||
let r: Element = ((root as? Document) != nil) ? root.child(0) : root;
|
||||
return element === r;
|
||||
}
|
||||
open override func toString()->String {
|
||||
return ":root";
|
||||
}
|
||||
}
|
||||
|
||||
public final class IsOnlyChild : Evaluator {
|
||||
open override func matches(_ root: Element, _ element: Element)throws->Bool {
|
||||
let p = element.parent();
|
||||
return p != nil && !((p as? Document) != nil) && element.siblingElements().array().count == 0;
|
||||
}
|
||||
open override func toString()->String {
|
||||
return ":only-child";
|
||||
}
|
||||
}
|
||||
|
||||
public final class IsOnlyOfType : Evaluator {
|
||||
open override func matches(_ root: Element, _ element: Element)throws->Bool {
|
||||
let p = element.parent();
|
||||
if (p == nil || (p as? Document) != nil) {return false}
|
||||
|
||||
var pos = 0;
|
||||
if let family = p?.children().array(){
|
||||
for el in family {
|
||||
if (el.tag() == element.tag()) {pos+=1}
|
||||
}
|
||||
}
|
||||
return pos == 1;
|
||||
}
|
||||
open override func toString()->String {
|
||||
return ":only-of-type";
|
||||
}
|
||||
}
|
||||
|
||||
public final class IsEmpty : Evaluator {
|
||||
open override func matches(_ root: Element, _ element: Element)throws->Bool {
|
||||
let family: Array<Node> = element.getChildNodes();
|
||||
for n in family
|
||||
{
|
||||
if (!((n as? Comment) != nil || (n as? XmlDeclaration) != nil || (n as? DocumentType) != nil)) {return false}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
open override func toString()->String {
|
||||
return ":empty";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Abstract evaluator for sibling index matching
|
||||
*
|
||||
* @author ant
|
||||
*/
|
||||
public class IndexEvaluator : Evaluator {
|
||||
let index: Int
|
||||
|
||||
public init(_ index: Int) {
|
||||
self.index = index;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluator for matching Element (and its descendants) text
|
||||
*/
|
||||
public final class ContainsText : Evaluator {
|
||||
private let searchText: String;
|
||||
|
||||
public init(_ searchText: String) {
|
||||
self.searchText = searchText.lowercased();
|
||||
}
|
||||
|
||||
open override func matches(_ root: Element, _ element: Element)throws->Bool {
|
||||
return (try element.text().lowercased().contains(searchText));
|
||||
}
|
||||
|
||||
open override func toString()->String {
|
||||
return ":contains(\(searchText)"
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluator for matching Element's own text
|
||||
*/
|
||||
public final class ContainsOwnText : Evaluator {
|
||||
private let searchText : String;
|
||||
|
||||
public init(_ searchText: String) {
|
||||
self.searchText = searchText.lowercased();
|
||||
}
|
||||
|
||||
open override func matches(_ root: Element, _ element: Element)throws->Bool {
|
||||
return (element.ownText().lowercased().contains(searchText));
|
||||
}
|
||||
|
||||
open override func toString()->String {
|
||||
return ":containsOwn(\(searchText)"
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluator for matching Element (and its descendants) text with regex
|
||||
*/
|
||||
public final class Matches : Evaluator {
|
||||
private let pattern: Pattern;
|
||||
|
||||
public init(_ pattern: Pattern) {
|
||||
self.pattern = pattern;
|
||||
}
|
||||
|
||||
open override func matches(_ root: Element, _ element: Element)throws->Bool {
|
||||
let m = try pattern.matcher(in: element.text());
|
||||
return m.find();
|
||||
}
|
||||
|
||||
open override func toString()->String {
|
||||
return ":matches(\(pattern)"
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluator for matching Element's own text with regex
|
||||
*/
|
||||
public final class MatchesOwn : Evaluator {
|
||||
private let pattern: Pattern;
|
||||
|
||||
public init(_ pattern: Pattern) {
|
||||
self.pattern = pattern;
|
||||
}
|
||||
|
||||
open override func matches(_ root: Element, _ element: Element)throws->Bool {
|
||||
let m = pattern.matcher(in: element.ownText());
|
||||
return m.find();
|
||||
}
|
||||
|
||||
open override func toString()->String {
|
||||
return ":matchesOwn(\(pattern.toString())"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
//
|
||||
// NodeTraversor.swift
|
||||
// SwiftSoup
|
||||
//
|
||||
// Created by Nabil Chatbi on 17/10/16.
|
||||
// Copyright © 2016 Nabil Chatbi.. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
class NodeTraversor
|
||||
{
|
||||
private let visitor : NodeVisitor ;
|
||||
|
||||
/**
|
||||
* Create a new traversor.
|
||||
* @param visitor a class implementing the {@link NodeVisitor} interface, to be called when visiting each node.
|
||||
*/
|
||||
public init(_ visitor: NodeVisitor) {
|
||||
self.visitor = visitor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Start a depth-first traverse of the root and all of its descendants.
|
||||
* @param root the root node point to traverse.
|
||||
*/
|
||||
open func traverse(_ root: Node?)throws {
|
||||
var node : Node? = root;
|
||||
var depth : Int = 0;
|
||||
|
||||
while (node != nil) {
|
||||
try visitor.head(node!, depth);
|
||||
if (node!.childNodeSize() > 0) {
|
||||
node = node!.childNode(0);
|
||||
depth+=1;
|
||||
} else {
|
||||
while (node!.nextSibling() == nil && depth > 0) {
|
||||
try visitor.tail(node!, depth);
|
||||
node = node!.getParentNode();
|
||||
depth-=1;
|
||||
}
|
||||
try visitor.tail(node!, depth);
|
||||
if (node === root){
|
||||
break;
|
||||
}
|
||||
node = node!.nextSibling();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
//
|
||||
// NodeVisitor.swift
|
||||
// SwiftSoup
|
||||
//
|
||||
// Created by Nabil Chatbi on 16/10/16.
|
||||
// Copyright © 2016 Nabil Chatbi.. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
/**
|
||||
* Node visitor interface. Provide an implementing class to {@link NodeTraversor} to iterate through nodes.
|
||||
* <p>
|
||||
* This interface provides two methods, {@code head} and {@code tail}. The head method is called when the node is first
|
||||
* seen, and the tail method when all of the node's children have been visited. As an example, head can be used to
|
||||
* create a start tag for a node, and tail to create the end tag.
|
||||
* </p>
|
||||
*/
|
||||
public protocol NodeVisitor {
|
||||
/**
|
||||
* Callback for when a node is first visited.
|
||||
*
|
||||
* @param node the node being visited.
|
||||
* @param depth the depth of the node, relative to the root node. E.g., the root node has depth 0, and a child node
|
||||
* of that will have depth 1.
|
||||
*/
|
||||
func head(_ node: Node, _ depth: Int)throws
|
||||
|
||||
/**
|
||||
* Callback for when a node is last visited, after all of its descendants have been visited.
|
||||
*
|
||||
* @param node the node being visited.
|
||||
* @param depth the depth of the node, relative to the root node. E.g., the root node has depth 0, and a child node
|
||||
* of that will have depth 1.
|
||||
*/
|
||||
func tail(_ node: Node, _ depth: Int)throws
|
||||
}
|
|
@ -0,0 +1,398 @@
|
|||
//
|
||||
// QueryParser.swift
|
||||
// SwiftSoup
|
||||
//
|
||||
// Created by Nabil Chatbi on 23/10/16.
|
||||
// Copyright © 2016 Nabil Chatbi.. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
/**
|
||||
* Parses a CSS selector into an Evaluator tree.
|
||||
*/
|
||||
public class QueryParser {
|
||||
private static let combinators : [String] = [",", ">", "+", "~", " "]
|
||||
private static let AttributeEvals : [String] = ["=", "!=", "^=", "$=", "*=", "~="]
|
||||
|
||||
private var tq: TokenQueue;
|
||||
private var query: String;
|
||||
private var evals: Array<Evaluator> = Array<Evaluator>();
|
||||
|
||||
/**
|
||||
* Create a new QueryParser.
|
||||
* @param query CSS query
|
||||
*/
|
||||
private init(_ query: String) {
|
||||
self.query = query;
|
||||
self.tq = TokenQueue(query);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a CSS query into an Evaluator.
|
||||
* @param query CSS query
|
||||
* @return Evaluator
|
||||
*/
|
||||
public static func parse(_ query: String)throws->Evaluator {
|
||||
let p = QueryParser(query);
|
||||
return try p.parse();
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the query
|
||||
* @return Evaluator
|
||||
*/
|
||||
public func parse()throws->Evaluator {
|
||||
tq.consumeWhitespace();
|
||||
|
||||
if (tq.matchesAny(QueryParser.combinators)) { // if starts with a combinator, use root as elements
|
||||
evals.append( StructuralEvaluator.Root());
|
||||
try combinator(tq.consume());
|
||||
} else {
|
||||
try findElements();
|
||||
}
|
||||
|
||||
while (!tq.isEmpty()) {
|
||||
// hierarchy and extras
|
||||
let seenWhite: Bool = tq.consumeWhitespace();
|
||||
|
||||
if (tq.matchesAny(QueryParser.combinators)) {
|
||||
try combinator(tq.consume());
|
||||
} else if (seenWhite) {
|
||||
try combinator(" " as Character);
|
||||
} else { // E.class, E#id, E[attr] etc. AND
|
||||
try findElements(); // take next el, #. etc off queue
|
||||
}
|
||||
}
|
||||
|
||||
if (evals.count == 1){
|
||||
return evals[0]
|
||||
}
|
||||
return CombiningEvaluator.And(evals);
|
||||
}
|
||||
|
||||
private func combinator(_ combinator: Character)throws {
|
||||
tq.consumeWhitespace();
|
||||
let subQuery: String = consumeSubQuery(); // support multi > childs
|
||||
|
||||
var rootEval: Evaluator? // the new topmost evaluator
|
||||
var currentEval: Evaluator? // the evaluator the new eval will be combined to. could be root, or rightmost or.
|
||||
let newEval: Evaluator = try QueryParser.parse(subQuery); // the evaluator to add into target evaluator
|
||||
var replaceRightMost: Bool = false;
|
||||
|
||||
if (evals.count == 1) {
|
||||
currentEval = evals[0]
|
||||
rootEval = currentEval
|
||||
// make sure OR (,) has precedence:
|
||||
if (((rootEval as? CombiningEvaluator.Or) != nil) && combinator != ",") {
|
||||
currentEval = (currentEval as! CombiningEvaluator.Or).rightMostEvaluator();
|
||||
replaceRightMost = true;
|
||||
}
|
||||
}
|
||||
else {
|
||||
currentEval = CombiningEvaluator.And(evals)
|
||||
rootEval = currentEval
|
||||
}
|
||||
evals.removeAll()
|
||||
|
||||
// for most combinators: change the current eval into an AND of the current eval and the new eval
|
||||
if (combinator == ">")
|
||||
{currentEval = CombiningEvaluator.And(newEval, StructuralEvaluator.ImmediateParent(currentEval!));}
|
||||
else if (combinator == " ")
|
||||
{currentEval = CombiningEvaluator.And(newEval, StructuralEvaluator.Parent(currentEval!))}
|
||||
else if (combinator == "+")
|
||||
{currentEval = CombiningEvaluator.And(newEval, StructuralEvaluator.ImmediatePreviousSibling(currentEval!))}
|
||||
else if (combinator == "~")
|
||||
{currentEval = CombiningEvaluator.And(newEval, StructuralEvaluator.PreviousSibling(currentEval!))}
|
||||
else if (combinator == ",") { // group or.
|
||||
let or : CombiningEvaluator.Or
|
||||
if ((currentEval as? CombiningEvaluator.Or) != nil) {
|
||||
or = currentEval as! CombiningEvaluator.Or
|
||||
or.add(newEval);
|
||||
} else {
|
||||
or = CombiningEvaluator.Or();
|
||||
or.add(currentEval!);
|
||||
or.add(newEval);
|
||||
}
|
||||
currentEval = or;
|
||||
}
|
||||
else{
|
||||
throw Exception.Error(type: ExceptionType.SelectorParseException, Message: "Unknown combinator: \(String(combinator))")
|
||||
}
|
||||
|
||||
|
||||
if (replaceRightMost)
|
||||
{
|
||||
(rootEval as! CombiningEvaluator.Or).replaceRightMostEvaluator(currentEval!)
|
||||
}
|
||||
else {
|
||||
rootEval = currentEval
|
||||
}
|
||||
evals.append(rootEval!);
|
||||
}
|
||||
|
||||
|
||||
private func consumeSubQuery()->String {
|
||||
let sq : StringBuilder = StringBuilder();
|
||||
while (!tq.isEmpty()) {
|
||||
if (tq.matches("(")){
|
||||
sq.append("(").append(tq.chompBalanced("(", ")")).append(")");
|
||||
}else if (tq.matches("[")){
|
||||
sq.append("[").append(tq.chompBalanced("[", "]")).append("]");
|
||||
}else if (tq.matchesAny(QueryParser.combinators)){
|
||||
break;
|
||||
}else{
|
||||
sq.append(tq.consume());
|
||||
}
|
||||
}
|
||||
return sq.toString();
|
||||
}
|
||||
|
||||
private func findElements()throws {
|
||||
if (tq.matchChomp("#"))
|
||||
{
|
||||
try byId()
|
||||
}else if (tq.matchChomp("."))
|
||||
{
|
||||
try byClass()}
|
||||
else if (tq.matchesWord() || tq.matches("*|"))
|
||||
{try byTag()}
|
||||
else if (tq.matches("["))
|
||||
{try byAttribute()}
|
||||
else if (tq.matchChomp("*"))
|
||||
{ allElements()}
|
||||
else if (tq.matchChomp(":lt("))
|
||||
{try indexLessThan()}
|
||||
else if (tq.matchChomp(":gt("))
|
||||
{try indexGreaterThan()}
|
||||
else if (tq.matchChomp(":eq("))
|
||||
{try indexEquals()}
|
||||
else if (tq.matches(":has("))
|
||||
{try has()}
|
||||
else if (tq.matches(":contains("))
|
||||
{try contains(false)}
|
||||
else if (tq.matches(":containsOwn("))
|
||||
{try contains(true)}
|
||||
else if (tq.matches(":matches("))
|
||||
{try matches(false)}
|
||||
else if (tq.matches(":matchesOwn("))
|
||||
{try matches(true)}
|
||||
else if (tq.matches(":not("))
|
||||
{try not()}
|
||||
else if (tq.matchChomp(":nth-child("))
|
||||
{try cssNthChild(false, false)}
|
||||
else if (tq.matchChomp(":nth-last-child("))
|
||||
{try cssNthChild(true, false)}
|
||||
else if (tq.matchChomp(":nth-of-type("))
|
||||
{try cssNthChild(false, true)}
|
||||
else if (tq.matchChomp(":nth-last-of-type("))
|
||||
{try cssNthChild(true, true)}
|
||||
else if (tq.matchChomp(":first-child"))
|
||||
{evals.append(Evaluator.IsFirstChild())}
|
||||
else if (tq.matchChomp(":last-child"))
|
||||
{evals.append(Evaluator.IsLastChild())}
|
||||
else if (tq.matchChomp(":first-of-type"))
|
||||
{evals.append(Evaluator.IsFirstOfType())}
|
||||
else if (tq.matchChomp(":last-of-type"))
|
||||
{evals.append(Evaluator.IsLastOfType())}
|
||||
else if (tq.matchChomp(":only-child"))
|
||||
{evals.append(Evaluator.IsOnlyChild())}
|
||||
else if (tq.matchChomp(":only-of-type"))
|
||||
{evals.append(Evaluator.IsOnlyOfType())}
|
||||
else if (tq.matchChomp(":empty"))
|
||||
{evals.append(Evaluator.IsEmpty())}
|
||||
else if (tq.matchChomp(":root"))
|
||||
{evals.append(Evaluator.IsRoot())}
|
||||
else // unhandled
|
||||
{
|
||||
throw Exception.Error(type: ExceptionType.SelectorParseException, Message:"Could not parse query \(query): unexpected token at \(tq.remainder())")
|
||||
}
|
||||
}
|
||||
|
||||
private func byId()throws {
|
||||
let id: String = tq.consumeCssIdentifier();
|
||||
try Validate.notEmpty(string: id);
|
||||
evals.append(Evaluator.Id(id));
|
||||
}
|
||||
|
||||
private func byClass()throws {
|
||||
let className: String = tq.consumeCssIdentifier();
|
||||
try Validate.notEmpty(string: className);
|
||||
evals.append(Evaluator.Class(className.trim()))
|
||||
}
|
||||
|
||||
private func byTag()throws {
|
||||
var tagName = tq.consumeElementSelector();
|
||||
|
||||
try Validate.notEmpty(string: tagName);
|
||||
|
||||
// namespaces: wildcard match equals(tagName) or ending in ":"+tagName
|
||||
if (tagName.startsWith("*|")) {
|
||||
evals.append(
|
||||
CombiningEvaluator.Or(
|
||||
Evaluator.Tag(tagName.trim().lowercased()),
|
||||
Evaluator.TagEndsWith(tagName.replacingOccurrences(of: "*|", with: ":").trim().lowercased())))
|
||||
} else {
|
||||
// namespaces: if element name is "abc:def", selector must be "abc|def", so flip:
|
||||
if (tagName.contains("|")){
|
||||
tagName = tagName.replacingOccurrences(of: "|", with: ":")
|
||||
}
|
||||
|
||||
evals.append(Evaluator.Tag(tagName.trim()))
|
||||
}
|
||||
}
|
||||
|
||||
private func byAttribute()throws {
|
||||
let cq: TokenQueue = TokenQueue(tq.chompBalanced("[", "]")); // content queue
|
||||
let key: String = cq.consumeToAny(QueryParser.AttributeEvals); // eq, not, start, end, contain, match, (no val)
|
||||
try Validate.notEmpty(string: key);
|
||||
cq.consumeWhitespace();
|
||||
|
||||
if (cq.isEmpty()) {
|
||||
if (key.startsWith("^")){
|
||||
evals.append(try Evaluator.AttributeStarting(key.substring(1)));
|
||||
}else{
|
||||
evals.append(Evaluator.Attribute(key));
|
||||
}
|
||||
} else {
|
||||
if (cq.matchChomp("=")){
|
||||
evals.append(try Evaluator.AttributeWithValue(key, cq.remainder()));
|
||||
}
|
||||
|
||||
else if (cq.matchChomp("!=")){
|
||||
evals.append(try Evaluator.AttributeWithValueNot(key, cq.remainder()));
|
||||
}
|
||||
|
||||
else if (cq.matchChomp("^=")){
|
||||
evals.append(try Evaluator.AttributeWithValueStarting(key, cq.remainder()));
|
||||
}
|
||||
|
||||
else if (cq.matchChomp("$=")){
|
||||
evals.append(try Evaluator.AttributeWithValueEnding(key, cq.remainder()));
|
||||
}
|
||||
|
||||
else if (cq.matchChomp("*=")){
|
||||
evals.append(try Evaluator.AttributeWithValueContaining(key, cq.remainder()));
|
||||
}
|
||||
|
||||
else if (cq.matchChomp("~=")){
|
||||
evals.append( Evaluator.AttributeWithValueMatching(key, Pattern.compile(cq.remainder())));
|
||||
}else{
|
||||
throw Exception.Error(type: ExceptionType.SelectorParseException, Message:"Could not parse attribute query '\(query)': unexpected token at '\(cq.remainder())'")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func allElements() {
|
||||
evals.append(Evaluator.AllElements());
|
||||
}
|
||||
|
||||
// pseudo selectors :lt, :gt, :eq
|
||||
private func indexLessThan()throws {
|
||||
evals.append(Evaluator.IndexLessThan(try consumeIndex()));
|
||||
}
|
||||
|
||||
private func indexGreaterThan()throws {
|
||||
evals.append(Evaluator.IndexGreaterThan(try consumeIndex()));
|
||||
}
|
||||
|
||||
private func indexEquals()throws {
|
||||
evals.append(Evaluator.IndexEquals(try consumeIndex()));
|
||||
}
|
||||
|
||||
//pseudo selectors :first-child, :last-child, :nth-child, ...
|
||||
private static let NTH_AB: Pattern = Pattern.compile("((\\+|-)?(\\d+)?)n(\\s*(\\+|-)?\\s*\\d+)?", Pattern.CASE_INSENSITIVE);
|
||||
private static let NTH_B: Pattern = Pattern.compile("(\\+|-)?(\\d+)");
|
||||
|
||||
private func cssNthChild(_ backwards: Bool, _ ofType: Bool)throws {
|
||||
let argS: String = tq.chompTo(")").trim().lowercased();
|
||||
let mAB: Matcher = QueryParser.NTH_AB.matcher(in: argS);
|
||||
let mB: Matcher = QueryParser.NTH_B.matcher(in: argS);
|
||||
var a: Int
|
||||
var b: Int
|
||||
if ("odd"==argS) {
|
||||
a = 2;
|
||||
b = 1;
|
||||
} else if ("even"==argS) {
|
||||
a = 2;
|
||||
b = 0;
|
||||
} else if (mAB.matches.count > 0) {
|
||||
mAB.find()
|
||||
a = mAB.group(3) != nil ? Int(mAB.group(1)!.replaceFirst(of: "^\\+", with: ""))! : 1;
|
||||
b = mAB.group(4) != nil ? Int(mAB.group(4)!.replaceFirst(of: "^\\+", with: ""))! : 0;
|
||||
} else if (mB.matches.count > 0) {
|
||||
a = 0;
|
||||
mB.find()
|
||||
b = Int(mB.group()!.replaceFirst(of: "^\\+", with: ""))!;
|
||||
} else {
|
||||
throw Exception.Error(type: ExceptionType.SelectorParseException, Message:"Could not parse nth-index '\(argS)': unexpected format")
|
||||
}
|
||||
if (ofType){
|
||||
if (backwards){
|
||||
evals.append(Evaluator.IsNthLastOfType(a, b));
|
||||
}else{
|
||||
evals.append(Evaluator.IsNthOfType(a, b));
|
||||
}
|
||||
}else {
|
||||
if (backwards){
|
||||
evals.append(Evaluator.IsNthLastChild(a, b));
|
||||
}else{
|
||||
evals.append(Evaluator.IsNthChild(a, b));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
private func consumeIndex()throws->Int {
|
||||
let indexS: String = tq.chompTo(")").trim();
|
||||
try Validate.isTrue(val: StringUtil.isNumeric(indexS), msg: "Index must be numeric");
|
||||
return Int(indexS)!
|
||||
}
|
||||
|
||||
// pseudo selector :has(el)
|
||||
private func has()throws {
|
||||
try tq.consume(":has");
|
||||
let subQuery: String = tq.chompBalanced("(", ")");
|
||||
try Validate.notEmpty(string: subQuery, msg: ":has(el) subselect must not be empty");
|
||||
evals.append(StructuralEvaluator.Has(try QueryParser.parse(subQuery)));
|
||||
}
|
||||
|
||||
// pseudo selector :contains(text), containsOwn(text)
|
||||
private func contains(_ own: Bool)throws {
|
||||
try tq.consume(own ? ":containsOwn" : ":contains");
|
||||
let searchText: String = TokenQueue.unescape(tq.chompBalanced("(", ")"));
|
||||
try Validate.notEmpty(string: searchText, msg: ":contains(text) query must not be empty");
|
||||
if (own){
|
||||
evals.append(Evaluator.ContainsOwnText(searchText));
|
||||
}else{
|
||||
evals.append(Evaluator.ContainsText(searchText));
|
||||
}
|
||||
}
|
||||
|
||||
// :matches(regex), matchesOwn(regex)
|
||||
private func matches(_ own: Bool)throws {
|
||||
try tq.consume(own ? ":matchesOwn" : ":matches");
|
||||
let regex: String = tq.chompBalanced("(", ")"); // don't unescape, as regex bits will be escaped
|
||||
try Validate.notEmpty(string: regex, msg: ":matches(regex) query must not be empty");
|
||||
|
||||
if (own){
|
||||
evals.append(Evaluator.MatchesOwn(Pattern.compile(regex)));
|
||||
}else{
|
||||
evals.append(Evaluator.Matches(Pattern.compile(regex)));
|
||||
}
|
||||
}
|
||||
|
||||
// :not(selector)
|
||||
private func not()throws {
|
||||
try tq.consume(":not");
|
||||
let subQuery: String = tq.chompBalanced("(", ")");
|
||||
try Validate.notEmpty(string: subQuery, msg: ":not(selector) subselect must not be empty");
|
||||
|
||||
evals.append(StructuralEvaluator.Not(try QueryParser.parse(subQuery)));
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,165 @@
|
|||
//
|
||||
// Selector.swift
|
||||
// SwiftSoup
|
||||
//
|
||||
// Created by Nabil Chatbi on 21/10/16.
|
||||
// Copyright © 2016 Nabil Chatbi.. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
/**
|
||||
* CSS-like element selector, that finds elements matching a query.
|
||||
*
|
||||
* <h2>Selector syntax</h2>
|
||||
* <p>
|
||||
* A selector is a chain of simple selectors, separated by combinators. Selectors are <b>case insensitive</b> (including against
|
||||
* elements, attributes, and attribute values).
|
||||
* </p>
|
||||
* <p>
|
||||
* The universal selector (*) is implicit when no element selector is supplied (i.e. {@code *.header} and {@code .header}
|
||||
* is equivalent).
|
||||
* </p>
|
||||
* <table summary="">
|
||||
* <tr><th align="left">Pattern</th><th align="left">Matches</th><th align="left">Example</th></tr>
|
||||
* <tr><td><code>*</code></td><td>any element</td><td><code>*</code></td></tr>
|
||||
* <tr><td><code>tag</code></td><td>elements with the given tag name</td><td><code>div</code></td></tr>
|
||||
* <tr><td><code>*|E</code></td><td>elements of type E in any namespace <i>ns</i></td><td><code>*|name</code> finds <code><fb:name></code> elements</td></tr>
|
||||
* <tr><td><code>ns|E</code></td><td>elements of type E in the namespace <i>ns</i></td><td><code>fb|name</code> finds <code><fb:name></code> elements</td></tr>
|
||||
* <tr><td><code>#id</code></td><td>elements with attribute ID of "id"</td><td><code>div#wrap</code>, <code>#logo</code></td></tr>
|
||||
* <tr><td><code>.class</code></td><td>elements with a class name of "class"</td><td><code>div.left</code>, <code>.result</code></td></tr>
|
||||
* <tr><td><code>[attr]</code></td><td>elements with an attribute named "attr" (with any value)</td><td><code>a[href]</code>, <code>[title]</code></td></tr>
|
||||
* <tr><td><code>[^attrPrefix]</code></td><td>elements with an attribute name starting with "attrPrefix". Use to find elements with HTML5 datasets</td><td><code>[^data-]</code>, <code>div[^data-]</code></td></tr>
|
||||
* <tr><td><code>[attr=val]</code></td><td>elements with an attribute named "attr", and value equal to "val"</td><td><code>img[width=500]</code>, <code>a[rel=nofollow]</code></td></tr>
|
||||
* <tr><td><code>[attr="val"]</code></td><td>elements with an attribute named "attr", and value equal to "val"</td><td><code>span[hello="Cleveland"][goodbye="Columbus"]</code>, <code>a[rel="nofollow"]</code></td></tr>
|
||||
* <tr><td><code>[attr^=valPrefix]</code></td><td>elements with an attribute named "attr", and value starting with "valPrefix"</td><td><code>a[href^=http:]</code></td></tr>
|
||||
* <tr><td><code>[attr$=valSuffix]</code></td><td>elements with an attribute named "attr", and value ending with "valSuffix"</td><td><code>img[src$=.png]</code></td></tr>
|
||||
* <tr><td><code>[attr*=valContaining]</code></td><td>elements with an attribute named "attr", and value containing "valContaining"</td><td><code>a[href*=/search/]</code></td></tr>
|
||||
* <tr><td><code>[attr~=<em>regex</em>]</code></td><td>elements with an attribute named "attr", and value matching the regular expression</td><td><code>img[src~=(?i)\\.(png|jpe?g)]</code></td></tr>
|
||||
* <tr><td></td><td>The above may be combined in any order</td><td><code>div.header[title]</code></td></tr>
|
||||
* <tr><td><td colspan="3"><h3>Combinators</h3></td></tr>
|
||||
* <tr><td><code>E F</code></td><td>an F element descended from an E element</td><td><code>div a</code>, <code>.logo h1</code></td></tr>
|
||||
* <tr><td><code>E {@literal >} F</code></td><td>an F direct child of E</td><td><code>ol {@literal >} li</code></td></tr>
|
||||
* <tr><td><code>E + F</code></td><td>an F element immediately preceded by sibling E</td><td><code>li + li</code>, <code>div.head + div</code></td></tr>
|
||||
* <tr><td><code>E ~ F</code></td><td>an F element preceded by sibling E</td><td><code>h1 ~ p</code></td></tr>
|
||||
* <tr><td><code>E, F, G</code></td><td>all matching elements E, F, or G</td><td><code>a[href], div, h3</code></td></tr>
|
||||
* <tr><td><td colspan="3"><h3>Pseudo selectors</h3></td></tr>
|
||||
* <tr><td><code>:lt(<em>n</em>)</code></td><td>elements whose sibling index is less than <em>n</em></td><td><code>td:lt(3)</code> finds the first 3 cells of each row</td></tr>
|
||||
* <tr><td><code>:gt(<em>n</em>)</code></td><td>elements whose sibling index is greater than <em>n</em></td><td><code>td:gt(1)</code> finds cells after skipping the first two</td></tr>
|
||||
* <tr><td><code>:eq(<em>n</em>)</code></td><td>elements whose sibling index is equal to <em>n</em></td><td><code>td:eq(0)</code> finds the first cell of each row</td></tr>
|
||||
* <tr><td><code>:has(<em>selector</em>)</code></td><td>elements that contains at least one element matching the <em>selector</em></td><td><code>div:has(p)</code> finds divs that contain p elements </td></tr>
|
||||
* <tr><td><code>:not(<em>selector</em>)</code></td><td>elements that do not match the <em>selector</em>. See also {@link Elements#not(String)}</td><td><code>div:not(.logo)</code> finds all divs that do not have the "logo" class.<p><code>div:not(:has(div))</code> finds divs that do not contain divs.</p></td></tr>
|
||||
* <tr><td><code>:contains(<em>text</em>)</code></td><td>elements that contains the specified text. The search is case insensitive. The text may appear in the found element, or any of its descendants.</td><td><code>p:contains(jsoup)</code> finds p elements containing the text "jsoup".</td></tr>
|
||||
* <tr><td><code>:matches(<em>regex</em>)</code></td><td>elements whose text matches the specified regular expression. The text may appear in the found element, or any of its descendants.</td><td><code>td:matches(\\d+)</code> finds table cells containing digits. <code>div:matches((?i)login)</code> finds divs containing the text, case insensitively.</td></tr>
|
||||
* <tr><td><code>:containsOwn(<em>text</em>)</code></td><td>elements that directly contain the specified text. The search is case insensitive. The text must appear in the found element, not any of its descendants.</td><td><code>p:containsOwn(jsoup)</code> finds p elements with own text "jsoup".</td></tr>
|
||||
* <tr><td><code>:matchesOwn(<em>regex</em>)</code></td><td>elements whose own text matches the specified regular expression. The text must appear in the found element, not any of its descendants.</td><td><code>td:matchesOwn(\\d+)</code> finds table cells directly containing digits. <code>div:matchesOwn((?i)login)</code> finds divs containing the text, case insensitively.</td></tr>
|
||||
* <tr><td></td><td>The above may be combined in any order and with other selectors</td><td><code>.light:contains(name):eq(0)</code></td></tr>
|
||||
* <tr><td colspan="3"><h3>Structural pseudo selectors</h3></td></tr>
|
||||
* <tr><td><code>:root</code></td><td>The element that is the root of the document. In HTML, this is the <code>html</code> element</td><td><code>:root</code></td></tr>
|
||||
* <tr><td><code>:nth-child(<em>a</em>n+<em>b</em>)</code></td><td><p>elements that have <code><em>a</em>n+<em>b</em>-1</code> siblings <b>before</b> it in the document tree, for any positive integer or zero value of <code>n</code>, and has a parent element. For values of <code>a</code> and <code>b</code> greater than zero, this effectively divides the element's children into groups of a elements (the last group taking the remainder), and selecting the <em>b</em>th element of each group. For example, this allows the selectors to address every other row in a table, and could be used to alternate the color of paragraph text in a cycle of four. The <code>a</code> and <code>b</code> values must be integers (positive, negative, or zero). The index of the first child of an element is 1.</p>
|
||||
* In addition to this, <code>:nth-child()</code> can take <code>odd</code> and <code>even</code> as arguments instead. <code>odd</code> has the same signification as <code>2n+1</code>, and <code>even</code> has the same signification as <code>2n</code>.</td><td><code>tr:nth-child(2n+1)</code> finds every odd row of a table. <code>:nth-child(10n-1)</code> the 9th, 19th, 29th, etc, element. <code>li:nth-child(5)</code> the 5h li</td></tr>
|
||||
* <tr><td><code>:nth-last-child(<em>a</em>n+<em>b</em>)</code></td><td>elements that have <code><em>a</em>n+<em>b</em>-1</code> siblings <b>after</b> it in the document tree. Otherwise like <code>:nth-child()</code></td><td><code>tr:nth-last-child(-n+2)</code> the last two rows of a table</td></tr>
|
||||
* <tr><td><code>:nth-of-type(<em>a</em>n+<em>b</em>)</code></td><td>pseudo-class notation represents an element that has <code><em>a</em>n+<em>b</em>-1</code> siblings with the same expanded element name <em>before</em> it in the document tree, for any zero or positive integer value of n, and has a parent element</td><td><code>img:nth-of-type(2n+1)</code></td></tr>
|
||||
* <tr><td><code>:nth-last-of-type(<em>a</em>n+<em>b</em>)</code></td><td>pseudo-class notation represents an element that has <code><em>a</em>n+<em>b</em>-1</code> siblings with the same expanded element name <em>after</em> it in the document tree, for any zero or positive integer value of n, and has a parent element</td><td><code>img:nth-last-of-type(2n+1)</code></td></tr>
|
||||
* <tr><td><code>:first-child</code></td><td>elements that are the first child of some other element.</td><td><code>div {@literal >} p:first-child</code></td></tr>
|
||||
* <tr><td><code>:last-child</code></td><td>elements that are the last child of some other element.</td><td><code>ol {@literal >} li:last-child</code></td></tr>
|
||||
* <tr><td><code>:first-of-type</code></td><td>elements that are the first sibling of its type in the list of children of its parent element</td><td><code>dl dt:first-of-type</code></td></tr>
|
||||
* <tr><td><code>:last-of-type</code></td><td>elements that are the last sibling of its type in the list of children of its parent element</td><td><code>tr {@literal >} td:last-of-type</code></td></tr>
|
||||
* <tr><td><code>:only-child</code></td><td>elements that have a parent element and whose parent element hasve no other element children</td><td></td></tr>
|
||||
* <tr><td><code>:only-of-type</code></td><td> an element that has a parent element and whose parent element has no other element children with the same expanded element name</td><td></td></tr>
|
||||
* <tr><td><code>:empty</code></td><td>elements that have no children at all</td><td></td></tr>
|
||||
* </table>
|
||||
*
|
||||
* @see Element#select(String)
|
||||
*/
|
||||
open class Selector {
|
||||
private let evaluator : Evaluator ;
|
||||
private let root : Element ;
|
||||
|
||||
private init(_ query: String, _ root: Element)throws {
|
||||
let query = query.trim();
|
||||
try Validate.notEmpty(string: query);
|
||||
|
||||
self.evaluator = try QueryParser.parse(query);
|
||||
|
||||
self.root = root;
|
||||
}
|
||||
|
||||
private init(_ evaluator: Evaluator, _ root: Element) {
|
||||
self.evaluator = evaluator;
|
||||
self.root = root;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find elements matching selector.
|
||||
*
|
||||
* @param query CSS selector
|
||||
* @param root root element to descend into
|
||||
* @return matching elements, empty if none
|
||||
* @throws Selector.SelectorParseException (unchecked) on an invalid CSS query.
|
||||
*/
|
||||
public static func select(_ query: String, _ root: Element)throws->Elements {
|
||||
return try Selector(query, root).select();
|
||||
}
|
||||
|
||||
/**
|
||||
* Find elements matching selector.
|
||||
*
|
||||
* @param evaluator CSS selector
|
||||
* @param root root element to descend into
|
||||
* @return matching elements, empty if none
|
||||
*/
|
||||
public static func select(_ evaluator: Evaluator, _ root: Element)throws->Elements {
|
||||
return try Selector(evaluator, root).select();
|
||||
}
|
||||
|
||||
/**
|
||||
* Find elements matching selector.
|
||||
*
|
||||
* @param query CSS selector
|
||||
* @param roots root elements to descend into
|
||||
* @return matching elements, empty if none
|
||||
*/
|
||||
public static func select(_ query: String, _ roots: Array<Element>)throws->Elements {
|
||||
try Validate.notEmpty(string: query);
|
||||
let evaluator: Evaluator = try QueryParser.parse(query);
|
||||
var elements: Array<Element> = Array<Element>();
|
||||
var seenElements: Array<Element> = Array<Element>();
|
||||
// dedupe elements by identity, not equality
|
||||
|
||||
for root: Element in roots
|
||||
{
|
||||
let found: Elements = try select(evaluator, root);
|
||||
for el: Element in found.array()
|
||||
{
|
||||
if (!seenElements.contains(el)) {
|
||||
elements.append(el);
|
||||
seenElements.append(el)
|
||||
}
|
||||
}
|
||||
}
|
||||
return Elements(elements);
|
||||
}
|
||||
|
||||
private func select()throws->Elements {
|
||||
return try Collector.collect(evaluator, root);
|
||||
}
|
||||
|
||||
// exclude set. package open so that Elements can implement .not() selector.
|
||||
static func filterOut(_ elements: Array<Element>, _ outs: Array<Element>)->Elements {
|
||||
let output: Elements = Elements()
|
||||
for el: Element in elements {
|
||||
var found: Bool = false
|
||||
for out: Element in outs {
|
||||
if (el.equals(out)) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found){
|
||||
output.add(el)
|
||||
}
|
||||
}
|
||||
return output;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,178 @@
|
|||
//
|
||||
// StructuralEvaluator.swift
|
||||
// SwiftSoup
|
||||
//
|
||||
// Created by Nabil Chatbi on 23/10/16.
|
||||
// Copyright © 2016 Nabil Chatbi.. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
/**
|
||||
* Base structural evaluator.
|
||||
*/
|
||||
public class StructuralEvaluator : Evaluator {
|
||||
let evaluator: Evaluator
|
||||
|
||||
public init(_ evaluator: Evaluator) {
|
||||
self.evaluator = evaluator;
|
||||
}
|
||||
|
||||
public class Root : Evaluator {
|
||||
public override func matches(_ root: Element, _ element: Element)->Bool {
|
||||
return root === element;
|
||||
}
|
||||
}
|
||||
|
||||
public class Has : StructuralEvaluator {
|
||||
public override init(_ evaluator: Evaluator) {
|
||||
super.init(evaluator)
|
||||
}
|
||||
|
||||
public override func matches(_ root: Element, _ element: Element)throws->Bool {
|
||||
for e in try element.getAllElements().array() {
|
||||
do{
|
||||
if(e != element){
|
||||
if ((try evaluator.matches(root, e)))
|
||||
{
|
||||
return true
|
||||
}
|
||||
}
|
||||
}catch{}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
public override func toString()->String {
|
||||
return ":has(\(evaluator.toString()))"
|
||||
}
|
||||
}
|
||||
|
||||
public class Not : StructuralEvaluator {
|
||||
public override init(_ evaluator: Evaluator) {
|
||||
super.init(evaluator)
|
||||
}
|
||||
|
||||
public override func matches(_ root: Element, _ node: Element)->Bool {
|
||||
do{
|
||||
return try !evaluator.matches(root, node);
|
||||
}catch{}
|
||||
return false
|
||||
}
|
||||
|
||||
public override func toString()->String {
|
||||
return ":not\(evaluator.toString())"
|
||||
}
|
||||
}
|
||||
|
||||
public class Parent : StructuralEvaluator {
|
||||
public override init(_ evaluator: Evaluator) {
|
||||
super.init(evaluator)
|
||||
}
|
||||
|
||||
public override func matches(_ root: Element, _ element: Element)->Bool {
|
||||
if (root == element){
|
||||
return false;
|
||||
}
|
||||
|
||||
var parent = element.parent();
|
||||
while (true) {
|
||||
do{
|
||||
if parent != nil{
|
||||
if (try evaluator.matches(root, parent!)){
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}catch{}
|
||||
|
||||
if (parent == root){
|
||||
break;
|
||||
}
|
||||
parent = parent?.parent();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public override func toString()->String {
|
||||
return ":parent\(evaluator.toString())"
|
||||
}
|
||||
}
|
||||
|
||||
public class ImmediateParent : StructuralEvaluator {
|
||||
public override init(_ evaluator: Evaluator) {
|
||||
super.init(evaluator)
|
||||
}
|
||||
|
||||
public override func matches(_ root: Element, _ element: Element)->Bool {
|
||||
if (root == element){
|
||||
return false;
|
||||
}
|
||||
|
||||
if let parent = element.parent(){
|
||||
do{
|
||||
return try evaluator.matches(root, parent);
|
||||
}catch{}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
public override func toString()->String {
|
||||
return ":ImmediateParent\(evaluator.toString())"
|
||||
}
|
||||
}
|
||||
|
||||
public class PreviousSibling : StructuralEvaluator {
|
||||
public override init(_ evaluator: Evaluator) {
|
||||
super.init(evaluator)
|
||||
}
|
||||
|
||||
public override func matches(_ root: Element, _ element: Element)throws->Bool {
|
||||
if (root == element){
|
||||
return false;
|
||||
}
|
||||
|
||||
var prev = try element.previousElementSibling();
|
||||
|
||||
while (prev != nil) {
|
||||
do{
|
||||
if (try evaluator.matches(root, prev!)){
|
||||
return true;
|
||||
}
|
||||
}catch{}
|
||||
|
||||
prev = try prev!.previousElementSibling();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public override func toString()->String {
|
||||
return ":prev*\(evaluator.toString())"
|
||||
}
|
||||
}
|
||||
|
||||
class ImmediatePreviousSibling : StructuralEvaluator {
|
||||
public override init(_ evaluator: Evaluator) {
|
||||
super.init(evaluator)
|
||||
}
|
||||
|
||||
public override func matches(_ root: Element, _ element: Element)throws->Bool {
|
||||
if (root == element){
|
||||
return false;
|
||||
}
|
||||
|
||||
if let prev = try element.previousElementSibling(){
|
||||
do{
|
||||
return try evaluator.matches(root, prev)
|
||||
}catch{}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
public override func toString()->String {
|
||||
return ":prev\(evaluator.toString())"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,68 @@
|
|||
//
|
||||
// ArrayExt.swift
|
||||
// SwifSoup
|
||||
//
|
||||
// Created by Nabil Chatbi on 05/10/16.
|
||||
// Copyright © 2016 Nabil Chatbi.. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
extension Array
|
||||
{
|
||||
func binarySearch<T: Comparable>(_ collection: [T], _ target: T) -> Int {
|
||||
|
||||
let min = 0
|
||||
let max = collection.count - 1
|
||||
|
||||
return binaryMakeGuess(min: min, max: max, target: target, collection: collection)
|
||||
|
||||
}
|
||||
|
||||
|
||||
func binaryMakeGuess<T: Comparable>(min: Int, max: Int, target: T, collection: [T]) -> Int {
|
||||
|
||||
let guess = (min + max) / 2
|
||||
|
||||
if max < min {
|
||||
|
||||
// illegal, guess not in array
|
||||
return -1
|
||||
|
||||
} else if collection[guess] == target {
|
||||
|
||||
// guess is correct
|
||||
return guess
|
||||
|
||||
} else if collection[guess] > target {
|
||||
|
||||
// guess is too high
|
||||
return binaryMakeGuess(min: min, max: guess - 1, target: target, collection: collection)
|
||||
|
||||
} else {
|
||||
|
||||
// array[guess] < target
|
||||
// guess is too low
|
||||
return binaryMakeGuess(min: guess + 1, max: max, target: target, collection: collection)
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
extension Array where Element : Equatable
|
||||
{
|
||||
func lastIndexOf(_ e: Element) -> Int
|
||||
{
|
||||
for pos in (0..<self.count).reversed() {
|
||||
let next = self[pos]
|
||||
if (next == e) {
|
||||
return pos
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,200 @@
|
|||
//
|
||||
// CharacterExt.swift
|
||||
// SwifSoup
|
||||
//
|
||||
// Created by Nabil Chatbi on 08/10/16.
|
||||
// Copyright © 2016 Nabil Chatbi.. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
private let uppercaseSet = CharacterSet.uppercaseLetters
|
||||
private let lowercaseSet = CharacterSet.lowercaseLetters
|
||||
private let alphaSet = CharacterSet.letters
|
||||
private let alphaNumericSet = CharacterSet.alphanumerics
|
||||
private let symbolSet = CharacterSet.symbols
|
||||
private let digitSet = CharacterSet.decimalDigits
|
||||
|
||||
extension Character {
|
||||
|
||||
public static let BackslashF : Character = Character(UnicodeScalar(12))
|
||||
|
||||
//http://www.unicode.org/glossary/#supplementary_code_point
|
||||
public static let MIN_SUPPLEMENTARY_CODE_POINT : UInt32 = 0x010000;
|
||||
|
||||
/// The first `UnicodeScalar` of `self`.
|
||||
var unicodeScalar: UnicodeScalar {
|
||||
let unicodes = String(self).unicodeScalars
|
||||
return unicodes[unicodes.startIndex]
|
||||
}
|
||||
|
||||
/// True for any space character, and the control characters \t, \n, \r, \f, \v.
|
||||
var isWhitespace: Bool {
|
||||
|
||||
switch self {
|
||||
|
||||
case " ", "\t", "\n", "\r", "\r\n", Character.BackslashF: return true
|
||||
|
||||
case "\u{000B}", "\u{000C}": return true // Form Feed, vertical tab
|
||||
|
||||
default: return false
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// True for any Unicode space character, and the control characters \t, \n, \r, \f, \v.
|
||||
var isUnicodeSpace: Bool {
|
||||
|
||||
switch self {
|
||||
|
||||
case " ", "\t", "\n", "\r", "\r\n",Character.BackslashF: return true
|
||||
|
||||
case "\u{000C}", "\u{000B}", "\u{0085}": return true // Form Feed, vertical tab, next line (nel)
|
||||
|
||||
case "\u{00A0}", "\u{1680}", "\u{180E}": return true // No-break space, ogham space mark, mongolian vowel
|
||||
|
||||
case "\u{2000}"..."\u{200D}": return true // En quad, em quad, en space, em space, three-per-em space, four-per-em space, six-per-em space, figure space, ponctuation space, thin space, hair space, zero width space, zero width non-joiner, zero width joiner.
|
||||
case "\u{2028}", "\u{2029}": return true // Line separator, paragraph separator.
|
||||
|
||||
case "\u{202F}", "\u{205F}", "\u{2060}", "\u{3000}", "\u{FEFF}": return true // Narrow no-break space, medium mathematical space, word joiner, ideographic space, zero width no-break space.
|
||||
|
||||
default: return false
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// `true` if `self` normalized contains a single code unit that is in the categories of Uppercase and Titlecase Letters.
|
||||
var isUppercase: Bool {
|
||||
|
||||
return isMemberOfCharacterSet(uppercaseSet)
|
||||
|
||||
}
|
||||
|
||||
/// `true` if `self` normalized contains a single code unit that is in the category of Lowercase Letters.
|
||||
var isLowercase: Bool {
|
||||
|
||||
return isMemberOfCharacterSet(lowercaseSet)
|
||||
|
||||
}
|
||||
|
||||
/// `true` if `self` normalized contains a single code unit that is in the categories of Letters and Marks.
|
||||
var isAlpha: Bool {
|
||||
|
||||
return isMemberOfCharacterSet(alphaSet)
|
||||
|
||||
}
|
||||
|
||||
/// `true` if `self` normalized contains a single code unit that is in th categories of Letters, Marks, and Numbers.
|
||||
var isAlphaNumeric: Bool {
|
||||
|
||||
return isMemberOfCharacterSet(alphaNumericSet)
|
||||
|
||||
}
|
||||
|
||||
/// `true` if `self` normalized contains a single code unit that is in the category of Symbols. These characters include, for example, the dollar sign ($) and the plus (+) sign.
|
||||
var isSymbol: Bool {
|
||||
|
||||
return isMemberOfCharacterSet(symbolSet)
|
||||
|
||||
}
|
||||
|
||||
/// `true` if `self` normalized contains a single code unit that is in the category of Decimal Numbers.
|
||||
var isDigit: Bool {
|
||||
|
||||
return isMemberOfCharacterSet(digitSet)
|
||||
|
||||
}
|
||||
|
||||
/// `true` if `self` is an ASCII decimal digit, i.e. between "0" and "9".
|
||||
var isDecimalDigit: Bool {
|
||||
|
||||
return "0123456789".characters.contains(self)
|
||||
|
||||
}
|
||||
|
||||
/// `true` if `self` is an ASCII hexadecimal digit, i.e. "0"..."9", "a"..."f", "A"..."F".
|
||||
var isHexadecimalDigit: Bool {
|
||||
|
||||
return "01234567890abcdefABCDEF".characters.contains(self)
|
||||
|
||||
}
|
||||
|
||||
/// `true` if `self` is an ASCII octal digit, i.e. between '0' and '7'.
|
||||
var isOctalDigit: Bool {
|
||||
|
||||
return "01234567".characters.contains(self)
|
||||
|
||||
}
|
||||
|
||||
/// Lowercase `self`.
|
||||
var lowercase: Character {
|
||||
|
||||
let str = String(self).lowercased()
|
||||
return str[str.startIndex]
|
||||
|
||||
}
|
||||
|
||||
public func isChar(inSet set: CharacterSet) -> Bool {
|
||||
var found = true
|
||||
for ch in String(self).utf16 {
|
||||
if !set.contains(UnicodeScalar(ch)!) { found = false }
|
||||
}
|
||||
return found
|
||||
}
|
||||
|
||||
/// Uppercase `self`.
|
||||
var uppercase: Character {
|
||||
|
||||
let str = String(self).uppercased()
|
||||
return str[str.startIndex]
|
||||
|
||||
}
|
||||
|
||||
/// Return `true` if `self` normalized contains a single code unit that is a member of the supplied character set.
|
||||
///
|
||||
/// - parameter set: The `NSCharacterSet` used to test for membership.
|
||||
/// - returns: `true` if `self` normalized contains a single code unit that is a member of the supplied character set.
|
||||
func isMemberOfCharacterSet(_ set: CharacterSet) -> Bool {
|
||||
|
||||
let normalized = String(self).precomposedStringWithCanonicalMapping
|
||||
let unicodes = normalized.unicodeScalars
|
||||
|
||||
guard unicodes.count == 1 else { return false }
|
||||
return set.contains(UnicodeScalar(unicodes.first!.value)!)
|
||||
|
||||
}
|
||||
|
||||
public static func convertFromIntegerLiteral(value: IntegerLiteralType) -> Character {
|
||||
return Character(UnicodeScalar(value)!)
|
||||
}
|
||||
|
||||
func unicodeScalarCodePoint() -> UInt32
|
||||
{
|
||||
return unicodeScalar.value
|
||||
}
|
||||
|
||||
static func charCount(codePoint: UInt32) -> Int {
|
||||
return codePoint >= MIN_SUPPLEMENTARY_CODE_POINT ? 2 : 1;
|
||||
}
|
||||
|
||||
static func isLetter(_ char: Character)->Bool{
|
||||
return char.isLetter()
|
||||
}
|
||||
func isLetter()->Bool{
|
||||
return self.isMemberOfCharacterSet(CharacterSet.letters)
|
||||
}
|
||||
|
||||
static func isLetterOrDigit(_ char: Character)->Bool
|
||||
{
|
||||
return char.isLetterOrDigit()
|
||||
}
|
||||
func isLetterOrDigit()->Bool
|
||||
{
|
||||
if(self.isLetter()) {return true}
|
||||
return self.isDigit;
|
||||
}
|
||||
}
|
||||
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue