├── .gitattributes ├── .gitignore ├── .travis.yml ├── Example ├── Example.xcodeproj │ ├── project.pbxproj │ └── project.xcworkspace │ │ └── contents.xcworkspacedata ├── Prefix.pch ├── main.m └── nutrition.xml ├── LICENSE ├── Ono.playground ├── Contents.swift ├── Resources │ └── nutrition.xml └── contents.xcplayground ├── Ono.podspec ├── Ono.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist └── xcshareddata │ └── xcschemes │ ├── Ono iOS.xcscheme │ ├── Ono macOS.xcscheme │ └── Ono tvOS.xcscheme ├── Ono.xcworkspace ├── contents.xcworkspacedata └── xcshareddata │ └── IDEWorkspaceChecks.plist ├── README.md ├── Source ├── Info.plist ├── ONOXMLDocument.h ├── ONOXMLDocument.m └── Ono.h └── Tests ├── Info.plist ├── ONOAtomTests.m ├── ONOCSSTests.m ├── ONODefaultNamespaceXPathTests.m ├── ONOHTMLTests.m ├── ONOVMAPTests.m ├── ONOXMLTests.m ├── ONOXPathFunctionResultTests.m ├── atom.xml ├── ocf.xml ├── vmap.xml ├── web.html └── xml.xml /.gitattributes: -------------------------------------------------------------------------------- 1 | *.html linguist-detectable=false 2 | *.xml linguist-detectable=false 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | *.pbxuser 3 | !default.pbxuser 4 | *.mode1v3 5 | !default.mode1v3 6 | *.mode2v3 7 | !default.mode2v3 8 | *.perspectivev3 9 | !default.perspectivev3 10 | xcuserdata 11 | *.xccheckout 12 | profile 13 | *.moved-aside 14 | DerivedData 15 | *.hmap 16 | *.ipa -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | 3 | language: objective-c 4 | 5 | before_install: 6 | - gem install xcpretty 7 | 8 | script: 9 | - xcodebuild -workspace Ono.xcworkspace -scheme "Ono macOS" -sdk macosx | xcpretty 10 | - xcodebuild test -workspace Ono.xcworkspace -scheme "Ono macOS" -sdk macosx | xcpretty 11 | -------------------------------------------------------------------------------- /Example/Example.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 3D4BE0B41902F4F700F99600 /* libxml2.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = F897F98F18BFC5860043A736 /* libxml2.dylib */; }; 11 | F82D39C21AA61D4100DAC057 /* Ono.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F82D39C11AA61D4100DAC057 /* Ono.framework */; }; 12 | F897F97A18BFC44E0043A736 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F897F97918BFC44E0043A736 /* Foundation.framework */; }; 13 | F897F97D18BFC44E0043A736 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = F897F97C18BFC44E0043A736 /* main.m */; }; 14 | /* End PBXBuildFile section */ 15 | 16 | /* Begin PBXCopyFilesBuildPhase section */ 17 | F897F97418BFC44E0043A736 /* CopyFiles */ = { 18 | isa = PBXCopyFilesBuildPhase; 19 | buildActionMask = 2147483647; 20 | dstPath = /usr/share/man/man1/; 21 | dstSubfolderSpec = 0; 22 | files = ( 23 | ); 24 | runOnlyForDeploymentPostprocessing = 1; 25 | }; 26 | /* End PBXCopyFilesBuildPhase section */ 27 | 28 | /* Begin PBXFileReference section */ 29 | F82D39C11AA61D4100DAC057 /* Ono.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Ono.framework; path = "../build/Debug-iphoneos/Ono.framework"; sourceTree = ""; }; 30 | F897F97618BFC44E0043A736 /* Ono OS X Example */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = "Ono OS X Example"; sourceTree = BUILT_PRODUCTS_DIR; }; 31 | F897F97918BFC44E0043A736 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; 32 | F897F97C18BFC44E0043A736 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 33 | F897F97F18BFC44E0043A736 /* Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Prefix.pch; sourceTree = SOURCE_ROOT; }; 34 | F897F98D18BFC55B0043A736 /* nutrition.xml */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = nutrition.xml; sourceTree = SOURCE_ROOT; }; 35 | F897F98F18BFC5860043A736 /* libxml2.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libxml2.dylib; path = usr/lib/libxml2.dylib; sourceTree = SDKROOT; }; 36 | F897F99C18BFCC9A0043A736 /* XCTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCTest.framework; path = Library/Frameworks/XCTest.framework; sourceTree = DEVELOPER_DIR; }; 37 | /* End PBXFileReference section */ 38 | 39 | /* Begin PBXFrameworksBuildPhase section */ 40 | F897F97318BFC44E0043A736 /* Frameworks */ = { 41 | isa = PBXFrameworksBuildPhase; 42 | buildActionMask = 2147483647; 43 | files = ( 44 | F82D39C21AA61D4100DAC057 /* Ono.framework in Frameworks */, 45 | F897F97A18BFC44E0043A736 /* Foundation.framework in Frameworks */, 46 | 3D4BE0B41902F4F700F99600 /* libxml2.dylib in Frameworks */, 47 | ); 48 | runOnlyForDeploymentPostprocessing = 0; 49 | }; 50 | /* End PBXFrameworksBuildPhase section */ 51 | 52 | /* Begin PBXGroup section */ 53 | F897F96D18BFC44E0043A736 = { 54 | isa = PBXGroup; 55 | children = ( 56 | F897F97B18BFC44E0043A736 /* Example */, 57 | F897F97818BFC44E0043A736 /* Frameworks */, 58 | F897F97718BFC44E0043A736 /* Products */, 59 | ); 60 | sourceTree = ""; 61 | }; 62 | F897F97718BFC44E0043A736 /* Products */ = { 63 | isa = PBXGroup; 64 | children = ( 65 | F897F97618BFC44E0043A736 /* Ono OS X Example */, 66 | ); 67 | name = Products; 68 | sourceTree = ""; 69 | }; 70 | F897F97818BFC44E0043A736 /* Frameworks */ = { 71 | isa = PBXGroup; 72 | children = ( 73 | F82D39C11AA61D4100DAC057 /* Ono.framework */, 74 | F897F98F18BFC5860043A736 /* libxml2.dylib */, 75 | F897F97918BFC44E0043A736 /* Foundation.framework */, 76 | F897F99C18BFCC9A0043A736 /* XCTest.framework */, 77 | ); 78 | name = Frameworks; 79 | sourceTree = ""; 80 | }; 81 | F897F97B18BFC44E0043A736 /* Example */ = { 82 | isa = PBXGroup; 83 | children = ( 84 | F897F97C18BFC44E0043A736 /* main.m */, 85 | F897F98E18BFC5600043A736 /* Resources */, 86 | F897F97E18BFC44E0043A736 /* Supporting Files */, 87 | ); 88 | name = Example; 89 | sourceTree = SOURCE_ROOT; 90 | }; 91 | F897F97E18BFC44E0043A736 /* Supporting Files */ = { 92 | isa = PBXGroup; 93 | children = ( 94 | F897F97F18BFC44E0043A736 /* Prefix.pch */, 95 | ); 96 | name = "Supporting Files"; 97 | sourceTree = ""; 98 | }; 99 | F897F98E18BFC5600043A736 /* Resources */ = { 100 | isa = PBXGroup; 101 | children = ( 102 | F897F98D18BFC55B0043A736 /* nutrition.xml */, 103 | ); 104 | name = Resources; 105 | sourceTree = ""; 106 | }; 107 | /* End PBXGroup section */ 108 | 109 | /* Begin PBXNativeTarget section */ 110 | F897F97518BFC44E0043A736 /* Ono OS X Example */ = { 111 | isa = PBXNativeTarget; 112 | buildConfigurationList = F897F98418BFC44E0043A736 /* Build configuration list for PBXNativeTarget "Ono OS X Example" */; 113 | buildPhases = ( 114 | F897F97218BFC44E0043A736 /* Sources */, 115 | F897F97318BFC44E0043A736 /* Frameworks */, 116 | F897F97418BFC44E0043A736 /* CopyFiles */, 117 | ); 118 | buildRules = ( 119 | ); 120 | dependencies = ( 121 | ); 122 | name = "Ono OS X Example"; 123 | productName = "Ono Example"; 124 | productReference = F897F97618BFC44E0043A736 /* Ono OS X Example */; 125 | productType = "com.apple.product-type.tool"; 126 | }; 127 | /* End PBXNativeTarget section */ 128 | 129 | /* Begin PBXProject section */ 130 | F897F96E18BFC44E0043A736 /* Project object */ = { 131 | isa = PBXProject; 132 | attributes = { 133 | LastUpgradeCheck = 1020; 134 | ORGANIZATIONNAME = Mattt; 135 | }; 136 | buildConfigurationList = F897F97118BFC44E0043A736 /* Build configuration list for PBXProject "Example" */; 137 | compatibilityVersion = "Xcode 3.2"; 138 | developmentRegion = en; 139 | hasScannedForEncodings = 0; 140 | knownRegions = ( 141 | en, 142 | Base, 143 | ); 144 | mainGroup = F897F96D18BFC44E0043A736; 145 | productRefGroup = F897F97718BFC44E0043A736 /* Products */; 146 | projectDirPath = ""; 147 | projectRoot = ""; 148 | targets = ( 149 | F897F97518BFC44E0043A736 /* Ono OS X Example */, 150 | ); 151 | }; 152 | /* End PBXProject section */ 153 | 154 | /* Begin PBXSourcesBuildPhase section */ 155 | F897F97218BFC44E0043A736 /* Sources */ = { 156 | isa = PBXSourcesBuildPhase; 157 | buildActionMask = 2147483647; 158 | files = ( 159 | F897F97D18BFC44E0043A736 /* main.m in Sources */, 160 | ); 161 | runOnlyForDeploymentPostprocessing = 0; 162 | }; 163 | /* End PBXSourcesBuildPhase section */ 164 | 165 | /* Begin XCBuildConfiguration section */ 166 | F897F98218BFC44E0043A736 /* Debug */ = { 167 | isa = XCBuildConfiguration; 168 | buildSettings = { 169 | ALWAYS_SEARCH_USER_PATHS = NO; 170 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 171 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 172 | CLANG_CXX_LIBRARY = "libc++"; 173 | CLANG_ENABLE_OBJC_ARC = YES; 174 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 175 | CLANG_WARN_BOOL_CONVERSION = YES; 176 | CLANG_WARN_COMMA = YES; 177 | CLANG_WARN_CONSTANT_CONVERSION = YES; 178 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 179 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 180 | CLANG_WARN_EMPTY_BODY = YES; 181 | CLANG_WARN_ENUM_CONVERSION = YES; 182 | CLANG_WARN_INFINITE_RECURSION = YES; 183 | CLANG_WARN_INT_CONVERSION = YES; 184 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 185 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 186 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 187 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 188 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 189 | CLANG_WARN_STRICT_PROTOTYPES = YES; 190 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 191 | CLANG_WARN_UNREACHABLE_CODE = YES; 192 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 193 | COPY_PHASE_STRIP = NO; 194 | ENABLE_STRICT_OBJC_MSGSEND = YES; 195 | ENABLE_TESTABILITY = YES; 196 | GCC_C_LANGUAGE_STANDARD = gnu99; 197 | GCC_DYNAMIC_NO_PIC = NO; 198 | GCC_ENABLE_OBJC_EXCEPTIONS = YES; 199 | GCC_NO_COMMON_BLOCKS = YES; 200 | GCC_OPTIMIZATION_LEVEL = 0; 201 | GCC_PREPROCESSOR_DEFINITIONS = ( 202 | "DEBUG=1", 203 | "$(inherited)", 204 | ); 205 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 206 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 207 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 208 | GCC_WARN_UNDECLARED_SELECTOR = YES; 209 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 210 | GCC_WARN_UNUSED_FUNCTION = YES; 211 | GCC_WARN_UNUSED_VARIABLE = YES; 212 | MACOSX_DEPLOYMENT_TARGET = 10.7; 213 | ONLY_ACTIVE_ARCH = YES; 214 | SDKROOT = macosx; 215 | }; 216 | name = Debug; 217 | }; 218 | F897F98318BFC44E0043A736 /* Release */ = { 219 | isa = XCBuildConfiguration; 220 | buildSettings = { 221 | ALWAYS_SEARCH_USER_PATHS = NO; 222 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 223 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 224 | CLANG_CXX_LIBRARY = "libc++"; 225 | CLANG_ENABLE_OBJC_ARC = YES; 226 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 227 | CLANG_WARN_BOOL_CONVERSION = YES; 228 | CLANG_WARN_COMMA = YES; 229 | CLANG_WARN_CONSTANT_CONVERSION = YES; 230 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 231 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 232 | CLANG_WARN_EMPTY_BODY = YES; 233 | CLANG_WARN_ENUM_CONVERSION = YES; 234 | CLANG_WARN_INFINITE_RECURSION = YES; 235 | CLANG_WARN_INT_CONVERSION = YES; 236 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 237 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 238 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 239 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 240 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 241 | CLANG_WARN_STRICT_PROTOTYPES = YES; 242 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 243 | CLANG_WARN_UNREACHABLE_CODE = YES; 244 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 245 | COPY_PHASE_STRIP = YES; 246 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 247 | ENABLE_NS_ASSERTIONS = NO; 248 | ENABLE_STRICT_OBJC_MSGSEND = YES; 249 | GCC_C_LANGUAGE_STANDARD = gnu99; 250 | GCC_ENABLE_OBJC_EXCEPTIONS = YES; 251 | GCC_NO_COMMON_BLOCKS = YES; 252 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 253 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 254 | GCC_WARN_UNDECLARED_SELECTOR = YES; 255 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 256 | GCC_WARN_UNUSED_FUNCTION = YES; 257 | GCC_WARN_UNUSED_VARIABLE = YES; 258 | MACOSX_DEPLOYMENT_TARGET = 10.7; 259 | SDKROOT = macosx; 260 | }; 261 | name = Release; 262 | }; 263 | F897F98518BFC44E0043A736 /* Debug */ = { 264 | isa = XCBuildConfiguration; 265 | buildSettings = { 266 | CLANG_ENABLE_MODULES = YES; 267 | FRAMEWORK_SEARCH_PATHS = "$(inherited)"; 268 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 269 | GCC_PREFIX_HEADER = Prefix.pch; 270 | HEADER_SEARCH_PATHS = ( 271 | "$(inherited)", 272 | /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, 273 | "$(SDK_DIR)/usr/include/libxml2", 274 | ); 275 | MACOSX_DEPLOYMENT_TARGET = 10.7; 276 | PRODUCT_NAME = "$(TARGET_NAME)"; 277 | SDKROOT = macosx; 278 | WARNING_CFLAGS = ( 279 | "-Weverything", 280 | "-Wno-objc-missing-property-synthesis", 281 | ); 282 | }; 283 | name = Debug; 284 | }; 285 | F897F98618BFC44E0043A736 /* Release */ = { 286 | isa = XCBuildConfiguration; 287 | buildSettings = { 288 | CLANG_ENABLE_MODULES = YES; 289 | FRAMEWORK_SEARCH_PATHS = "$(inherited)"; 290 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 291 | GCC_PREFIX_HEADER = Prefix.pch; 292 | HEADER_SEARCH_PATHS = ( 293 | "$(inherited)", 294 | /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, 295 | "$(SDK_DIR)/usr/include/libxml2", 296 | ); 297 | MACOSX_DEPLOYMENT_TARGET = 10.7; 298 | PRODUCT_NAME = "$(TARGET_NAME)"; 299 | SDKROOT = macosx; 300 | WARNING_CFLAGS = ( 301 | "-Weverything", 302 | "-Wno-objc-missing-property-synthesis", 303 | ); 304 | }; 305 | name = Release; 306 | }; 307 | /* End XCBuildConfiguration section */ 308 | 309 | /* Begin XCConfigurationList section */ 310 | F897F97118BFC44E0043A736 /* Build configuration list for PBXProject "Example" */ = { 311 | isa = XCConfigurationList; 312 | buildConfigurations = ( 313 | F897F98218BFC44E0043A736 /* Debug */, 314 | F897F98318BFC44E0043A736 /* Release */, 315 | ); 316 | defaultConfigurationIsVisible = 0; 317 | defaultConfigurationName = Release; 318 | }; 319 | F897F98418BFC44E0043A736 /* Build configuration list for PBXNativeTarget "Ono OS X Example" */ = { 320 | isa = XCConfigurationList; 321 | buildConfigurations = ( 322 | F897F98518BFC44E0043A736 /* Debug */, 323 | F897F98618BFC44E0043A736 /* Release */, 324 | ); 325 | defaultConfigurationIsVisible = 0; 326 | defaultConfigurationName = Release; 327 | }; 328 | /* End XCConfigurationList section */ 329 | }; 330 | rootObject = F897F96E18BFC44E0043A736 /* Project object */; 331 | } 332 | -------------------------------------------------------------------------------- /Example/Example.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Example/Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header 3 | // 4 | // The contents of this file are implicitly included at the beginning of every source file. 5 | // 6 | 7 | #ifdef __OBJC__ 8 | @import Foundation; 9 | #endif 10 | -------------------------------------------------------------------------------- /Example/main.m: -------------------------------------------------------------------------------- 1 | // main.m 2 | // 3 | // Copyright (c) 2014 – 2018 Mattt (https://mat.tt) 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 11 | // 12 | // The above copyright notice and this permission notice shall be included in 13 | // all copies or substantial portions of the Software. 14 | // 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | // THE SOFTWARE. 22 | 23 | @import Foundation; 24 | @import Ono; 25 | 26 | int main(int argc, const char * argv[]) { 27 | @autoreleasepool { 28 | NSError *error = nil; 29 | NSString *XMLFilePath = [[@(__FILE__) stringByDeletingLastPathComponent] stringByAppendingPathComponent:@"nutrition.xml"]; 30 | NSData *data = [NSData dataWithContentsOfFile:XMLFilePath]; 31 | ONOXMLDocument *document = [ONOXMLDocument XMLDocumentWithData:data error:&error]; 32 | if (error) { 33 | NSLog(@"[Error] %@", error); 34 | return 0; 35 | } 36 | 37 | NSLog(@"Root Element: %@", document.rootElement.tag); 38 | 39 | NSLog(@"\n"); 40 | NSLog(@"Daily Values:"); 41 | for (ONOXMLElement *dailyValueElement in [[document.rootElement firstChildWithTag:@"daily-values"] children]) { 42 | NSString *nutrient = dailyValueElement.tag; 43 | NSNumber *amount = [dailyValueElement numberValue]; 44 | NSString *unit = dailyValueElement[@"units"]; 45 | NSLog(@"- %@%@ %@ ", amount, unit, nutrient); 46 | } 47 | 48 | NSLog(@"\n"); 49 | NSString *XPath = @"//food/name"; 50 | NSLog(@"XPath Search: %@", XPath); 51 | [document enumerateElementsWithXPath:XPath usingBlock:^(ONOXMLElement *element, __unused NSUInteger idx, __unused BOOL *stop) { 52 | NSLog(@"%@", element); 53 | }]; 54 | 55 | NSLog(@"\n"); 56 | NSString *CSS = @"food > serving[units]"; 57 | NSLog(@"CSS Search: %@", CSS); 58 | [document enumerateElementsWithCSS:CSS usingBlock:^(ONOXMLElement *element, __unused NSUInteger idx, __unused BOOL *stop) { 59 | NSLog(@"%@", element); 60 | }]; 61 | 62 | NSLog(@"\n"); 63 | XPath = @"//food/name"; 64 | NSLog(@"XPath Search: %@", XPath); 65 | __block ONOXMLElement *blockElement = nil; 66 | [document enumerateElementsWithXPath:XPath usingBlock:^(ONOXMLElement *element, NSUInteger idx, BOOL *stop) { 67 | *stop = idx == 1; 68 | if (*stop) { 69 | blockElement = element; 70 | } 71 | }]; 72 | NSLog(@"Second element: %@", blockElement); 73 | 74 | NSLog(@"\n"); 75 | XPath = @"//food/name"; 76 | NSLog(@"XPath Search: %@", XPath); 77 | ONOXMLElement *firstElement = [document firstChildWithXPath:XPath]; 78 | NSLog(@"First element: %@", firstElement); 79 | } 80 | 81 | return 0; 82 | } 83 | -------------------------------------------------------------------------------- /Example/nutrition.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 65 5 | 20 6 | 300 7 | 2400 8 | 300 9 | 25 10 | 50 11 | 12 | 13 | Avocado Dip 14 | Sunnydale 15 | 29 16 | 17 | 11 18 | 3 19 | 5 20 | 210 21 | 2 22 | 0 23 | 1 24 | 25 | 0 26 | 0 27 | 28 | 29 | 0 30 | 0 31 | 32 | 33 | 34 | Bagels, New York Style 35 | Thompson 36 | 104 37 | 38 | 4 39 | 1 40 | 0 41 | 510 42 | 54 43 | 3 44 | 11 45 | 46 | 0 47 | 0 48 | 49 | 50 | 8 51 | 20 52 | 53 | 54 | 55 | Beef Frankfurter, Quarter Pound 56 | Armitage 57 | 115 58 | 59 | 32 60 | 15 61 | 65 62 | 1100 63 | 8 64 | 0 65 | 13 66 | 67 | 0 68 | 2 69 | 70 | 71 | 1 72 | 6 73 | 74 | 75 | 76 | Chicken Pot Pie 77 | Lakeson 78 | 198 79 | 80 | 22 81 | 9 82 | 25 83 | 810 84 | 42 85 | 2 86 | 10 87 | 88 | 20 89 | 2 90 | 91 | 92 | 2 93 | 10 94 | 95 | 96 | 97 | Cole Slaw 98 | Fresh Quick 99 | 1.5 100 | 101 | 0 102 | 0 103 | 0 104 | 15 105 | 5 106 | 2 107 | 1 108 | 109 | 30 110 | 45 111 | 112 | 113 | 4 114 | 2 115 | 116 | 117 | 118 | Eggs 119 | Goodpath 120 | 50 121 | 122 | 4.5 123 | 1.5 124 | 215 125 | 65 126 | 1 127 | 0 128 | 6 129 | 130 | 6 131 | 0 132 | 133 | 134 | 2 135 | 4 136 | 137 | 138 | 139 | Hazelnut Spread 140 | Ferreira 141 | 2 142 | 143 | 10 144 | 2 145 | 0 146 | 20 147 | 23 148 | 2 149 | 3 150 | 151 | 0 152 | 0 153 | 154 | 155 | 6 156 | 4 157 | 158 | 159 | 160 | Potato Chips 161 | Lees 162 | 28 163 | 164 | 10 165 | 3 166 | 0 167 | 180 168 | 15 169 | 1 170 | 2 171 | 172 | 0 173 | 10 174 | 175 | 176 | 0 177 | 0 178 | 179 | 180 | 181 | Soy Patties, Grilled 182 | Gardenproducts 183 | 96 184 | 185 | 5 186 | 0 187 | 0 188 | 420 189 | 10 190 | 4 191 | 9 192 | 193 | 0 194 | 0 195 | 196 | 197 | 0 198 | 0 199 | 200 | 201 | 202 | Truffles, Dark Chocolate 203 | Lyndon's 204 | 39 205 | 206 | 19 207 | 14 208 | 25 209 | 10 210 | 16 211 | 1 212 | 1 213 | 214 | 0 215 | 0 216 | 217 | 218 | 0 219 | 0 220 | 221 | 222 | 223 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 – 2018 Mattt (https://mat.tt/) 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /Ono.playground/Contents.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import Ono 3 | 4 | guard let url = Bundle.main.url(forResource: "nutrition", withExtension: "xml"), 5 | let data = try? Data(contentsOf: url) else 6 | { 7 | fatalError("Missing resource: nutrition.xml") 8 | } 9 | 10 | let document = try ONOXMLDocument(data: data) 11 | document.rootElement.tag 12 | 13 | for element in document.rootElement.children.first?.children ?? [] { 14 | let nutrient = element.tag 15 | let amount = element.numberValue! 16 | let unit = element.attributes["units"]! 17 | 18 | print("- \(amount)\(unit) \(nutrient)") 19 | } 20 | 21 | document.enumerateElements(withXPath: "//food/name") { (element, _, _) in 22 | print(element) 23 | } 24 | 25 | document.enumerateElements(withCSS: "food > serving[units]") { (element, _, _) in 26 | print(element) 27 | } 28 | -------------------------------------------------------------------------------- /Ono.playground/Resources/nutrition.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 65 5 | 20 6 | 300 7 | 2400 8 | 300 9 | 25 10 | 50 11 | 12 | 13 | Avocado Dip 14 | Sunnydale 15 | 29 16 | 17 | 11 18 | 3 19 | 5 20 | 210 21 | 2 22 | 0 23 | 1 24 | 25 | 0 26 | 0 27 | 28 | 29 | 0 30 | 0 31 | 32 | 33 | 34 | Bagels, New York Style 35 | Thompson 36 | 104 37 | 38 | 4 39 | 1 40 | 0 41 | 510 42 | 54 43 | 3 44 | 11 45 | 46 | 0 47 | 0 48 | 49 | 50 | 8 51 | 20 52 | 53 | 54 | 55 | Beef Frankfurter, Quarter Pound 56 | Armitage 57 | 115 58 | 59 | 32 60 | 15 61 | 65 62 | 1100 63 | 8 64 | 0 65 | 13 66 | 67 | 0 68 | 2 69 | 70 | 71 | 1 72 | 6 73 | 74 | 75 | 76 | Chicken Pot Pie 77 | Lakeson 78 | 198 79 | 80 | 22 81 | 9 82 | 25 83 | 810 84 | 42 85 | 2 86 | 10 87 | 88 | 20 89 | 2 90 | 91 | 92 | 2 93 | 10 94 | 95 | 96 | 97 | Cole Slaw 98 | Fresh Quick 99 | 1.5 100 | 101 | 0 102 | 0 103 | 0 104 | 15 105 | 5 106 | 2 107 | 1 108 | 109 | 30 110 | 45 111 | 112 | 113 | 4 114 | 2 115 | 116 | 117 | 118 | Eggs 119 | Goodpath 120 | 50 121 | 122 | 4.5 123 | 1.5 124 | 215 125 | 65 126 | 1 127 | 0 128 | 6 129 | 130 | 6 131 | 0 132 | 133 | 134 | 2 135 | 4 136 | 137 | 138 | 139 | Hazelnut Spread 140 | Ferreira 141 | 2 142 | 143 | 10 144 | 2 145 | 0 146 | 20 147 | 23 148 | 2 149 | 3 150 | 151 | 0 152 | 0 153 | 154 | 155 | 6 156 | 4 157 | 158 | 159 | 160 | Potato Chips 161 | Lees 162 | 28 163 | 164 | 10 165 | 3 166 | 0 167 | 180 168 | 15 169 | 1 170 | 2 171 | 172 | 0 173 | 10 174 | 175 | 176 | 0 177 | 0 178 | 179 | 180 | 181 | Soy Patties, Grilled 182 | Gardenproducts 183 | 96 184 | 185 | 5 186 | 0 187 | 0 188 | 420 189 | 10 190 | 4 191 | 9 192 | 193 | 0 194 | 0 195 | 196 | 197 | 0 198 | 0 199 | 200 | 201 | 202 | Truffles, Dark Chocolate 203 | Lyndon's 204 | 39 205 | 206 | 19 207 | 14 208 | 25 209 | 10 210 | 16 211 | 1 212 | 1 213 | 214 | 0 215 | 0 216 | 217 | 218 | 0 219 | 0 220 | 221 | 222 | 223 | -------------------------------------------------------------------------------- /Ono.playground/contents.xcplayground: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /Ono.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = 'Ono' 3 | s.version = '2.1.2' 4 | s.license = 'MIT' 5 | s.summary = 'A sensible way to deal with XML & HTML.' 6 | s.homepage = 'https://github.com/mattt/Ono' 7 | s.social_media_url = 'https://twitter.com/mattt' 8 | s.authors = { 'Mattt' => 'mattt@me.com' } 9 | s.source = { git: 'https://github.com/mattt/Ono.git', tag: s.version } 10 | s.source_files = 'Source' 11 | s.requires_arc = true 12 | 13 | s.ios.deployment_target = '6.0' 14 | s.osx.deployment_target = '10.8' 15 | s.watchos.deployment_target = '2.0' 16 | s.tvos.deployment_target = '9.0' 17 | 18 | s.libraries = 'xml2' 19 | s.xcconfig = { 'HEADER_SEARCH_PATHS' => '$(SDKROOT)/usr/include/libxml2' } 20 | end 21 | -------------------------------------------------------------------------------- /Ono.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Ono.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Ono.xcodeproj/xcshareddata/xcschemes/Ono iOS.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 38 | 39 | 44 | 45 | 47 | 53 | 54 | 55 | 56 | 57 | 63 | 64 | 65 | 66 | 67 | 68 | 78 | 79 | 85 | 86 | 87 | 88 | 89 | 90 | 96 | 97 | 103 | 104 | 105 | 106 | 108 | 109 | 112 | 113 | 114 | -------------------------------------------------------------------------------- /Ono.xcodeproj/xcshareddata/xcschemes/Ono macOS.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 38 | 39 | 44 | 45 | 47 | 53 | 54 | 55 | 56 | 57 | 63 | 64 | 65 | 66 | 67 | 68 | 78 | 79 | 85 | 86 | 87 | 88 | 89 | 90 | 96 | 97 | 103 | 104 | 105 | 106 | 108 | 109 | 112 | 113 | 114 | -------------------------------------------------------------------------------- /Ono.xcodeproj/xcshareddata/xcschemes/Ono tvOS.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 49 | 50 | 51 | 52 | 53 | 54 | 64 | 65 | 71 | 72 | 73 | 74 | 75 | 76 | 82 | 83 | 89 | 90 | 91 | 92 | 94 | 95 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /Ono.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 10 | 12 | 13 | 15 | 16 | 18 | 19 | 20 | 22 | 23 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Ono.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Ono (斧) 2 | 3 | Foundation lacks a convenient, cross-platform way to work with HTML and XML. 4 | [`NSXMLParser`](https://developer.apple.com/documentation/foundation/nsxmlparser?language=objc) 5 | is an event-driven, 6 | [SAX](https://en.wikipedia.org/wiki/Simple_API_for_XML)-style API 7 | that can be cumbersome to work with. 8 | [`NSXMLDocument`](https://developer.apple.com/documentation/foundation/nsxmldocument), 9 | offers a more convenient 10 | [DOM](https://en.wikipedia.org/wiki/Document_Object_Model)-style API, 11 | but is only supported on macOS. 12 | 13 | **Ono offers a sensible way to work with XML & HTML on Apple platforms in Objective-C and Swift** 14 | 15 | Whether your app needs to 16 | scrape a website, parse an RSS feed, or interface with a XML-RPC webservice, 17 | Ono will make your day a whole lot less terrible. 18 | 19 | > Ono (斧) means "axe", in homage to [Nokogiri](http://nokogiri.org) (鋸), which means "saw". 20 | 21 | ## Features 22 | 23 | - [x] Simple, modern API following standard Objective-C conventions, including extensive use of blocks and `NSFastEnumeration` 24 | - [x] Extremely performant document parsing and traversal, powered by `libxml2` 25 | - [x] Support for both [XPath](http://en.wikipedia.org/wiki/XPath) and [CSS](http://en.wikipedia.org/wiki/Cascading_Style_Sheets) queries 26 | - [x] Automatic conversion of date and number values 27 | - [x] Correct, common-sense handling of XML namespaces for elements and attributes 28 | - [x] Ability to load HTML and XML documents from either `NSString` or `NSData` 29 | - [x] Full documentation 30 | - [x] Comprehensive test suite 31 | 32 | ## Installation 33 | 34 | [CocoaPods](http://cocoapods.org) is the recommended method of installing Ono. 35 | Add the following line to your `Podfile`: 36 | 37 | #### Podfile 38 | 39 | ```ruby 40 | pod 'Ono' 41 | ``` 42 | 43 | ## Usage 44 | 45 | ### Swift 46 | 47 | ```swift 48 | import Foundation 49 | import Ono 50 | 51 | guard let url = Bundle.main.url(forResource: "nutrition", withExtension: "xml"), 52 | let data = try? Data(contentsOf: url) else 53 | { 54 | fatalError("Missing resource: nutrition.xml") 55 | } 56 | 57 | let document = try ONOXMLDocument(data: data) 58 | document.rootElement.tag 59 | 60 | for element in document.rootElement.children.first?.children ?? [] { 61 | let nutrient = element.tag 62 | let amount = element.numberValue! 63 | let unit = element.attributes["units"]! 64 | 65 | print("- \(amount)\(unit) \(nutrient)") 66 | } 67 | 68 | document.enumerateElements(withXPath: "//food/name") { (element, _, _) in 69 | print(element) 70 | } 71 | 72 | document.enumerateElements(withCSS: "food > serving[units]") { (element, _, _) in 73 | print(element) 74 | } 75 | ``` 76 | 77 | ### Objective-C 78 | 79 | ```objective-c 80 | #import "Ono.h" 81 | 82 | NSData *data = ...; 83 | NSError *error; 84 | 85 | ONOXMLDocument *document = [ONOXMLDocument XMLDocumentWithData:data error:&error]; 86 | for (ONOXMLElement *element in document.rootElement.children) { 87 | NSLog(@"%@: %@", element.tag, element.attributes); 88 | } 89 | 90 | // Support for Namespaces 91 | NSString *author = [[document.rootElement firstChildWithTag:@"creator" inNamespace:@"dc"] stringValue]; 92 | 93 | // Automatic Conversion for Number & Date Values 94 | NSDate *date = [[document.rootElement firstChildWithTag:@"created_at"] dateValue]; // ISO 8601 Timestamp 95 | NSInteger numberOfWords = [[[document.rootElement firstChildWithTag:@"word_count"] numberValue] integerValue]; 96 | BOOL isPublished = [[[document.rootElement firstChildWithTag:@"is_published"] numberValue] boolValue]; 97 | 98 | // Convenient Accessors for Attributes 99 | NSString *unit = [document.rootElement firstChildWithTag:@"Length"][@"unit"]; 100 | NSDictionary *authorAttributes = [[document.rootElement firstChildWithTag:@"author"] attributes]; 101 | 102 | // Support for XPath & CSS Queries 103 | [document enumerateElementsWithXPath:@"//Content" usingBlock:^(ONOXMLElement *element, NSUInteger idx, BOOL *stop) { 104 | NSLog(@"%@", element); 105 | }]; 106 | ``` 107 | 108 | ## Demo 109 | 110 | Build and run the example project in Xcode to see `Ono` in action, 111 | or check out the provided Swift Playground. 112 | 113 | ## Requirements 114 | 115 | Ono is compatible with iOS 5 and higher, as well as macOS 10.7 and higher. 116 | It requires the `libxml2` library, 117 | which is included automatically when installed with CocoaPods, 118 | or added manually by adding "libxml2.dylib" 119 | to the target's "Link Binary With Libraries" build phase. 120 | 121 | ## Contact 122 | 123 | [Mattt](https://twitter.com/mattt) 124 | 125 | ## License 126 | 127 | Ono is available under the MIT license. 128 | See the LICENSE file for more info. 129 | -------------------------------------------------------------------------------- /Source/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(CURRENT_PROJECT_VERSION) 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Source/ONOXMLDocument.h: -------------------------------------------------------------------------------- 1 | // ONOXMLDocument.h 2 | // 3 | // Copyright (c) 2014 – 2018 Mattt (https://mat.tt) 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 11 | // 12 | // The above copyright notice and this permission notice shall be included in 13 | // all copies or substantial portions of the Software. 14 | // 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | // THE SOFTWARE. 22 | 23 | @import Foundation; 24 | 25 | #pragma clang diagnostic push 26 | #pragma clang diagnostic ignored "-Wobjc-missing-property-synthesis" 27 | 28 | NS_ASSUME_NONNULL_BEGIN 29 | 30 | @class ONOXMLElement; 31 | 32 | @class ONOXPathFunctionResult; 33 | 34 | /** 35 | The `ONOSearching` protocol is adopted by `ONOXMLDocument` and `ONOXMLElement`, denoting that they can search for elements using XPath or CSS selectors. 36 | 37 | @see http://www.w3.org/TR/xpath/ 38 | @see http://www.w3.org/TR/CSS21/selector.html 39 | */ 40 | @protocol ONOSearching 41 | 42 | ///--------------------------- 43 | /// @name Searching with XPath 44 | ///--------------------------- 45 | 46 | /** 47 | Returns the results for an XPath selector. 48 | 49 | @param XPath The XPath selector 50 | 51 | @return An enumerable collection of results. 52 | */ 53 | - (id )XPath:(NSString *)XPath; 54 | 55 | /** 56 | Returns the result for an XPath selector that contains XPath function. 57 | 58 | @param XPath The XPath selector 59 | 60 | @return The function result 61 | */ 62 | - (nullable ONOXPathFunctionResult *)functionResultByEvaluatingXPath:(NSString *)XPath; 63 | 64 | /** 65 | @deprecated Use `enumerateElementsWithXPath:usingBlock:` instead 66 | */ 67 | - (void)enumerateElementsWithXPath:(NSString *)XPath 68 | block:(void (^)(ONOXMLElement *element))block DEPRECATED_ATTRIBUTE; 69 | 70 | /** 71 | Enumerate elements matching an XPath selector. 72 | 73 | @param XPath The XPath selector 74 | @param block A block that is executed for each result. This block has no return value and takes three arguments: 75 | element: The enumerated element. 76 | idx: The index of the current item. 77 | stop: The block can set the value to `YES` to stop further processing of the elements. The stop argument is an out-only argument. You should only ever set this BOOL to `YES` within the block. 78 | */ 79 | - (void)enumerateElementsWithXPath:(NSString *)XPath 80 | usingBlock:(void (^)(ONOXMLElement *element, NSUInteger idx, BOOL *stop))block; 81 | 82 | /** 83 | Returns the first elements matching an XPath selector, or `nil` if there are no results. 84 | 85 | @param XPath The XPath selector 86 | 87 | @return The child element. 88 | */ 89 | - (nullable ONOXMLElement *)firstChildWithXPath:(NSString *)XPath; 90 | 91 | ///--------------------------- 92 | /// @name Searching with CSS 93 | ///--------------------------- 94 | 95 | /** 96 | Returns the results for a CSS selector. 97 | 98 | @param CSS The CSS selector 99 | 100 | @return An enumerable collection of results. 101 | */ 102 | - (id )CSS:(NSString *)CSS; 103 | 104 | /** 105 | @deprecated Use `enumerateElementsWithCSS:usingBlock:` instead 106 | */ 107 | - (void)enumerateElementsWithCSS:(NSString *)CSS 108 | block:(void (^)(ONOXMLElement *element))block DEPRECATED_ATTRIBUTE; 109 | 110 | /** 111 | Enumerate elements matching a CSS selector. 112 | 113 | @param CSS The CSS selector 114 | @param block A block that is executed for each result. This block has no return value and takes three arguments: 115 | element: the enumerated element. 116 | idx: the index of the current item. 117 | stop: the block can set the value to `YES` to stop further processing of the elements. The stop argument is an out-only argument. You should only ever set this BOOL to `YES` within the block. 118 | */ 119 | - (void)enumerateElementsWithCSS:(NSString *)CSS 120 | usingBlock:(void (^)(ONOXMLElement *element, NSUInteger idx, BOOL *stop))block; 121 | 122 | /** 123 | Returns the first elements matching a CSS selector, or `nil` if there are no results. 124 | 125 | @param CSS The CSS selector 126 | 127 | @return The child element. 128 | */ 129 | - (nullable ONOXMLElement *)firstChildWithCSS:(NSString *)CSS; 130 | 131 | @end 132 | 133 | #pragma mark - 134 | 135 | /** 136 | `ONOXMLDocument` encapsulates an XML or HTML document, which can be searched and queried. 137 | */ 138 | @interface ONOXMLDocument : NSObject 139 | 140 | ///------------------------------------ 141 | /// @name Accessing Document Attributes 142 | ///------------------------------------ 143 | 144 | /** 145 | The XML version. 146 | */ 147 | @property (readonly, nonatomic, copy) NSString *version; 148 | 149 | /** 150 | The string encoding for the document. This is 0 if no encoding is set, or it cannot be calculated. 151 | */ 152 | @property (readonly, nonatomic, assign) NSStringEncoding stringEncoding; 153 | 154 | 155 | ///--------------------------------- 156 | /// @name Accessing the Root Element 157 | ///--------------------------------- 158 | 159 | /** 160 | The root element of the document. 161 | */ 162 | @property (readonly, nonatomic, strong) ONOXMLElement *rootElement; 163 | 164 | ///------------------------------------ 165 | /// @name Accessing Document Formatters 166 | ///------------------------------------ 167 | 168 | /** 169 | The formatter used to determine `numberValue` for elements in the document. 170 | 171 | By default, this is an `NSNumberFormatter` instance with `NSNumberFormatterDecimalStyle`. 172 | */ 173 | @property (readonly, nonatomic, strong) NSNumberFormatter *numberFormatter; 174 | 175 | /** 176 | The formatter used to determine `dateValue` for elements in the document. 177 | 178 | By default, this is an `NSDateFormatter` instance configured to accept ISO 8601 formatted timestamps. 179 | 180 | @see http://en.wikipedia.org/wiki/ISO_8601 181 | */ 182 | @property (readonly, nonatomic, strong) NSDateFormatter *dateFormatter; 183 | 184 | ///----------------------------- 185 | /// @name Creating XML Documents 186 | ///----------------------------- 187 | 188 | /** 189 | Creates and returns an instance of ONOXMLDocument from an XML string. 190 | 191 | @param string The XML string. 192 | @param encoding The string encoding. 193 | @param error The error error that occured while parsing the XML, or `nil`. 194 | 195 | @return An `ONOXMLDocument` with the contents of the specified XML string. 196 | */ 197 | + (nullable instancetype)XMLDocumentWithString:(NSString *)string 198 | encoding:(NSStringEncoding)encoding 199 | error:(NSError * __autoreleasing *)error; 200 | 201 | /** 202 | Creates and returns an instance of ONOXMLDocument from XML data. 203 | 204 | @param data The XML data. 205 | @param error The error error that occured while parsing the XML, or `nil`. 206 | 207 | @return An `ONOXMLDocument` with the contents of the specified XML data. 208 | */ 209 | + (nullable instancetype)XMLDocumentWithData:(nullable NSData *)data 210 | error:(NSError * __autoreleasing *)error; 211 | 212 | ///------------------------------ 213 | /// @name Creating HTML Documents 214 | ///------------------------------ 215 | 216 | /** 217 | Creates and returns an instance of ONOXMLDocument from an HTML string. 218 | 219 | @param string The HTML string. 220 | @param encoding The string encoding. 221 | @param error The error error that occured while parsing the HTML, or `nil`. 222 | 223 | @return An `ONOXMLDocument` with the contents of the specified HTML string. 224 | */ 225 | + (nullable instancetype)HTMLDocumentWithString:(NSString *)string 226 | encoding:(NSStringEncoding)encoding 227 | error:(NSError * __autoreleasing *)error; 228 | 229 | /** 230 | Creates and returns an instance of ONOXMLDocument from HTML data. 231 | 232 | @param data The HTML string. 233 | @param error The error error that occured while parsing the HTML, or `nil`. 234 | 235 | @return An `ONOXMLDocument` with the contents of the specified HTML string. 236 | */ 237 | + (nullable instancetype)HTMLDocumentWithData:(nullable NSData *)data 238 | error:(NSError * __autoreleasing *)error; 239 | 240 | ///------------------------------------------ 241 | /// @name Defining Default Namespace Prefixes 242 | ///------------------------------------------ 243 | 244 | /** 245 | Define a prefix for a default namespace. 246 | 247 | @param prefix The prefix name 248 | @param ns The default namespace URI that declared in XML Document 249 | */ 250 | - (void)definePrefix:(NSString *)prefix 251 | forDefaultNamespace:(NSString *)ns; 252 | 253 | @end 254 | 255 | #pragma mark - 256 | 257 | /** 258 | `ONOXMLElement` represents an element in an `ONOXMLDocument`. 259 | */ 260 | @interface ONOXMLElement : NSObject 261 | 262 | /** 263 | The document containing the element. 264 | */ 265 | @property (readonly, nonatomic, weak) ONOXMLDocument *document; 266 | 267 | /** 268 | The element's namespace. 269 | */ 270 | #ifdef __cplusplus 271 | @property (nullable, readonly, nonatomic, copy) NSString *ns; 272 | #else 273 | @property (nullable, readonly, nonatomic, copy) NSString *namespace; 274 | #endif 275 | 276 | /** 277 | The element's tag. 278 | */ 279 | @property (readonly, nonatomic, copy) NSString *tag; 280 | 281 | /** 282 | The element's line number 283 | */ 284 | @property (readonly, nonatomic, assign) NSUInteger lineNumber; 285 | 286 | ///--------------------------- 287 | /// @name Accessing Attributes 288 | ///--------------------------- 289 | 290 | /** 291 | All attributes for the element. 292 | */ 293 | @property (readonly, nonatomic, strong) NSDictionary *attributes; 294 | 295 | /** 296 | Returns the value for the specified attribute. 297 | 298 | @param attribute The attribute name. 299 | 300 | @return The associated value. 301 | */ 302 | - (nullable id)valueForAttribute:(NSString *)attribute; 303 | 304 | /** 305 | Returns the value for an attribute in a particular namespace. 306 | 307 | @param attribute The attribute name. 308 | @param ns The attribute namespace. 309 | 310 | @return The associated value. 311 | */ 312 | - (nullable id)valueForAttribute:(NSString *)attribute 313 | inNamespace:(nullable NSString *)ns; 314 | 315 | ///---------------------------------------------------- 316 | /// @name Accessing Parent, Child, and Sibling Elements 317 | ///---------------------------------------------------- 318 | 319 | /** 320 | The element's parent element. 321 | */ 322 | @property (nullable, readonly, nonatomic, strong) ONOXMLElement *parent; 323 | 324 | /** 325 | The element's children elements. 326 | */ 327 | @property (readonly, nonatomic, strong) NSArray *children; 328 | 329 | /** 330 | The element's previous sibling. 331 | */ 332 | @property (nullable, readonly, nonatomic, strong) ONOXMLElement *previousSibling; 333 | 334 | /** 335 | The element's next sibling. 336 | */ 337 | @property (nullable, readonly, nonatomic, strong) ONOXMLElement *nextSibling; 338 | 339 | /** 340 | Returns the first child element with the specified tag, or `nil` if no such element exists. 341 | 342 | @param tag The tag name. 343 | 344 | @return The child element. 345 | */ 346 | - (nullable ONOXMLElement *)firstChildWithTag:(NSString *)tag; 347 | 348 | /** 349 | Returns the first child element with a tag in a particular namespace, or `nil` if no such element exists. 350 | 351 | @param tag The tag name. 352 | @param ns The namespace. 353 | 354 | @return The child element. 355 | */ 356 | - (nullable ONOXMLElement *)firstChildWithTag:(NSString *)tag 357 | inNamespace:(nullable NSString *)ns; 358 | 359 | /** 360 | Returns all children elements with the specified tag. 361 | 362 | @param tag The tag name. 363 | 364 | @return The children elements. 365 | */ 366 | - (NSArray *)childrenWithTag:(NSString *)tag; 367 | 368 | /** 369 | Returns all children elements with the specified tag. 370 | 371 | @param tag The tag name. 372 | @param ns The namepsace. 373 | 374 | @return The children elements. 375 | */ 376 | - (NSArray *)childrenWithTag:(NSString *)tag 377 | inNamespace:(nullable NSString *)ns; 378 | 379 | ///------------------------ 380 | /// @name Accessing Content 381 | ///------------------------ 382 | 383 | /** 384 | Whether the element has a value. 385 | */ 386 | @property (readonly, nonatomic, assign, getter = isBlank) BOOL blank; 387 | 388 | /** 389 | A string representation of the element's value. 390 | */ 391 | @property (nullable, readonly, nonatomic, copy) NSString *stringValue; 392 | 393 | /** 394 | A number representation of the element's value, which is generated from the document's `numberFormatter` property. 395 | */ 396 | @property (nullable, readonly, nonatomic, copy) NSNumber *numberValue; 397 | 398 | /** 399 | A date representation of the element's value, which is generated from the document's `dateFormatter` property. 400 | */ 401 | @property (nullable, readonly, nonatomic, copy) NSDate *dateValue; 402 | 403 | ///-------------------------------------- 404 | /// @name Subscripted Convenience Methods 405 | ///-------------------------------------- 406 | 407 | /** 408 | Returns the child element at the specified index. 409 | 410 | @param idx The index. 411 | 412 | @return The child element. 413 | */ 414 | - (nullable id)objectAtIndexedSubscript:(NSUInteger)idx; 415 | 416 | /** 417 | Returns the value for the attribute with the specified key. 418 | 419 | @param key The key. 420 | 421 | @return The attribute value, or `nil` if the attribute is not defined. 422 | */ 423 | - (nullable id)objectForKeyedSubscript:(id)key; 424 | 425 | @end 426 | 427 | /** 428 | `ONOXPathFunctionResult` represents a XPath function result. 429 | */ 430 | @interface ONOXPathFunctionResult : NSObject 431 | 432 | /** 433 | The Boolean value of the function result. 434 | */ 435 | @property (readonly, nonatomic, assign) BOOL boolValue; 436 | 437 | /** 438 | The floating point value of the function result. 439 | 440 | @deprecated Use the `numberValue` property instead. 441 | */ 442 | @property (readonly, nonatomic) double numericValue DEPRECATED_ATTRIBUTE; 443 | 444 | /** 445 | The numeric value of the function result. 446 | */ 447 | @property (nullable, readonly, nonatomic, copy) NSNumber *numberValue; 448 | 449 | /** 450 | The string value of the function result. 451 | */ 452 | @property (nullable, readonly, nonatomic, copy) NSString *stringValue; 453 | 454 | @end 455 | 456 | ///--------------------------- 457 | /// @name Constants 458 | ///--------------------------- 459 | 460 | /** 461 | ## Error Domains 462 | 463 | The following error domain is predefined. 464 | 465 | - `NSString * const ONOErrorDomain` 466 | 467 | ### Constants 468 | 469 | `ONOErrorDomain` 470 | Ono errors. Error codes for `ONOErrorDomain` are not currently defined. 471 | */ 472 | extern NSString * const ONOErrorDomain; 473 | 474 | NS_ASSUME_NONNULL_END 475 | 476 | #pragma clang diagnostic pop 477 | -------------------------------------------------------------------------------- /Source/ONOXMLDocument.m: -------------------------------------------------------------------------------- 1 | // ONOXMLDocument.m 2 | // 3 | // Copyright (c) 2014 – 2018 Mattt (https://mat.tt) 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 11 | // 12 | // The above copyright notice and this permission notice shall be included in 13 | // all copies or substantial portions of the Software. 14 | // 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | // THE SOFTWARE. 22 | 23 | #import "ONOXMLDocument.h" 24 | 25 | @import libxml2.xmlreader; 26 | @import libxml2.xpath; 27 | @import libxml2.xpathInternals; 28 | @import libxml2.HTMLparser; 29 | 30 | #pragma clang diagnostic push 31 | #pragma clang diagnostic ignored "-Wobjc-missing-property-synthesis" 32 | #if !defined(__has_warning) || __has_warning("-Wobjc-messaging-id") 33 | #pragma clang diagnostic ignored "-Wobjc-messaging-id" 34 | #endif 35 | 36 | NS_ASSUME_NONNULL_BEGIN 37 | 38 | static NSString * const ONOXMLDocumentErrorDomain = @"com.ono.error"; 39 | 40 | static NSRegularExpression * ONOIdRegularExpression() { 41 | static NSRegularExpression *_ONOIdRegularExpression = nil; 42 | static dispatch_once_t onceToken; 43 | dispatch_once(&onceToken, ^{ 44 | _ONOIdRegularExpression = [NSRegularExpression regularExpressionWithPattern:@"\\#([\\w-_]+)" options:(NSRegularExpressionOptions)0 error:nil]; 45 | }); 46 | 47 | return _ONOIdRegularExpression; 48 | } 49 | 50 | static NSRegularExpression * ONOClassRegularExpression() { 51 | static NSRegularExpression *_ONOClassRegularExpression = nil; 52 | static dispatch_once_t onceToken; 53 | dispatch_once(&onceToken, ^{ 54 | _ONOClassRegularExpression = [NSRegularExpression regularExpressionWithPattern:@"\\.([^\\.]+)" options:(NSRegularExpressionOptions)0 error:nil]; 55 | }); 56 | 57 | return _ONOClassRegularExpression; 58 | } 59 | 60 | static NSRegularExpression * ONOAttributeRegularExpression() { 61 | static NSRegularExpression *_ONOAttributeRegularExpression = nil; 62 | static dispatch_once_t onceToken; 63 | dispatch_once(&onceToken, ^{ 64 | _ONOAttributeRegularExpression = [NSRegularExpression regularExpressionWithPattern:@"\\[(\\w+)\\]" options:(NSRegularExpressionOptions)0 error:nil]; 65 | }); 66 | 67 | return _ONOAttributeRegularExpression; 68 | } 69 | 70 | NSString * ONOXPathFromCSS(NSString *CSS); 71 | NSString * ONOXPathFromCSS(NSString *CSS) { 72 | NSMutableArray *mutableXPathExpressions = [NSMutableArray array]; 73 | [[CSS componentsSeparatedByString:@","] enumerateObjectsUsingBlock:^(NSString *expression, __unused NSUInteger _idx, __unused BOOL *stop) { 74 | if (expression && [expression length] > 0) { 75 | __block NSMutableArray *mutableXPathComponents = [NSMutableArray arrayWithObject:@"./"]; 76 | __block NSString *prefix = nil; 77 | 78 | [[[expression stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]] componentsSeparatedByCharactersInSet:[NSCharacterSet whitespaceCharacterSet]] enumerateObjectsUsingBlock:^(NSString *token, NSUInteger idx, __unused BOOL *_stop) { 79 | if ([token isEqualToString:@"*"] && idx != 0) { 80 | [mutableXPathComponents addObject:@"/*"]; 81 | } else if ([token isEqualToString:@">"]) { 82 | prefix = @""; 83 | } else if ([token isEqualToString:@"+"]) { 84 | prefix = @"following-sibling::*[1]/self::"; 85 | } else if ([token isEqualToString:@"~"]) { 86 | prefix = @"following-sibling::"; 87 | } else { 88 | if (!prefix && idx != 0) { 89 | prefix = @"descendant::"; 90 | } 91 | 92 | NSRange symbolRange = [token rangeOfCharacterFromSet:[NSCharacterSet characterSetWithCharactersInString:@"#.[]"]]; 93 | if (symbolRange.location != NSNotFound) { 94 | NSMutableString *mutableXPathComponent = [NSMutableString stringWithString:[token substringToIndex:symbolRange.location]]; 95 | NSRange range = NSMakeRange(0, [token length]); 96 | 97 | { 98 | NSTextCheckingResult *result = [ONOIdRegularExpression() firstMatchInString:token options:(NSMatchingOptions)0 range:range]; 99 | if ([result numberOfRanges] > 1) { 100 | [mutableXPathComponent appendFormat:@"%@[@id = '%@']", (symbolRange.location == 0) ? @"*" : @"", [token substringWithRange:[result rangeAtIndex:1]]]; 101 | } 102 | } 103 | 104 | { 105 | for (NSTextCheckingResult *result in [ONOClassRegularExpression() matchesInString:token options:(NSMatchingOptions)0 range:range]) { 106 | if ([result numberOfRanges] > 1) { 107 | [mutableXPathComponent appendFormat:@"%@[contains(concat(' ',normalize-space(@class),' '),' %@ ')]", (symbolRange.location == 0) ? @"*" : @"", [token substringWithRange:[result rangeAtIndex:1]]]; 108 | } 109 | } 110 | } 111 | 112 | { 113 | for (NSTextCheckingResult *result in [ONOAttributeRegularExpression() matchesInString:token options:(NSMatchingOptions)0 range:range]) { 114 | if ([result numberOfRanges] > 1) { 115 | [mutableXPathComponent appendFormat:@"[@%@]", [token substringWithRange:[result rangeAtIndex:1]]]; 116 | } 117 | } 118 | } 119 | 120 | token = mutableXPathComponent; 121 | } 122 | 123 | if (prefix) { 124 | token = [prefix stringByAppendingString:token]; 125 | prefix = nil; 126 | } 127 | 128 | [mutableXPathComponents addObject:token]; 129 | } 130 | }]; 131 | 132 | [mutableXPathExpressions addObject:[mutableXPathComponents componentsJoinedByString:@"/"]]; 133 | } 134 | }]; 135 | 136 | return [mutableXPathExpressions componentsJoinedByString:@" | "]; 137 | } 138 | 139 | static BOOL ONOXMLNodeMatchesTagInNamespace(xmlNodePtr node, NSString *tag, NSString * _Nullable ns) { 140 | BOOL matchingTag = !tag || [@((const char *)node->name) compare:tag options:NSCaseInsensitiveSearch] == NSOrderedSame; 141 | 142 | BOOL matchingNamespace = !ns ? YES : (((node->ns != NULL) && (node->ns->prefix != NULL)) ? [@((const char *)node->ns->prefix) compare:(NSString *)ns options:NSCaseInsensitiveSearch] == NSOrderedSame : NO); 143 | 144 | return matchingTag && matchingNamespace; 145 | } 146 | 147 | static void ONOSetErrorFromXMLErrorPtr(NSError * __autoreleasing *error, xmlErrorPtr errorPtr) { 148 | if (error && errorPtr) { 149 | NSString *message = [[NSString stringWithCString:(const char *)errorPtr->message encoding:NSUTF8StringEncoding] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; 150 | NSInteger code = errorPtr->code; 151 | NSDictionary *userInfo = @{NSLocalizedFailureReasonErrorKey: message}; 152 | *error = [NSError errorWithDomain:ONOXMLDocumentErrorDomain code:code userInfo:userInfo]; 153 | xmlResetError(errorPtr); 154 | } 155 | } 156 | 157 | @interface ONOXPathEnumerator : NSEnumerator 158 | @end 159 | 160 | @interface ONOXPathFunctionResult() 161 | @property (readwrite, nonatomic, assign) xmlXPathObjectPtr xmlXPath; 162 | @end 163 | 164 | @interface ONOXPathEnumerator () 165 | @property (readwrite, nonatomic, assign) xmlXPathObjectPtr xmlXPath; 166 | @property (readwrite, nonatomic, assign) NSUInteger cursor; 167 | @property (readwrite, nonatomic, strong) ONOXMLDocument *document; 168 | @end 169 | 170 | @interface ONOXMLElement () 171 | @property (readwrite, nonatomic, assign) xmlNodePtr xmlNode; 172 | @property (readwrite, nonatomic, weak) ONOXMLDocument *document; 173 | 174 | - (nullable xmlXPathObjectPtr)xmlXPathObjectPtrWithXPath:(NSString *)XPath; 175 | @end 176 | 177 | @interface ONOXMLDocument () 178 | @property (readwrite, nonatomic, assign) xmlDocPtr xmlDocument; 179 | @property (readwrite, nonatomic, strong) ONOXMLElement *rootElement; 180 | @property (readwrite, nonatomic, copy) NSString *version; 181 | @property (readwrite, nonatomic, assign) NSStringEncoding stringEncoding; 182 | @property (readwrite, nonatomic, strong) NSNumberFormatter *numberFormatter; 183 | @property (readwrite, nonatomic, strong) NSDateFormatter *dateFormatter; 184 | @property (readwrite, nonatomic, strong) NSMutableDictionary *defaultNamespaces; 185 | 186 | - (nullable ONOXMLElement *)elementWithNode:(xmlNodePtr)node; 187 | - (nullable ONOXPathEnumerator *)enumeratorWithXPathObject:(xmlXPathObjectPtr)XPath; 188 | @end 189 | 190 | #pragma mark - 191 | 192 | @implementation ONOXPathEnumerator 193 | 194 | - (void)dealloc { 195 | if (_xmlXPath) { 196 | xmlXPathFreeObject(_xmlXPath); 197 | } 198 | } 199 | 200 | - (nullable id)objectAtIndex:(NSInteger)idx { 201 | if (idx >= (NSInteger)xmlXPathNodeSetGetLength(self.xmlXPath->nodesetval)) { 202 | return nil; 203 | } 204 | 205 | return [self.document elementWithNode:self.xmlXPath->nodesetval->nodeTab[idx]]; 206 | } 207 | 208 | #pragma mark - NSEnumerator 209 | 210 | - (NSArray *)allObjects { 211 | NSMutableArray *mutableObjects = [NSMutableArray arrayWithCapacity:(NSUInteger)self.xmlXPath->nodesetval->nodeNr]; 212 | for (NSInteger idx = 0; idx < xmlXPathNodeSetGetLength(self.xmlXPath->nodesetval); idx++) { 213 | ONOXMLElement *element = [self objectAtIndex:idx]; 214 | if (element) { 215 | [mutableObjects addObject:element]; 216 | } 217 | } 218 | 219 | return [NSArray arrayWithArray:mutableObjects]; 220 | } 221 | 222 | - (nullable id)nextObject { 223 | if (self.cursor >= (NSUInteger)self.xmlXPath->nodesetval->nodeNr) { 224 | return nil; 225 | } 226 | 227 | return [self objectAtIndex:((NSInteger)self.cursor++)]; 228 | } 229 | 230 | @end 231 | 232 | #pragma mark - 233 | 234 | @implementation ONOXPathFunctionResult 235 | 236 | - (void)dealloc { 237 | if (_xmlXPath) { 238 | xmlXPathFreeObject(_xmlXPath); 239 | } 240 | } 241 | 242 | - (BOOL)boolValue { 243 | return self.xmlXPath->boolval > 0; 244 | } 245 | 246 | - (double)numericValue { 247 | return self.xmlXPath->floatval; 248 | } 249 | 250 | - (nullable NSNumber *)numberValue { 251 | if (!self.xmlXPath) { 252 | return nil; 253 | } 254 | 255 | return [NSNumber numberWithDouble:self.xmlXPath->floatval]; 256 | } 257 | 258 | - (nullable NSString *)stringValue { 259 | if (!self.xmlXPath) { 260 | return nil; 261 | } 262 | 263 | return [NSString stringWithCString:(char *)self.xmlXPath->stringval encoding:NSUTF8StringEncoding]; 264 | } 265 | 266 | 267 | @end 268 | 269 | #pragma mark - 270 | 271 | @implementation ONOXMLDocument 272 | 273 | + (nullable instancetype)XMLDocumentWithString:(NSString *)string 274 | encoding:(NSStringEncoding)encoding 275 | error:(NSError * __autoreleasing *)error 276 | { 277 | return [self XMLDocumentWithData:[string dataUsingEncoding:encoding] error:error]; 278 | } 279 | 280 | + (nullable instancetype)XMLDocumentWithData:(nullable NSData *)data 281 | error:(NSError * __autoreleasing *)error 282 | { 283 | xmlDocPtr document = xmlReadMemory([data bytes], (int)[data length], "", nil, XML_PARSE_NOWARNING | XML_PARSE_NOERROR | XML_PARSE_RECOVER); 284 | if (!document) { 285 | ONOSetErrorFromXMLErrorPtr(error, xmlGetLastError()); 286 | return nil; 287 | } 288 | 289 | xmlResetLastError(); 290 | 291 | return [[self alloc] initWithDocument:document]; 292 | } 293 | 294 | + (nullable instancetype)HTMLDocumentWithString:(NSString *)string 295 | encoding:(NSStringEncoding)encoding 296 | error:(NSError * __autoreleasing *)error 297 | { 298 | return [self HTMLDocumentWithData:[string dataUsingEncoding:encoding] error:error]; 299 | } 300 | 301 | + (nullable instancetype)HTMLDocumentWithData:(nullable NSData *)data 302 | error:(NSError * __autoreleasing *)error 303 | { 304 | xmlDocPtr document = htmlReadMemory([data bytes], (int)[data length], "", nil, HTML_PARSE_NOWARNING | HTML_PARSE_NOERROR | HTML_PARSE_RECOVER); 305 | if (!document) { 306 | ONOSetErrorFromXMLErrorPtr(error, xmlGetLastError()); 307 | return nil; 308 | } 309 | 310 | xmlResetLastError(); 311 | 312 | return [[self alloc] initWithDocument:document]; 313 | } 314 | 315 | #pragma mark - 316 | 317 | - (instancetype)initWithDocument:(xmlDocPtr)document { 318 | self = [super init]; 319 | if (!self) { 320 | return nil; 321 | } 322 | 323 | _xmlDocument = document; 324 | if (self.xmlDocument) { 325 | self.rootElement = (ONOXMLElement * )[self elementWithNode:xmlDocGetRootElement(self.xmlDocument)]; 326 | } 327 | 328 | return self; 329 | } 330 | 331 | - (void)dealloc { 332 | if (_xmlDocument) { 333 | xmlFreeDoc(_xmlDocument); 334 | } 335 | } 336 | 337 | #pragma mark - 338 | 339 | - (NSNumberFormatter *)numberFormatter { 340 | if (!_numberFormatter) { 341 | NSNumberFormatter *numberFormatter = [[NSNumberFormatter alloc] init]; 342 | [numberFormatter setNumberStyle:NSNumberFormatterDecimalStyle]; 343 | self.numberFormatter = numberFormatter; 344 | } 345 | 346 | return _numberFormatter; 347 | } 348 | 349 | - (NSDateFormatter *)dateFormatter { 350 | if (!_dateFormatter) { 351 | NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init]; 352 | [dateFormatter setLocale:[[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"]]; 353 | [dateFormatter setDateFormat:@"yyyy-MM-dd'T'HH:mm:ssZ"]; 354 | self.dateFormatter = dateFormatter; 355 | } 356 | 357 | return _dateFormatter; 358 | } 359 | 360 | #pragma mark - 361 | 362 | - (void)definePrefix:(NSString *)prefix 363 | forDefaultNamespace:(NSString *)ns 364 | { 365 | if (!self.defaultNamespaces) { 366 | self.defaultNamespaces = [NSMutableDictionary dictionary]; 367 | } 368 | 369 | self.defaultNamespaces[ns] = prefix; 370 | } 371 | 372 | #pragma mark - Private Methods 373 | 374 | - (nullable ONOXMLElement *)elementWithNode:(xmlNodePtr)node { 375 | if (!node) { 376 | return nil; 377 | } 378 | 379 | ONOXMLElement *element = [[ONOXMLElement alloc] init]; 380 | element.xmlNode = node; 381 | element.document = self; 382 | 383 | return element; 384 | } 385 | 386 | - (nullable ONOXPathEnumerator *)enumeratorWithXPathObject:(xmlXPathObjectPtr)XPath { 387 | if (!XPath || xmlXPathNodeSetIsEmpty(XPath->nodesetval)) { 388 | xmlXPathFreeObject(XPath); 389 | return nil; 390 | } 391 | 392 | ONOXPathEnumerator *enumerator = [[ONOXPathEnumerator alloc] init]; 393 | enumerator.xmlXPath = XPath; 394 | enumerator.document = self; 395 | 396 | return enumerator; 397 | } 398 | 399 | #pragma mark - ONOSearching 400 | 401 | - (id )XPath:(NSString *)XPath { 402 | return [self.rootElement XPath:XPath]; 403 | } 404 | 405 | - (nullable ONOXPathFunctionResult *)functionResultByEvaluatingXPath:(NSString *)XPath { 406 | return [self.rootElement functionResultByEvaluatingXPath:XPath]; 407 | } 408 | 409 | #pragma GCC diagnostic push 410 | #pragma GCC diagnostic ignored "-Wdeprecated-implementations" 411 | - (void)enumerateElementsWithXPath:(NSString *)XPath 412 | block:(void (^)(ONOXMLElement *element))block 413 | { 414 | if (!block) { 415 | return; 416 | } 417 | 418 | [self.rootElement enumerateElementsWithXPath:XPath usingBlock:^(ONOXMLElement *element, __unused NSUInteger idx, __unused BOOL *stop) { 419 | block(element); 420 | }]; 421 | } 422 | #pragma GCC diagnostic pop 423 | 424 | - (void)enumerateElementsWithXPath:(NSString *)XPath 425 | usingBlock:(void (^)(ONOXMLElement *element, NSUInteger idx, BOOL *stop))block 426 | { 427 | [self.rootElement enumerateElementsWithXPath:XPath usingBlock:block]; 428 | } 429 | 430 | - (nullable ONOXMLElement *)firstChildWithXPath:(NSString *)XPath { 431 | return [self.rootElement firstChildWithXPath:XPath]; 432 | } 433 | 434 | - (id )CSS:(NSString *)CSS { 435 | return [self.rootElement CSS:CSS]; 436 | } 437 | 438 | #pragma GCC diagnostic push 439 | #pragma GCC diagnostic ignored "-Wdeprecated-declarations" 440 | #pragma GCC diagnostic ignored "-Wdeprecated-implementations" 441 | - (void)enumerateElementsWithCSS:(NSString *)CSS 442 | block:(void (^)(ONOXMLElement *))block 443 | { 444 | [self.rootElement enumerateElementsWithCSS:CSS block:block]; 445 | } 446 | #pragma GCC diagnostic pop 447 | 448 | - (void)enumerateElementsWithCSS:(NSString *)CSS 449 | usingBlock:(void (^)(ONOXMLElement *element, NSUInteger idx, BOOL *stop))block 450 | { 451 | [self.rootElement enumerateElementsWithCSS:CSS usingBlock:block]; 452 | } 453 | 454 | - (nullable ONOXMLElement *)firstChildWithCSS:(NSString *)CSS { 455 | return [self.rootElement firstChildWithCSS:CSS]; 456 | } 457 | 458 | #pragma mark - 459 | 460 | - (NSString *)version { 461 | if (!_version && self.xmlDocument->version != NULL) { 462 | self.version = (NSString *)@((const char *)self.xmlDocument->version); 463 | } 464 | 465 | return _version; 466 | } 467 | 468 | - (NSStringEncoding)stringEncoding { 469 | if (!_stringEncoding && self.xmlDocument->encoding != NULL) { 470 | NSString *encodingName = @((const char *)self.xmlDocument->encoding); 471 | CFStringEncoding encoding = CFStringConvertIANACharSetNameToEncoding((CFStringRef)encodingName); 472 | if (encoding != kCFStringEncodingInvalidId) { 473 | self.stringEncoding = CFStringConvertEncodingToNSStringEncoding(encoding); 474 | } 475 | } 476 | 477 | return _stringEncoding; 478 | } 479 | 480 | #pragma mark - NSObject 481 | 482 | - (NSString *)description { 483 | return [self.rootElement description]; 484 | } 485 | 486 | - (BOOL)isEqual:(id)object { 487 | if (self == object) { 488 | return YES; 489 | } 490 | 491 | if (![object isKindOfClass:[self class]]) { 492 | return NO; 493 | } 494 | 495 | return [self hash] == [object hash]; 496 | } 497 | 498 | - (NSUInteger)hash { 499 | return (NSUInteger)self.xmlDocument; 500 | } 501 | 502 | #pragma mark - NSCopying 503 | 504 | - (id)copyWithZone:(nullable NSZone *)zone { 505 | ONOXMLDocument *document = [[[self class] allocWithZone:zone] init]; 506 | document.version = self.version; 507 | document.rootElement = self.rootElement; 508 | 509 | return document; 510 | } 511 | 512 | #pragma mark - NSCoding 513 | 514 | - (nullable instancetype)initWithCoder:(NSCoder *)decoder { 515 | self = [super init]; 516 | if (!self) { 517 | return nil; 518 | } 519 | 520 | NSString *version = [decoder decodeObjectForKey:NSStringFromSelector(@selector(version))]; 521 | ONOXMLElement *rootElement = [decoder decodeObjectForKey:NSStringFromSelector(@selector(rootElement))]; 522 | 523 | if (!version || !rootElement) { 524 | return nil; 525 | } 526 | 527 | self.version = version; 528 | self.rootElement = rootElement; 529 | 530 | return self; 531 | } 532 | 533 | - (void)encodeWithCoder:(NSCoder *)coder { 534 | [coder encodeObject:self.version forKey:NSStringFromSelector(@selector(version))]; 535 | [coder encodeObject:self.rootElement forKey:NSStringFromSelector(@selector(rootElement))]; 536 | } 537 | 538 | @end 539 | 540 | #pragma mark - 541 | 542 | @interface ONOXMLElement () 543 | @property (readwrite, nonatomic, copy) NSString *rawXMLString; 544 | @property (readwrite, nonatomic, copy) NSString *tag; 545 | @property (readwrite, nonatomic, assign) NSUInteger lineNumber; 546 | #ifdef __cplusplus 547 | @property (nullable, readwrite, nonatomic, copy) NSString *ns; 548 | #else 549 | @property (nullable, readwrite, nonatomic, copy) NSString *namespace; 550 | #endif 551 | @property (nullable, readwrite, nonatomic, strong) ONOXMLElement *parent; 552 | @property (readwrite, nonatomic, strong) NSArray *children; 553 | @property (nullable, readwrite, nonatomic, strong) ONOXMLElement *previousSibling; 554 | @property (nullable, readwrite, nonatomic, strong) ONOXMLElement *nextSibling; 555 | @property (readwrite, nonatomic, strong) NSDictionary *attributes; 556 | @property (nullable, readwrite, nonatomic, copy) NSString *stringValue; 557 | @property (nullable, readwrite, nonatomic, copy) NSNumber *numberValue; 558 | @property (nullable, readwrite, nonatomic, copy) NSDate *dateValue; 559 | @end 560 | 561 | @implementation ONOXMLElement 562 | @dynamic children; 563 | 564 | #ifdef __cplusplus 565 | - (nullable NSString *)ns { 566 | if (!_ns && self.xmlNode->ns != NULL && self.xmlNode->ns->prefix != NULL) { 567 | self.ns = @((const char *)self.xmlNode->ns->prefix); 568 | } 569 | 570 | return _ns; 571 | } 572 | #else 573 | - (nullable NSString *)namespace { 574 | if (!_namespace && self.xmlNode->ns != NULL && self.xmlNode->ns->prefix != NULL) { 575 | self.namespace = @((const char *)self.xmlNode->ns->prefix); 576 | } 577 | 578 | return _namespace; 579 | } 580 | #endif 581 | 582 | - (NSString *)tag { 583 | if (!_tag && self.xmlNode->name != NULL) { 584 | self.tag = (NSString *)@((const char *)self.xmlNode->name); 585 | } 586 | 587 | return _tag; 588 | } 589 | 590 | - (NSUInteger)lineNumber { 591 | if (!_lineNumber) { 592 | self.lineNumber = (NSUInteger)xmlGetLineNo(self.xmlNode); 593 | } 594 | 595 | return _lineNumber; 596 | } 597 | 598 | #pragma mark - 599 | 600 | - (NSDictionary *)attributes { 601 | if (!_attributes) { 602 | NSMutableDictionary *mutableAttributes = [NSMutableDictionary dictionary]; 603 | for (xmlAttrPtr attribute = self.xmlNode->properties; attribute != NULL; attribute = attribute->next) { 604 | NSString *key = @((const char *)attribute->name); 605 | [mutableAttributes setObject:(id)[self valueForAttribute:key] forKey:key]; 606 | } 607 | 608 | self.attributes = [NSDictionary dictionaryWithDictionary:mutableAttributes]; 609 | } 610 | 611 | return _attributes; 612 | } 613 | 614 | - (nullable id)valueForAttribute:(NSString *)attribute { 615 | id value = nil; 616 | xmlChar *xmlValue = xmlGetProp(self.xmlNode, (const xmlChar *)[attribute UTF8String]); 617 | if (xmlValue) { 618 | value = @((const char *)xmlValue); 619 | xmlFree(xmlValue); 620 | } 621 | 622 | return value; 623 | } 624 | 625 | - (nullable id)valueForAttribute:(NSString *)attribute 626 | inNamespace:(nullable NSString *)ns 627 | { 628 | id value = nil; 629 | xmlChar *xmlValue = xmlGetNsProp(self.xmlNode, (const xmlChar *)[attribute UTF8String], (const xmlChar *)[ns UTF8String]); 630 | if (xmlValue) { 631 | value = @((const char *)xmlValue); 632 | xmlFree(xmlValue); 633 | } 634 | 635 | return value; 636 | } 637 | 638 | #pragma mark - 639 | 640 | - (nullable ONOXMLElement *)parent { 641 | if (!_parent) { 642 | self.parent = [self.document elementWithNode:self.xmlNode->parent]; 643 | } 644 | 645 | return _parent; 646 | } 647 | 648 | - (NSArray *)children { 649 | return [self childrenAtIndexes:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, NSIntegerMax)]]; 650 | } 651 | 652 | - (nullable ONOXMLElement *)firstChildWithTag:(NSString *)tag { 653 | return [self firstChildWithTag:tag inNamespace:nil]; 654 | } 655 | 656 | - (nullable ONOXMLElement *)firstChildWithTag:(NSString *)tag 657 | inNamespace:(nullable NSString *)ns 658 | { 659 | NSArray *children = [self childrenAtIndexes:[self indexesOfChildrenPassingTest:^BOOL(xmlNodePtr node, BOOL *stop) { 660 | *stop = ONOXMLNodeMatchesTagInNamespace(node, tag, ns); 661 | return *stop; 662 | }]]; 663 | 664 | if ([children count] == 0) { 665 | return nil; 666 | } 667 | 668 | return [children objectAtIndex:0]; 669 | } 670 | 671 | - (NSArray *)childrenWithTag:(NSString *)tag { 672 | return [self childrenWithTag:tag inNamespace:nil]; 673 | } 674 | 675 | - (NSArray *)childrenWithTag:(NSString *)tag 676 | inNamespace:(nullable NSString *)ns 677 | { 678 | return [self childrenAtIndexes:[self indexesOfChildrenPassingTest:^BOOL(xmlNodePtr node, __unused BOOL *stop) { 679 | return ONOXMLNodeMatchesTagInNamespace(node, tag, ns); 680 | }]]; 681 | } 682 | 683 | - (NSArray *)childrenAtIndexes:(NSIndexSet *)indexes { 684 | NSMutableArray *mutableChildren = [NSMutableArray array]; 685 | 686 | xmlNodePtr cursor = self.xmlNode->children; 687 | NSUInteger idx = 0; 688 | while (cursor) { 689 | if ([indexes containsIndex:idx] && cursor->type == XML_ELEMENT_NODE) { 690 | ONOXMLElement *element = [self.document elementWithNode:cursor]; 691 | if (element) { 692 | [mutableChildren addObject:element]; 693 | } 694 | } 695 | 696 | cursor = cursor->next; 697 | idx++; 698 | } 699 | 700 | return [NSArray arrayWithArray:mutableChildren]; 701 | } 702 | 703 | - (NSIndexSet *)indexesOfChildrenPassingTest:(BOOL (^)(xmlNodePtr node, BOOL *stop))block { 704 | if (!block) { 705 | return [NSIndexSet indexSet]; 706 | } 707 | 708 | NSMutableIndexSet *mutableIndexSet = [NSMutableIndexSet indexSet]; 709 | 710 | xmlNodePtr cursor = self.xmlNode->children; 711 | NSUInteger idx = 0; 712 | BOOL stop = NO; 713 | while (cursor && !stop) { 714 | if (block(cursor, &stop)) { 715 | [mutableIndexSet addIndex:idx]; 716 | } 717 | 718 | cursor = cursor->next; 719 | idx++; 720 | } 721 | 722 | return mutableIndexSet; 723 | } 724 | 725 | - (nullable ONOXMLElement *)previousSibling { 726 | if (!_previousSibling) { 727 | self.previousSibling = [self.document elementWithNode:self.xmlNode->prev]; 728 | } 729 | 730 | return _previousSibling; 731 | } 732 | 733 | - (nullable ONOXMLElement *)nextSibling { 734 | if (!_nextSibling) { 735 | self.nextSibling = [self.document elementWithNode:self.xmlNode->next]; 736 | } 737 | 738 | return _nextSibling; 739 | } 740 | 741 | #pragma mark - 742 | 743 | - (BOOL)isBlank { 744 | return [[self stringValue] length] == 0; 745 | } 746 | 747 | - (nullable NSString *)stringValue { 748 | if (!_stringValue) { 749 | xmlChar *key = xmlNodeGetContent(self.xmlNode); 750 | if (key) { 751 | self.stringValue = [NSString stringWithCString:(const char *)key encoding:NSUTF8StringEncoding]; 752 | } 753 | xmlFree(key); 754 | } 755 | 756 | return _stringValue; 757 | } 758 | 759 | - (nullable NSNumber *)numberValue { 760 | if (!_numberValue) { 761 | NSString *stringValue = self.stringValue; 762 | if (!stringValue) { 763 | return nil; 764 | } 765 | 766 | self.numberValue = [self.document.numberFormatter numberFromString:stringValue]; 767 | } 768 | 769 | return _numberValue; 770 | } 771 | 772 | - (nullable NSDate *)dateValue { 773 | if (!_dateValue) { 774 | NSString *stringValue = self.stringValue; 775 | if (!stringValue) { 776 | return nil; 777 | } 778 | 779 | self.dateValue = [self.document.dateFormatter dateFromString:stringValue]; 780 | } 781 | 782 | return _dateValue; 783 | } 784 | 785 | #pragma mark - 786 | 787 | - (nullable id)objectForKeyedSubscript:(id)key { 788 | return [self valueForAttribute:key]; 789 | } 790 | 791 | - (nullable id)objectAtIndexedSubscript:(NSUInteger)idx { 792 | return [self.children objectAtIndex:idx]; 793 | } 794 | 795 | #pragma mark - NSObject 796 | 797 | - (NSString *)description { 798 | xmlBufferPtr buffer = xmlBufferCreate(); 799 | xmlNodeDump(buffer, self.xmlNode->doc, self.xmlNode, 0, false); 800 | NSString *rawXMLString = @((const char *)xmlBufferContent(buffer)); 801 | xmlBufferFree(buffer); 802 | 803 | return rawXMLString; 804 | } 805 | 806 | - (BOOL)isEqual:(id)object { 807 | if (self == object) { 808 | return YES; 809 | } 810 | 811 | if (![object isKindOfClass:[self class]]) { 812 | return NO; 813 | } 814 | 815 | return [self hash] == [object hash]; 816 | } 817 | 818 | - (NSUInteger)hash { 819 | return (NSUInteger)self.xmlNode; 820 | } 821 | 822 | #pragma mark - ONOSearching 823 | 824 | - (id )XPath:(NSString *)XPath { 825 | if (!XPath) { 826 | return [[NSArray array] objectEnumerator]; 827 | } 828 | 829 | xmlXPathObjectPtr xmlXPath = [self xmlXPathObjectPtrWithXPath:XPath]; 830 | if (!xmlXPath) { 831 | return [[NSArray array] objectEnumerator];; 832 | } 833 | 834 | return (id )[self.document enumeratorWithXPathObject:xmlXPath]; 835 | } 836 | 837 | - (nullable ONOXPathFunctionResult *)functionResultByEvaluatingXPath:(NSString *)XPath { 838 | xmlXPathObjectPtr xmlXPath = [self xmlXPathObjectPtrWithXPath:XPath]; 839 | if (xmlXPath) { 840 | ONOXPathFunctionResult *result = [[ONOXPathFunctionResult alloc] init]; 841 | result.xmlXPath = xmlXPath; 842 | 843 | return result; 844 | } 845 | 846 | return nil; 847 | } 848 | 849 | #pragma GCC diagnostic push 850 | #pragma GCC diagnostic ignored "-Wdeprecated-declarations" 851 | #pragma GCC diagnostic ignored "-Wdeprecated-implementations" 852 | - (void)enumerateElementsWithXPath:(NSString *)XPath 853 | block:(void (^)(ONOXMLElement *element))block 854 | { 855 | if (!block) { 856 | return; 857 | } 858 | 859 | [self enumerateElementsWithXPath:XPath usingBlock:^(ONOXMLElement *element, __unused NSUInteger idx, __unused BOOL *stop) { 860 | block(element); 861 | }]; 862 | } 863 | #pragma GCC diagnostic pop 864 | 865 | - (void)enumerateElementsWithXPath:(NSString *)XPath 866 | usingBlock:(void (^)(ONOXMLElement *element, NSUInteger idx, BOOL *stop))block 867 | { 868 | if (!block) { 869 | return; 870 | } 871 | 872 | NSUInteger idx = 0; 873 | BOOL stop = NO; 874 | for (ONOXMLElement *element in [self XPath:XPath]) { 875 | block(element, idx++, &stop); 876 | 877 | if (stop) { 878 | break; 879 | } 880 | } 881 | } 882 | 883 | - (nullable ONOXMLElement *)firstChildWithXPath:(NSString *)XPath 884 | { 885 | for (ONOXMLElement *element in [self XPath:XPath]) { 886 | return element; 887 | } 888 | 889 | return nil; 890 | } 891 | 892 | - (id )CSS:(NSString *)CSS { 893 | return [self XPath:ONOXPathFromCSS(CSS)]; 894 | } 895 | 896 | #pragma GCC diagnostic push 897 | #pragma GCC diagnostic ignored "-Wdeprecated-declarations" 898 | #pragma GCC diagnostic ignored "-Wdeprecated-implementations" 899 | - (void)enumerateElementsWithCSS:(NSString *)CSS 900 | block:(void (^)(ONOXMLElement *element))block 901 | { 902 | if (!block) { 903 | return; 904 | } 905 | 906 | [self enumerateElementsWithCSS:ONOXPathFromCSS(CSS) usingBlock:^(ONOXMLElement *element, __unused NSUInteger idx, __unused BOOL *stop) { 907 | block(element); 908 | }]; 909 | } 910 | #pragma GCC diagnostic pop 911 | 912 | - (void)enumerateElementsWithCSS:(NSString *)CSS 913 | usingBlock:(void (^)(ONOXMLElement *element, NSUInteger idx, BOOL *stop))block 914 | { 915 | [self enumerateElementsWithXPath:ONOXPathFromCSS(CSS) usingBlock:block]; 916 | } 917 | 918 | - (nullable ONOXMLElement *)firstChildWithCSS:(NSString *)CSS 919 | { 920 | for (ONOXMLElement *element in [self CSS:CSS]) { 921 | return element; 922 | } 923 | 924 | return nil; 925 | } 926 | 927 | #pragma mark - 928 | 929 | - (nullable xmlXPathObjectPtr)xmlXPathObjectPtrWithXPath:(NSString *)XPath { 930 | xmlXPathContextPtr context = xmlXPathNewContext(self.xmlNode->doc); 931 | if (context) { 932 | context->node = self.xmlNode; 933 | 934 | // Due to a bug in libxml2, namespaces may not appear in `xmlNode->ns`. 935 | // As a workaround, `xmlNode->nsDef` is recursed to explicitly register namespaces. 936 | for (xmlNodePtr node = self.xmlNode; node->parent != NULL; node = node->parent) { 937 | for (xmlNsPtr ns = node->nsDef; ns != NULL; ns = ns->next) { 938 | const xmlChar *prefix = ns->prefix; 939 | if (!prefix && self.document.defaultNamespaces) { 940 | NSString *href = @((const char *)ns->href); 941 | NSString *defaultPrefix = self.document.defaultNamespaces[href]; 942 | if (defaultPrefix) { 943 | prefix = (const xmlChar *)[defaultPrefix UTF8String]; 944 | } 945 | } 946 | 947 | if (prefix) { 948 | xmlXPathRegisterNs(context, prefix, ns->href); 949 | } 950 | } 951 | } 952 | 953 | xmlXPathObjectPtr xmlXPath = xmlXPathEvalExpression((const xmlChar *)[XPath UTF8String], context); 954 | xmlXPathFreeContext(context); 955 | 956 | return xmlXPath; 957 | } 958 | 959 | return NULL; 960 | } 961 | 962 | #pragma mark - NSObject 963 | 964 | - (nullable NSMethodSignature *)methodSignatureForSelector:(SEL)selector { 965 | return [[self stringValue] methodSignatureForSelector:selector]; 966 | } 967 | 968 | - (void)forwardInvocation:(NSInvocation *)invocation { 969 | NSString *stringValue = self.stringValue; 970 | if (stringValue) { 971 | [invocation invokeWithTarget:stringValue]; 972 | } 973 | } 974 | 975 | #pragma mark - NSCopying 976 | 977 | - (id)copyWithZone:(nullable NSZone *)zone { 978 | ONOXMLElement *element = [[[self class] allocWithZone:zone] init]; 979 | element.xmlNode = self.xmlNode; 980 | element.document = self.document; 981 | 982 | return element; 983 | } 984 | 985 | #pragma mark - NSCoding 986 | 987 | - (nullable instancetype)initWithCoder:(NSCoder *)decoder { 988 | self = [super init]; 989 | if (!self) { 990 | return nil; 991 | } 992 | 993 | NSString *tag = [decoder decodeObjectForKey:NSStringFromSelector(@selector(tag))]; 994 | NSDictionary *attributes = [decoder decodeObjectForKey:NSStringFromSelector(@selector(attributes))]; 995 | NSString *stringValue = [decoder decodeObjectForKey:NSStringFromSelector(@selector(stringValue))]; 996 | NSArray *children = [decoder decodeObjectForKey:NSStringFromSelector(@selector(children))]; 997 | 998 | if (!tag || !attributes || !stringValue || !children) { 999 | return nil; 1000 | } 1001 | 1002 | self.tag = tag; 1003 | self.attributes = attributes; 1004 | self.stringValue = stringValue; 1005 | self.children = children; 1006 | 1007 | return self; 1008 | } 1009 | 1010 | - (void)encodeWithCoder:(NSCoder *)coder { 1011 | [coder encodeObject:self.tag forKey:NSStringFromSelector(@selector(tag))]; 1012 | [coder encodeObject:self.attributes forKey:NSStringFromSelector(@selector(attributes))]; 1013 | [coder encodeObject:self.stringValue forKey:NSStringFromSelector(@selector(stringValue))]; 1014 | [coder encodeObject:self.children forKey:NSStringFromSelector(@selector(children))]; 1015 | } 1016 | 1017 | @end 1018 | 1019 | NS_ASSUME_NONNULL_END 1020 | 1021 | #pragma clang diagnostic pop 1022 | -------------------------------------------------------------------------------- /Source/Ono.h: -------------------------------------------------------------------------------- 1 | // Ono.h 2 | // 3 | // Copyright (c) 2014 – 2018 Mattt (https://mat.tt) 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 11 | // 12 | // The above copyright notice and this permission notice shall be included in 13 | // all copies or substantial portions of the Software. 14 | // 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | // THE SOFTWARE. 22 | 23 | @import Foundation; 24 | 25 | //! Project version number for Ono iOS. 26 | FOUNDATION_EXPORT double Ono_VersionNumber; 27 | 28 | //! Project version string for Ono iOS. 29 | FOUNDATION_EXPORT const unsigned char Ono_VersionString[]; 30 | 31 | #import 32 | -------------------------------------------------------------------------------- /Tests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundlePackageType 14 | BNDL 15 | CFBundleShortVersionString 16 | 1.0 17 | CFBundleSignature 18 | ???? 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /Tests/ONOAtomTests.m: -------------------------------------------------------------------------------- 1 | // ONOXMLTests.m 2 | // 3 | // Copyright (c) 2014 – 2018 Mattt (https://mat.tt) 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 11 | // 12 | // The above copyright notice and this permission notice shall be included in 13 | // all copies or substantial portions of the Software. 14 | // 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | // THE SOFTWARE. 22 | 23 | #import 24 | 25 | #import "Ono.h" 26 | 27 | @interface ONOAtomTests : XCTestCase 28 | @property (nonatomic, strong) ONOXMLDocument *document; 29 | @end 30 | 31 | @implementation ONOAtomTests 32 | 33 | - (void)setUp { 34 | [super setUp]; 35 | 36 | NSError *error = nil; 37 | NSString *filePath = [[NSBundle bundleForClass:[self class]] pathForResource:@"atom" ofType:@"xml"]; 38 | self.document = [ONOXMLDocument XMLDocumentWithData:[NSData dataWithContentsOfFile:filePath] error:&error]; 39 | [self.document definePrefix:@"atom" forDefaultNamespace:@"http://www.w3.org/2005/Atom"]; 40 | 41 | XCTAssertNotNil(self.document, @"Document should not be nil"); 42 | XCTAssertNil(error, @"Error should not be generated"); 43 | } 44 | 45 | #pragma mark - 46 | 47 | - (void)testXMLVersion { 48 | XCTAssertEqualObjects(self.document.version, @"1.0", @"XML version should be 1.0"); 49 | } 50 | 51 | - (void)testXMLEncoding { 52 | XCTAssertEqual(self.document.stringEncoding, NSUTF8StringEncoding, @"XML encoding should be UTF-8"); 53 | } 54 | 55 | - (void)testRootElement { 56 | XCTAssertEqualObjects(self.document.rootElement.tag, @"feed", @"root element should be feed"); 57 | // XCTAssertEqualObjects(self.document.rootElement.namespace, @"http://www.w3.org/2005/Atom", @"XML namespace should be Atom"); 58 | } 59 | 60 | - (void)testTitle { 61 | ONOXMLElement *titleElement = [self.document.rootElement firstChildWithTag:@"title"]; 62 | 63 | XCTAssertNotNil(titleElement, @"title element should not be nil"); 64 | XCTAssertEqualObjects(titleElement.tag, @"title", @"tag should be `title`"); 65 | XCTAssertEqualObjects([titleElement stringValue], @"Example Feed", @"title string value should be 'Example Feed'"); 66 | } 67 | 68 | - (void)testXPathTitle { 69 | ONOXMLElement *titleElement = [self.document.rootElement firstChildWithXPath:@"/atom:feed/atom:title"]; 70 | 71 | XCTAssertNotNil(titleElement, @"title element should not be nil"); 72 | XCTAssertEqualObjects(titleElement.tag, @"title", @"tag should be `title`"); 73 | XCTAssertEqualObjects([titleElement stringValue], @"Example Feed", @"title string value should be 'Example Feed'"); 74 | } 75 | 76 | - (void)testLinks { 77 | NSArray *linkElements = [self.document.rootElement childrenWithTag:@"link"]; 78 | 79 | XCTAssertTrue([linkElements count] == 2, @"should have 2 link elements"); 80 | XCTAssertEqualObjects([linkElements[0] stringValue], @"", @"stringValue should be nil"); 81 | XCTAssertNotEqualObjects(linkElements[0][@"href"], linkElements[1][@"href"], @"href values should not be equal"); 82 | } 83 | 84 | - (void)testUpdated { 85 | ONOXMLElement *updatedElement = [self.document.rootElement firstChildWithTag:@"updated"]; 86 | 87 | XCTAssertNotNil([updatedElement dateValue], @"dateValue should not be nil"); 88 | XCTAssertTrue([[updatedElement dateValue] isKindOfClass:[NSDate class]], @"dateValue should be kind of NSDate"); 89 | 90 | NSDateComponents *dateComponents = [[NSDateComponents alloc] init]; 91 | dateComponents.timeZone = [NSTimeZone timeZoneWithAbbreviation:@"UTC"]; 92 | dateComponents.year = 2003; 93 | dateComponents.month = 12; 94 | dateComponents.day = 13; 95 | dateComponents.hour = 18; 96 | dateComponents.minute = 30; 97 | dateComponents.second = 2; 98 | 99 | XCTAssertEqualObjects([updatedElement dateValue], [[[NSCalendar alloc] initWithCalendarIdentifier:NSCalendarIdentifierGregorian] dateFromComponents:dateComponents], @"dateValue should be equal to December 13, 2003 6:30:02 PM"); 100 | } 101 | 102 | - (void)testEntries { 103 | NSArray *entryElements = [self.document.rootElement childrenWithTag:@"entry"]; 104 | XCTAssertTrue([entryElements count] == 1, @"should be 1 entry element"); 105 | } 106 | 107 | - (void)testNamespace { 108 | NSArray *entryElements = [self.document.rootElement childrenWithTag:@"entry"]; 109 | XCTAssertTrue([entryElements count] == 1, @"should be 1 entry element"); 110 | 111 | NSArray *namespacedElements = [[entryElements firstObject] childrenWithTag:@"language" inNamespace:@"dc"]; 112 | XCTAssertTrue([namespacedElements count] == 1, @"should be 1 entry element"); 113 | 114 | ONOXMLElement *namespacedElement = [namespacedElements firstObject]; 115 | XCTAssertNotNil(namespacedElement.namespace, @"the namespace shouldn't be nil"); 116 | XCTAssertTrue([namespacedElement.namespace isEqualToString:@"dc"], @"Namespaces should match"); 117 | } 118 | 119 | -(void)testXPathWithNamespaces { 120 | 121 | __block NSUInteger count = 0; 122 | [self.document enumerateElementsWithXPath:@"//dc:language" usingBlock:^(ONOXMLElement *element, NSUInteger idx, BOOL *stop) { 123 | XCTAssertNotNil(element.namespace, @"the namespace shouldn't be nil"); 124 | XCTAssertTrue([element.namespace isEqualToString:@"dc"], @"Namespaces should match"); 125 | count = idx + 1; 126 | }]; 127 | XCTAssertEqual(count, 1, @"should be 1 entry element"); 128 | } 129 | 130 | @end 131 | -------------------------------------------------------------------------------- /Tests/ONOCSSTests.m: -------------------------------------------------------------------------------- 1 | // ONOCSSTests.m 2 | // 3 | // Copyright (c) 2014 – 2018 Mattt (https://mat.tt) 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 11 | // 12 | // The above copyright notice and this permission notice shall be included in 13 | // all copies or substantial portions of the Software. 14 | // 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | // THE SOFTWARE. 22 | 23 | #import 24 | 25 | extern NSString * ONOXPathFromCSS(NSString *CSS); 26 | 27 | @interface ONOCSSTests : XCTestCase 28 | @end 29 | 30 | @implementation ONOCSSTests 31 | 32 | #pragma mark - 33 | 34 | - (void)testCSSWildcardSelector { 35 | XCTAssertEqualObjects(ONOXPathFromCSS(@"*"), @".//*"); 36 | } 37 | 38 | - (void)testCSSElementSelector { 39 | XCTAssertEqualObjects(ONOXPathFromCSS(@"div"), @".//div"); 40 | } 41 | 42 | - (void)testCSSClassSelector { 43 | XCTAssertEqualObjects(ONOXPathFromCSS(@".highlighted"), @".//*[contains(concat(' ',normalize-space(@class),' '),' highlighted ')]"); 44 | } 45 | 46 | - (void)testCSSElementAndClassSelector { 47 | XCTAssertEqualObjects(ONOXPathFromCSS(@"span.highlighted"), @".//span[contains(concat(' ',normalize-space(@class),' '),' highlighted ')]"); 48 | } 49 | 50 | - (void)testCSSElementAndIDSelector { 51 | XCTAssertEqualObjects(ONOXPathFromCSS(@"h1#logo"), @".//h1[@id = 'logo']"); 52 | } 53 | 54 | - (void)testCSSIDSelector { 55 | XCTAssertEqualObjects(ONOXPathFromCSS(@"#logo"), @".//*[@id = 'logo']"); 56 | } 57 | 58 | - (void)testCSSWildcardChildSelector { 59 | XCTAssertEqualObjects(ONOXPathFromCSS(@"html *"), @".//html//*"); 60 | } 61 | 62 | - (void)testCSSDescendantCombinatorSelector { 63 | XCTAssertEqualObjects(ONOXPathFromCSS(@"body p"), @".//body/descendant::p"); 64 | } 65 | 66 | - (void)testCSSChildCombinatorSelector { 67 | XCTAssertEqualObjects(ONOXPathFromCSS(@"ul > li"), @".//ul/li"); 68 | } 69 | 70 | - (void)testCSSAdjacentSiblingCombinatorSelector { 71 | XCTAssertEqualObjects(ONOXPathFromCSS(@"h1 + p"), @".//h1/following-sibling::*[1]/self::p"); 72 | } 73 | 74 | - (void)testCSSGeneralSiblingCombinatorSelector { 75 | XCTAssertEqualObjects(ONOXPathFromCSS(@"p ~ p"), @".//p/following-sibling::p"); 76 | } 77 | 78 | - (void)testCSSMultipleExpressionSelector { 79 | XCTAssertEqualObjects(ONOXPathFromCSS(@"ul, ol"), @".//ul | .//ol"); 80 | } 81 | 82 | - (void)testCSSAttributeSelector { 83 | XCTAssertEqualObjects(ONOXPathFromCSS(@"img[alt]"), @".//img[@alt]"); 84 | } 85 | 86 | - (void)testCSSCombinedSelector { 87 | XCTAssertEqualObjects(ONOXPathFromCSS(@"div#test .note span:first-child"), @".//div[@id = 'test']/descendant::*[contains(concat(' ',normalize-space(@class),' '),' note ')]/descendant::span:first-child"); 88 | } 89 | 90 | /* 91 | - (void)testCSSAttributeContainsValueSelector { 92 | XCTAssertEqualObjects(ONOXPathFromCSS(@"i[href~=\"icon\"]"), @"//i[contains(concat(' ', @class, ' '),concat(' ', 'icon', ' '))]"); 93 | } 94 | 95 | - (void)testCSSAttributeBeginsWithValueSelector { 96 | XCTAssertEqualObjects(ONOXPathFromCSS(@"a[href|=\"https://\"]"), @"//a[@href = 'https://' or starts-with(@href, concat('https://', '-'))]"); 97 | } 98 | */ 99 | 100 | @end 101 | -------------------------------------------------------------------------------- /Tests/ONODefaultNamespaceXPathTests.m: -------------------------------------------------------------------------------- 1 | // ONODefaultNamespaceXPathTests.m 2 | // 3 | // Copyright (c) 2014 – 2018 Mattt (https://mat.tt) 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 11 | // 12 | // The above copyright notice and this permission notice shall be included in 13 | // all copies or substantial portions of the Software. 14 | // 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | // THE SOFTWARE. 22 | 23 | #import 24 | 25 | #import "Ono.h" 26 | 27 | @interface ONODefaultNamespaceXPathTests : XCTestCase 28 | @property (nonatomic, strong) ONOXMLDocument *document; 29 | @end 30 | 31 | @implementation ONODefaultNamespaceXPathTests 32 | 33 | - (void)setUp { 34 | [super setUp]; 35 | 36 | NSError *error = nil; 37 | NSString *filePath = [[NSBundle bundleForClass:[self class]] pathForResource:@"ocf" ofType:@"xml"]; 38 | self.document = [ONOXMLDocument XMLDocumentWithData:[NSData dataWithContentsOfFile:filePath] error:&error]; 39 | 40 | XCTAssertNotNil(self.document, @"Document should not be nil"); 41 | XCTAssertNil(error, @"Error should not be generated"); 42 | } 43 | 44 | #pragma mark - 45 | 46 | - (void)testAbsoluteXPathWithDefaultNamespace { 47 | [self.document definePrefix:@"ocf" forDefaultNamespace:@"urn:oasis:names:tc:opendocument:xmlns:container"]; 48 | NSString *XPath = @"/ocf:container/ocf:rootfiles/ocf:rootfile"; 49 | NSUInteger count = 0; 50 | for (ONOXMLElement *element in [self.document XPath:XPath]) { 51 | XCTAssertEqualObjects(@"rootfile", element.tag, @"tag should be `rootfile`"); 52 | count++; 53 | } 54 | 55 | XCTAssertEqual(1, count, @"Element should be found at XPath '%@'", XPath); 56 | } 57 | 58 | - (void)testRelativeXPathWithDefaultNamespace { 59 | [self.document definePrefix:@"ocf" forDefaultNamespace:@"urn:oasis:names:tc:opendocument:xmlns:container"]; 60 | NSString *absoluteXPath = @"/ocf:container/ocf:rootfiles"; 61 | NSString *relativeXPath = @"./ocf:rootfile"; 62 | NSUInteger count = 0; 63 | for (ONOXMLElement *absoluteElement in [self.document XPath:absoluteXPath]) { 64 | for (ONOXMLElement *relativeElement in [absoluteElement XPath:relativeXPath]) { 65 | XCTAssertEqualObjects(@"rootfile", relativeElement.tag, @"tag should be `rootfile`"); 66 | count++; 67 | } 68 | } 69 | 70 | XCTAssertEqual(1, count, @"Element should be found at XPath '%@' relative to XPath '%@'", relativeXPath, absoluteXPath); 71 | } 72 | 73 | @end 74 | -------------------------------------------------------------------------------- /Tests/ONOHTMLTests.m: -------------------------------------------------------------------------------- 1 | // ONOHTMLTests.m 2 | // 3 | // Copyright (c) 2014 – 2018 Mattt (https://mat.tt) 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 11 | // 12 | // The above copyright notice and this permission notice shall be included in 13 | // all copies or substantial portions of the Software. 14 | // 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | // THE SOFTWARE. 22 | 23 | #import 24 | 25 | #import "Ono.h" 26 | 27 | @interface ONOHTMLTests : XCTestCase 28 | @property (nonatomic, strong) ONOXMLDocument *document; 29 | @end 30 | 31 | @implementation ONOHTMLTests 32 | 33 | - (void)setUp { 34 | [super setUp]; 35 | 36 | NSError *error = nil; 37 | NSString *filePath = [[NSBundle bundleForClass:[self class]] pathForResource:@"web" ofType:@"html"]; 38 | self.document = [ONOXMLDocument HTMLDocumentWithData:[NSData dataWithContentsOfFile:filePath] error:&error]; 39 | 40 | XCTAssertNotNil(self.document, @"Document should not be nil"); 41 | XCTAssertNil(error, @"Error should not be generated"); 42 | } 43 | 44 | #pragma mark - 45 | 46 | - (void)testRootElement { 47 | XCTAssertEqualObjects(self.document.rootElement.tag, @"html", @"html not root element"); 48 | } 49 | 50 | - (void)testRootElementChildren { 51 | NSArray *children = [self.document.rootElement children]; 52 | XCTAssertNotNil(children, @"children should not be nil"); 53 | XCTAssertTrue([children count] == 2, @"root element has more than two children"); 54 | XCTAssertEqualObjects([(ONOXMLElement *)[children firstObject] tag], @"head", @"head not first child of html"); 55 | XCTAssertEqualObjects([(ONOXMLElement *)[children lastObject] tag], @"body", @"body not last child of html"); 56 | } 57 | 58 | - (void)testTitleXPath { 59 | NSUInteger idx = 0; 60 | for (ONOXMLElement *element in [self.document XPath:@"//head/title"]) { 61 | XCTAssertTrue(idx == 0, @"more than one element found"); 62 | XCTAssertEqualObjects([element stringValue], @"mattt/Ono", @"title mismatch"); 63 | idx++; 64 | } 65 | XCTAssertTrue(idx == 1, @"fewer than one element found"); 66 | } 67 | 68 | - (void)testTitleCSS { 69 | NSUInteger idx = 0; 70 | for (ONOXMLElement *element in [self.document CSS:@"head title"]) { 71 | XCTAssertTrue(idx == 0, @"more than one element found"); 72 | XCTAssertEqualObjects([element stringValue], @"mattt/Ono", @"title mismatch"); 73 | idx++; 74 | } 75 | XCTAssertTrue(idx == 1, @"fewer than one element found"); 76 | } 77 | 78 | - (void)testIDCSS { 79 | NSUInteger idx = 0; 80 | for (ONOXMLElement *element in [self.document CSS:@"#account_settings"]) { 81 | XCTAssertTrue(idx == 0, @"more than one element found"); 82 | XCTAssertEqualObjects(element[@"href"], @"/settings/profile", @"href mismatch"); 83 | idx++; 84 | } 85 | XCTAssertTrue(idx == 1, @"fewer than one element found"); 86 | } 87 | 88 | @end 89 | -------------------------------------------------------------------------------- /Tests/ONOVMAPTests.m: -------------------------------------------------------------------------------- 1 | // ONOVMAPTests.m 2 | // 3 | // Copyright (c) 2014 – 2018 Mattt (https://mat.tt) 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 11 | // 12 | // The above copyright notice and this permission notice shall be included in 13 | // all copies or substantial portions of the Software. 14 | // 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | // THE SOFTWARE. 22 | 23 | #import 24 | 25 | #import "Ono.h" 26 | 27 | @interface ONOVMAPTests : XCTestCase 28 | @property (nonatomic, strong) ONOXMLDocument *document; 29 | @end 30 | 31 | @implementation ONOVMAPTests 32 | 33 | - (void)setUp { 34 | [super setUp]; 35 | 36 | NSError *error = nil; 37 | NSString *filePath = [[NSBundle bundleForClass:[self class]] pathForResource:@"vmap" ofType:@"xml"]; 38 | self.document = [ONOXMLDocument XMLDocumentWithData:[NSData dataWithContentsOfFile:filePath] error:&error]; 39 | 40 | XCTAssertNotNil(self.document, @"Document should not be nil"); 41 | XCTAssertNil(error, @"Error should not be generated"); 42 | } 43 | 44 | #pragma mark - 45 | 46 | - (void)testAbsoluteXPathWithNamespace { 47 | NSString *XPath = @"/vmap:VMAP/vmap:Extensions/uo:unicornOnce"; 48 | NSUInteger count = 0; 49 | for (ONOXMLElement *element in [self.document XPath:XPath]) { 50 | XCTAssertEqualObjects(@"unicornOnce", element.tag, @"tag should be `unicornOnce`"); 51 | count++; 52 | } 53 | 54 | XCTAssertEqual(1, count, @"Element should be found at XPath '%@'", XPath); 55 | } 56 | 57 | - (void)testRelativeXPathWithNamespace { 58 | NSString *absoluteXPath = @"/vmap:VMAP/vmap:Extensions"; 59 | NSString *relativeXPath = @"./uo:unicornOnce"; 60 | NSUInteger count = 0; 61 | for (ONOXMLElement *absoluteElement in [self.document XPath:absoluteXPath]) { 62 | for (ONOXMLElement *relativeElement in [absoluteElement XPath:relativeXPath]) { 63 | XCTAssertEqualObjects(@"unicornOnce", relativeElement.tag, @"tag should be `unicornOnce`"); 64 | count++; 65 | } 66 | } 67 | 68 | XCTAssertEqual(1, count, @"Element should be found at XPath '%@' relative to XPath '%@'", relativeXPath, absoluteXPath); 69 | } 70 | 71 | - (void)testUnicornOnceIsBlank { 72 | NSString *XPath = @"/vmap:VMAP/vmap:Extensions/uo:unicornOnce"; 73 | ONOXMLElement *element = [self.document firstChildWithXPath:XPath]; 74 | XCTAssertNotNil(element, @"Element should not be nil"); 75 | XCTAssertTrue([element isBlank], @"Element should be blank"); 76 | } 77 | 78 | @end 79 | -------------------------------------------------------------------------------- /Tests/ONOXMLTests.m: -------------------------------------------------------------------------------- 1 | // ONOXMLTests.m 2 | // 3 | // Copyright (c) 2014 – 2018 Mattt (https://mat.tt) 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 11 | // 12 | // The above copyright notice and this permission notice shall be included in 13 | // all copies or substantial portions of the Software. 14 | // 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | // THE SOFTWARE. 22 | 23 | #import 24 | 25 | #import 26 | 27 | @interface ONOXMLTests : XCTestCase 28 | @property (nonatomic, strong) ONOXMLDocument *document; 29 | @end 30 | 31 | @implementation ONOXMLTests 32 | 33 | - (void)setUp { 34 | [super setUp]; 35 | 36 | NSError *error = nil; 37 | NSString *filePath = [[NSBundle bundleForClass:[self class]] pathForResource:@"xml" ofType:@"xml"]; 38 | self.document = [ONOXMLDocument XMLDocumentWithData:[NSData dataWithContentsOfFile:filePath] error:&error]; 39 | 40 | XCTAssertNotNil(self.document, @"Document should not be nil"); 41 | XCTAssertNil(error, @"Error should not be generated"); 42 | } 43 | 44 | #pragma mark - 45 | 46 | - (void)testXMLVersion { 47 | XCTAssertEqualObjects(self.document.version, @"1.0", @"XML version should be 1.0"); 48 | } 49 | 50 | - (void)testXMLEncoding { 51 | XCTAssertEqual(self.document.stringEncoding, NSUTF8StringEncoding, @"XML encoding should be UTF-8"); 52 | } 53 | 54 | - (void)testRootElement { 55 | XCTAssertEqualObjects(self.document.rootElement.tag, @"spec", @"root element should be spec"); 56 | XCTAssertEqualObjects(self.document.rootElement.attributes[@"w3c-doctype"], @"rec", @"w3c-doctype should be rec"); 57 | XCTAssertEqualObjects(self.document.rootElement.attributes[@"lang"], @"en", @"xml:lang should be en"); 58 | } 59 | 60 | - (void)testTitle { 61 | ONOXMLElement *titleElement = [[self.document.rootElement firstChildWithTag:@"header"] firstChildWithTag:@"title"]; 62 | 63 | XCTAssertNotNil(titleElement, @"title element should not be nil"); 64 | XCTAssertEqualObjects(titleElement.tag, @"title", @"tag should be `title`"); 65 | XCTAssertEqualObjects([titleElement stringValue], @"Extensible Markup Language (XML)", @"title string value should be 'Extensible Markup Language (XML)'"); 66 | } 67 | 68 | - (void)testXPath { 69 | NSString *path = @"/spec/header/title"; 70 | id elts = [self.document XPath:path]; 71 | 72 | NSUInteger counter = 0; 73 | for (ONOXMLElement *elt in elts) 74 | { 75 | XCTAssertEqualObjects(@"title", elt.tag, @"tag should be `title`"); 76 | ++counter; 77 | } 78 | XCTAssertEqual(1, counter, @"at least one element should have been found at element path '%@'", path); 79 | } 80 | 81 | - (void)testLineNumber { 82 | ONOXMLElement *headerElement = [self.document.rootElement firstChildWithTag:@"header"]; 83 | 84 | XCTAssertNotNil(headerElement, @"header element should not be nil"); 85 | XCTAssertEqual(headerElement.lineNumber, 123, @"header line number should be correct"); 86 | } 87 | 88 | @end 89 | -------------------------------------------------------------------------------- /Tests/ONOXPathFunctionResultTests.m: -------------------------------------------------------------------------------- 1 | // ONOXPathFunctionResultTests.m 2 | // 3 | // Copyright (c) 2014 – 2018 Mattt (https://mat.tt) 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 11 | // 12 | // The above copyright notice and this permission notice shall be included in 13 | // all copies or substantial portions of the Software. 14 | // 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | // THE SOFTWARE. 22 | 23 | #import 24 | 25 | #import "Ono.h" 26 | 27 | @interface ONOXPathFunctionResultTests : XCTestCase 28 | @property (nonatomic, strong) ONOXMLDocument *document; 29 | @end 30 | 31 | @implementation ONOXPathFunctionResultTests 32 | 33 | - (void)setUp { 34 | [super setUp]; 35 | 36 | NSError *error = nil; 37 | NSString *filePath = [[NSBundle bundleForClass:[self class]] pathForResource:@"atom" ofType:@"xml"]; 38 | self.document = [ONOXMLDocument XMLDocumentWithData:[NSData dataWithContentsOfFile:filePath] error:&error]; 39 | [self.document definePrefix:@"atom" forDefaultNamespace:@"http://www.w3.org/2005/Atom"]; 40 | 41 | XCTAssertNotNil(self.document, @"Document should not be nil"); 42 | XCTAssertNil(error, @"Error should not be generated"); 43 | } 44 | 45 | #pragma mark - 46 | 47 | - (void)testFunctionResultBoolVaule { 48 | NSString *XPath = @"starts-with('Ono','O')"; 49 | ONOXPathFunctionResult *result = [self.document.rootElement functionResultByEvaluatingXPath:XPath]; 50 | XCTAssertTrue(result.boolValue, "Result should be true"); 51 | } 52 | 53 | - (void)testFunctionResultNumberValue { 54 | NSString *XPath = @"count(./atom:link)"; 55 | ONOXPathFunctionResult *result = [self.document.rootElement functionResultByEvaluatingXPath:XPath]; 56 | XCTAssertEqual([result.numberValue integerValue], 2, "Number of child links should be 2"); 57 | } 58 | 59 | - (void)testFunctionResultStringVaule { 60 | NSString *XPath = @"string(./atom:entry[1]/dc:language[1]/text())"; 61 | ONOXPathFunctionResult *result = [self.document.rootElement functionResultByEvaluatingXPath:XPath]; 62 | XCTAssertEqualObjects(result.stringValue, @"en-us", "Result stringValue should be `en-us`"); 63 | } 64 | 65 | @end 66 | -------------------------------------------------------------------------------- /Tests/atom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Example Feed 4 | A subtitle. 5 | 6 | 7 | urn:uuid:60a76c80-d399-11d9-b91C-0003939e0af6 8 | 2003-12-13T18:30:02Z 9 | 10 | Atom-Powered Robots Run Amok 11 | en-us 12 | 13 | 14 | 15 | urn:uuid:1225c695-cfb8-4ebb-aaaa-80da344efa6a 16 | 2003-12-13T18:30:02Z 17 | Some text. 18 | 19 | John Doe 20 | johndoe@example.com 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /Tests/ocf.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /Tests/vmap.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Tests/web.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | mattt/Ono 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | Skip to content 59 |
60 | 61 | 62 | 63 | 64 | 65 | 66 |
67 |
68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 |
79 |
80 | 81 | 88 | 89 | 90 | 91 |
92 | 93 | This repository 94 | 95 | 96 | 113 |
114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 |
123 | 129 |
130 | 131 | 132 | 133 | 134 | 165 | 166 | 190 | 191 | 192 | 193 |
194 |
195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 |
203 |
204 | 205 |
206 |
207 | 208 | 209 |
    210 | 211 |
  • 212 |
    213 | 214 |
    215 | 218 | 219 | 220 | 221 | Watch 222 | 223 | 224 | 225 |
    226 | 276 |
    277 |
    278 | 279 |
    280 |
  • 281 | 282 |
  • 283 | 284 | 285 | 302 | 303 |
  • 304 | 305 | 306 |
  • 307 | 308 | Fork 309 | 310 | 311 |
  • 312 | 313 | 314 |
315 | 316 |

317 | public 318 | 319 | 320 | 321 | 322 | / 323 | Ono 324 | 325 | 326 | Octocat-spinner-32 327 | 328 | 329 |

330 |
331 |
332 | 333 |
334 |
335 |
336 | 337 | 338 |
339 |
340 | 368 |
369 | 389 | 390 | 391 |
392 | 401 |
402 |
403 | 404 |
405 | 406 | 407 | 408 | 409 |
412 |

HTTPS clone URL

413 |
414 | 416 | 417 | 418 |
419 |
420 | 421 | 422 | 423 |
426 |

SSH clone URL

427 |
428 | 430 | 431 | 432 |
433 |
434 | 435 | 436 | 437 |
440 |

Subversion checkout URL

441 |
442 | 444 | 445 | 446 |
447 |
448 | 449 | 450 |

You can clone with 451 | HTTPS, 452 | SSH, 453 | or Subversion. 454 | 455 | 456 | 457 | 458 | 459 |

460 | 461 | 462 | 463 | Clone in Desktop 464 | 465 | 466 | 467 | 472 | 473 | Download ZIP 474 | 475 |
476 |
477 | 478 |
479 | 480 | 481 | 482 | 483 |
484 |
485 |

A sensible way to deal with XML & HTML for iOS & Mac OS X

486 |
487 | 488 | 489 | Edit 490 |
491 | 492 |
493 | 494 | 495 |
496 | 497 |
498 | 499 | 500 |
501 | 502 | 503 | or cancel 504 |
505 |
506 | 507 |
508 | 509 | 572 | 573 |
574 | 575 | 582 | 583 | 584 |
586 |
587 | 588 | 589 |
590 | 591 | 592 | 593 | 594 | 595 | 596 |
597 | 601 | 602 | branch: 603 | master 604 | 605 | 606 | 688 |
689 | 690 | 691 | 692 |
693 | 694 | 695 | 696 | 698 |
699 | 700 | 701 |
702 |

703 | Merge pull request #4 from slightair/fixDescendantCombinatorSelectorP… 712 | 713 |

714 |
…roblem
715 | 
716 | Fix CSS descendant combinator selector problem
717 |
718 | 719 | latest commit 903780b268 720 | 721 |
722 | Mattt 723 | 724 | authored 725 | 726 |
727 |
728 |
729 | 730 | 731 | 732 | 733 | 735 | 736 | 740 | 743 | 754 | 755 | 756 | 757 | 761 | 764 | 767 | 768 | 769 | 770 | 774 | 777 | 788 | 789 | 790 | 791 | 795 | 798 | 801 | 802 | 803 | 804 | 808 | 811 | 814 | 815 | 816 | 817 | 821 | 824 | 827 | 828 | 829 | 830 | 831 |
737 | 738 | Octocat-spinner-32 739 | 741 | Example 742 | 744 | Merge pull request #4 from slightair/fixDescendantCombinatorSelectorP… 753 |
758 | 759 | Octocat-spinner-32 760 | 762 | Ono.xcworkspace 763 | 765 | Adding workspace 766 |
771 | 772 | Octocat-spinner-32 773 | 775 | Ono 776 | 778 | Merge pull request #4 from slightair/fixDescendantCombinatorSelectorP… 787 |
792 | 793 | Octocat-spinner-32 794 | 796 | LICENSE 797 | 799 | Initial Import 800 |
805 | 806 | Octocat-spinner-32 807 | 809 | Ono.podspec 810 | 812 | Bumping version to 0.0.2 813 |
818 | 819 | Octocat-spinner-32 820 | 822 | README.md 823 | 825 | Bumping version to 0.0.1 826 |
832 |
833 | 834 |
835 | 836 | 837 | README.md 838 | 839 | 840 |

841 | Ono (斧)

842 | 843 |

A sensible way to deal with XML & HTML for iOS & Mac OS X

844 | 845 |
846 |

Ono (斧) means "axe", in homage to Nokogiri (鋸), which means "saw".

847 |
848 | 849 |

850 | Usage

851 | 852 |
#import "Ono.h"
853 | 
854 | NSData *data = ...;
855 | NSError *error;
856 | 
857 | ONOXMLDocument *document = [ONOXMLDocument XMLDocumentWithData:data error:&error];
858 | for (ONOXMLElement *element in document.rootElement.children) {
859 |     NSLog(@"%@: %@", element.tag, element.attributes);
860 | }
861 | 
862 | // Support for Namespaces
863 | NSString *author = [[document.rootElement firstChildWithTag:@"creator" inNamespace:@"dc"] stringValue];
864 | 
865 | // Automatic Conversion for Number & Date Values
866 | NSDate *date = [[document.rootElement firstChildWithTag:@"created_at"] dateValue]; // ISO 8601 Timestamp
867 | NSInteger numberOfWords = [[document.rootElement firstChildWithTag:@"word_count"] numberValue] integerValue];
868 | BOOL isPublished = [[document.rootElement firstChildWithTag:@"is_published"] numberValue] boolValue];
869 | 
870 | // Convenient Accessors for Attributes
871 | NSString *unit = [document.rootElement firstChildWithTag:@"Length"][@"unit"]
872 | NSDictionary *authorAttributes = [[document.rootElement firstChildWithTag:@"author"] attributes];
873 | 
874 | // Support for XPath & CSS Queries
875 | [document enumerateElementsWithXPath:@"//Content" block:^(ONOXMLElement *element) {
876 |     NSLog(@"%@", element);
877 | }];
878 | 
879 | 880 |

881 | Contact

882 | 883 |

Mattt 884 | @mattt

885 | 886 |

887 | License

888 | 889 |

Ono is available under the MIT license. See the LICENSE file for more info.

890 |
891 | 892 | 893 |
894 | 895 |
896 | 897 |
898 |
899 | 900 | 901 |
902 | 903 |
904 | 927 |
928 | 929 | 930 |
931 |
932 |
933 | 934 |
935 |
936 | 945 |
946 | 947 | 948 | 949 |
950 | 951 | 952 | Something went wrong with that request. Please try again. 953 |
954 | 955 | 956 | 957 | --------------------------------------------------------------------------------