├── .swift-version ├── logo.png ├── SwiftScanner-logo-source.sketch ├── SwiftScanner ├── .idea │ ├── SwiftScanner.iml │ ├── xcode.xml │ ├── modules.xml │ └── workspace.xml ├── SwiftScannerTests │ ├── SwiftScannerTests-Bridging-Header.h │ └── Info.plist ├── SwiftScanner.xcodeproj │ ├── xcuserdata │ │ ├── danielemm.xcuserdatad │ │ │ ├── xcdebugger │ │ │ │ └── Breakpoints_v2.xcbkptlist │ │ │ └── xcschemes │ │ │ │ ├── xcschememanagement.plist │ │ │ │ ├── SwiftScannerTests.xcscheme │ │ │ │ └── DemoApp.xcscheme │ │ ├── dan.xcuserdatad │ │ │ └── xcschemes │ │ │ │ └── xcschememanagement.plist │ │ ├── daniele.xcuserdatad │ │ │ └── xcschemes │ │ │ │ └── xcschememanagement.plist │ │ └── imuz.xcuserdatad │ │ │ └── xcschemes │ │ │ └── xcschememanagement.plist │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ ├── xcuserdata │ │ │ ├── dan.xcuserdatad │ │ │ │ └── UserInterfaceState.xcuserstate │ │ │ ├── imuz.xcuserdatad │ │ │ │ └── UserInterfaceState.xcuserstate │ │ │ ├── daniele.xcuserdatad │ │ │ │ └── UserInterfaceState.xcuserstate │ │ │ └── danielemm.xcuserdatad │ │ │ │ ├── UserInterfaceState.xcuserstate │ │ │ │ ├── xcschemes │ │ │ │ └── xcschememanagement.plist │ │ │ │ └── xcdebugger │ │ │ │ └── Expressions.xcexplist │ │ └── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ ├── xcshareddata │ │ ├── xcbaselines │ │ │ └── 084635E01DF6D60000BE9EF1.xcbaseline │ │ │ │ ├── 768D88BA-F54A-405E-8656-FEE29B4B4CC9.plist │ │ │ │ └── Info.plist │ │ └── xcschemes │ │ │ └── SwiftScanner.xcscheme │ └── project.pbxproj ├── SwiftScanner │ ├── SwiftScanner.h │ └── Info.plist └── DemoApp │ ├── ViewController.swift │ ├── Assets.xcassets │ └── AppIcon.appiconset │ │ └── Contents.json │ ├── Info.plist │ ├── Base.lproj │ ├── Main.storyboard │ └── LaunchScreen.storyboard │ └── AppDelegate.swift ├── SwiftScanner.podspec ├── Package.swift ├── LICENSE ├── .gitignore ├── Tests └── SwiftScannerTests │ ├── NSScanner+Extenions.swift │ └── TestSwiftScanner.swift ├── README.md └── Sources └── SwiftScanner └── StringScanner.swift /.swift-version: -------------------------------------------------------------------------------- 1 | 4.2 2 | -------------------------------------------------------------------------------- /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/malcommac/SwiftScanner/HEAD/logo.png -------------------------------------------------------------------------------- /SwiftScanner-logo-source.sketch: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/malcommac/SwiftScanner/HEAD/SwiftScanner-logo-source.sketch -------------------------------------------------------------------------------- /SwiftScanner/.idea/SwiftScanner.iml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /SwiftScanner/SwiftScannerTests/SwiftScannerTests-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | // 2 | // Use this file to import your target's public headers that you would like to expose to Swift. 3 | // 4 | 5 | -------------------------------------------------------------------------------- /SwiftScanner/SwiftScanner.xcodeproj/xcuserdata/danielemm.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | -------------------------------------------------------------------------------- /SwiftScanner/.idea/xcode.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /SwiftScanner/SwiftScanner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /SwiftScanner/SwiftScanner.xcodeproj/project.xcworkspace/xcuserdata/dan.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/malcommac/SwiftScanner/HEAD/SwiftScanner/SwiftScanner.xcodeproj/project.xcworkspace/xcuserdata/dan.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /SwiftScanner/SwiftScanner.xcodeproj/project.xcworkspace/xcuserdata/imuz.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/malcommac/SwiftScanner/HEAD/SwiftScanner/SwiftScanner.xcodeproj/project.xcworkspace/xcuserdata/imuz.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /SwiftScanner/SwiftScanner.xcodeproj/project.xcworkspace/xcuserdata/daniele.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/malcommac/SwiftScanner/HEAD/SwiftScanner/SwiftScanner.xcodeproj/project.xcworkspace/xcuserdata/daniele.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /SwiftScanner/SwiftScanner.xcodeproj/project.xcworkspace/xcuserdata/danielemm.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/malcommac/SwiftScanner/HEAD/SwiftScanner/SwiftScanner.xcodeproj/project.xcworkspace/xcuserdata/danielemm.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /SwiftScanner/SwiftScanner.xcodeproj/project.xcworkspace/xcuserdata/danielemm.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /SwiftScanner/.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /SwiftScanner/SwiftScanner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /SwiftScanner/SwiftScanner/SwiftScanner.h: -------------------------------------------------------------------------------- 1 | // 2 | // SwiftScanner.h 3 | // SwiftScanner 4 | // 5 | // Created by Daniele Margutti on 02/12/2016. 6 | // Copyright © 2016 Daniele Margutti. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | //! Project version number for SwiftScanner. 12 | FOUNDATION_EXPORT double SwiftScannerVersionNumber; 13 | 14 | //! Project version string for SwiftScanner. 15 | FOUNDATION_EXPORT const unsigned char SwiftScannerVersionString[]; 16 | 17 | // In this header, you should import all the public headers of your framework using statements like #import 18 | 19 | 20 | -------------------------------------------------------------------------------- /SwiftScanner/DemoApp/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // DemoApp 4 | // 5 | // Created by Daniele Margutti on 02/12/2016. 6 | // Copyright © 2016 Daniele Margutti. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import SwiftScanner 11 | 12 | class ViewController: UIViewController { 13 | 14 | override func viewDidLoad() { 15 | super.viewDidLoad() 16 | } 17 | 18 | override func viewDidAppear(_ animated: Bool) { 19 | super.viewDidAppear(animated) 20 | } 21 | 22 | override func didReceiveMemoryWarning() { 23 | super.didReceiveMemoryWarning() 24 | // Dispose of any resources that can be recreated. 25 | } 26 | } 27 | 28 | -------------------------------------------------------------------------------- /SwiftScanner/SwiftScanner.xcodeproj/xcuserdata/dan.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | DemoApp.xcscheme 8 | 9 | orderHint 10 | 1 11 | 12 | SwiftScanner.xcscheme_^#shared#^_ 13 | 14 | orderHint 15 | 0 16 | 17 | SwiftScannerTests.xcscheme 18 | 19 | orderHint 20 | 2 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /SwiftScanner/SwiftScanner.xcodeproj/xcuserdata/daniele.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | DemoApp.xcscheme 8 | 9 | orderHint 10 | 1 11 | 12 | SwiftScanner.xcscheme_^#shared#^_ 13 | 14 | orderHint 15 | 0 16 | 17 | SwiftScannerTests.xcscheme 18 | 19 | orderHint 20 | 2 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /SwiftScanner/SwiftScanner.xcodeproj/xcuserdata/imuz.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | DemoApp.xcscheme 8 | 9 | orderHint 10 | 1 11 | 12 | SwiftScanner.xcscheme_^#shared#^_ 13 | 14 | orderHint 15 | 0 16 | 17 | SwiftScannerTests.xcscheme 18 | 19 | orderHint 20 | 2 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /SwiftScanner/SwiftScanner.xcodeproj/xcshareddata/xcbaselines/084635E01DF6D60000BE9EF1.xcbaseline/768D88BA-F54A-405E-8656-FEE29B4B4CC9.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | classNames 6 | 7 | SwiftScannerTests 8 | 9 | testPerformanceonExtractTags_NSScanner() 10 | 11 | com.apple.XCTPerformanceMetric_WallClockTime 12 | 13 | baselineAverage 14 | 0.03135 15 | baselineIntegrationDisplayName 16 | Local Baseline 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /SwiftScanner/SwiftScannerTests/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 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /SwiftScanner/SwiftScanner/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.1.0 19 | CFBundleVersion 20 | 0 21 | NSPrincipalClass 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /SwiftScanner.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |spec| 2 | spec.name = 'SwiftScanner' 3 | spec.version = '1.1.0' 4 | spec.summary = 'Pure native Swift implementation of a string scanner; with no dependecies and full unicode support.' 5 | spec.homepage = 'https://github.com/malcommac/SwiftScanner' 6 | spec.license = { :type => 'MIT', :file => 'LICENSE' } 7 | spec.author = { 'Daniele Margutti' => 'me@danielemargutti.com' } 8 | spec.social_media_url = 'http://twitter.com/danielemargutti' 9 | spec.source = { :git => 'https://github.com/malcommac/SwiftScanner.git', :tag => "#{spec.version}" } 10 | spec.source_files = 'Sources/**/*.swift' 11 | spec.ios.deployment_target = '8.0' 12 | spec.watchos.deployment_target = '2.0' 13 | spec.osx.deployment_target = '10.10' 14 | spec.tvos.deployment_target = '9.0' 15 | spec.requires_arc = true 16 | spec.module_name = 'SwiftScanner' 17 | end 18 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:4.0 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | import PackageDescription 4 | 5 | let package = Package( 6 | name: "SwiftScanner", 7 | products: [ 8 | // Products define the executables and libraries produced by a package, and make them visible to other packages. 9 | .library( 10 | name: "SwiftScanner", 11 | targets: ["SwiftScanner"]) 12 | ], 13 | dependencies: [ 14 | // Dependencies declare other packages that this package depends on. 15 | // .package(url: /* package url */, from: "1.0.0"), 16 | ], 17 | targets: [ 18 | // Targets are the basic building blocks of a package. A target can define a module or a test suite. 19 | // Targets can depend on other targets in this package, and on products in packages which this package depends on. 20 | .target( 21 | name: "SwiftScanner", 22 | dependencies: []), 23 | ] 24 | ) 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 daniele margutti 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. -------------------------------------------------------------------------------- /SwiftScanner/SwiftScanner.xcodeproj/xcuserdata/danielemm.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | DemoApp.xcscheme 8 | 9 | orderHint 10 | 1 11 | 12 | SwiftScanner.xcscheme_^#shared#^_ 13 | 14 | orderHint 15 | 0 16 | 17 | SwiftScannerTests.xcscheme 18 | 19 | orderHint 20 | 2 21 | 22 | 23 | SuppressBuildableAutocreation 24 | 25 | 084635E01DF6D60000BE9EF1 26 | 27 | primary 28 | 29 | 30 | 08906C491DF1A90400FC4209 31 | 32 | primary 33 | 34 | 35 | 08906C5C1DF1BBDD00FC4209 36 | 37 | primary 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /SwiftScanner/SwiftScanner.xcodeproj/xcshareddata/xcbaselines/084635E01DF6D60000BE9EF1.xcbaseline/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | runDestinationsByUUID 6 | 7 | 768D88BA-F54A-405E-8656-FEE29B4B4CC9 8 | 9 | localComputer 10 | 11 | busSpeedInMHz 12 | 100 13 | cpuCount 14 | 1 15 | cpuKind 16 | Intel Core i7 17 | cpuSpeedInMHz 18 | 2200 19 | logicalCPUCoresPerPackage 20 | 8 21 | modelCode 22 | MacBookPro11,4 23 | physicalCPUCoresPerPackage 24 | 4 25 | platformIdentifier 26 | com.apple.platform.macosx 27 | 28 | targetArchitecture 29 | x86_64 30 | targetDevice 31 | 32 | modelCode 33 | iPhone9,1 34 | platformIdentifier 35 | com.apple.platform.iphonesimulator 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /SwiftScanner/DemoApp/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "29x29", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "40x40", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "40x40", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "60x60", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "60x60", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "ipad", 35 | "size" : "29x29", 36 | "scale" : "1x" 37 | }, 38 | { 39 | "idiom" : "ipad", 40 | "size" : "29x29", 41 | "scale" : "2x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "40x40", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "40x40", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "76x76", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "76x76", 61 | "scale" : "2x" 62 | } 63 | ], 64 | "info" : { 65 | "version" : 1, 66 | "author" : "xcode" 67 | } 68 | } -------------------------------------------------------------------------------- /SwiftScanner/SwiftScanner.xcodeproj/project.xcworkspace/xcuserdata/danielemm.xcuserdatad/xcdebugger/Expressions.xcexplist: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 7 | 8 | 10 | 11 | 12 | 13 | 15 | 16 | 18 | 19 | 20 | 21 | 23 | 24 | 26 | 27 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /SwiftScanner/DemoApp/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 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UILaunchStoryboardName 24 | LaunchScreen 25 | UIMainStoryboardFile 26 | Main 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationPortraitUpsideDown 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | .DS_Store 3 | 4 | # Xcode 5 | # 6 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 7 | 8 | ## Build generated 9 | build/ 10 | DerivedData 11 | 12 | ## Various settings 13 | *.pbxuser 14 | !default.pbxuser 15 | *.mode1v3 16 | !default.mode1v3 17 | *.mode2v3 18 | !default.mode2v3 19 | *.perspectivev3 20 | !default.perspectivev3 21 | xcuserdata 22 | 23 | ## Other 24 | *.xccheckout 25 | *.moved-aside 26 | *.xcuserstate 27 | *.xcscmblueprint 28 | 29 | ## Other 30 | *.moved-aside 31 | *.xccheckout 32 | *.xcscmblueprint 33 | 34 | ## Obj-C/Swift specific 35 | *.hmap 36 | *.ipa 37 | 38 | ## Playgrounds 39 | timeline.xctimeline 40 | playground.xcworkspace 41 | 42 | # Swift Package Manager 43 | # 44 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 45 | # Packages/ 46 | .build/ 47 | 48 | # Bundler 49 | .bundle 50 | 51 | # CocoaPods 52 | # 53 | # We recommend against adding the Pods directory to your .gitignore. However 54 | # you should judge for yourself, the pros and cons are mentioned at: 55 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 56 | # 57 | Pods/ 58 | 59 | # Carthage 60 | # 61 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 62 | # Carthage/Checkouts 63 | # Carthage/Build 64 | Carthage 65 | 66 | # fastlane 67 | # 68 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 69 | # screenshots whenever they are needed. 70 | # For more information about the recommended setup visit: 71 | # https://github.com/fastlane/fastlane/blob/master/docs/Gitignore.md 72 | 73 | fastlane/report.xml 74 | fastlane/screenshots 75 | 76 | .DS_ 77 | -------------------------------------------------------------------------------- /SwiftScanner/DemoApp/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /SwiftScanner/DemoApp/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /SwiftScanner/DemoApp/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // DemoApp 4 | // 5 | // Created by Daniele Margutti on 02/12/2016. 6 | // Copyright © 2016 Daniele Margutti. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | 17 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { 18 | // Override point for customization after application launch. 19 | return true 20 | } 21 | 22 | func applicationWillResignActive(_ application: UIApplication) { 23 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 24 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. 25 | } 26 | 27 | func applicationDidEnterBackground(_ application: UIApplication) { 28 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 29 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 30 | } 31 | 32 | func applicationWillEnterForeground(_ application: UIApplication) { 33 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. 34 | } 35 | 36 | func applicationDidBecomeActive(_ application: UIApplication) { 37 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 38 | } 39 | 40 | func applicationWillTerminate(_ application: UIApplication) { 41 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 42 | } 43 | 44 | 45 | } 46 | 47 | -------------------------------------------------------------------------------- /SwiftScanner/SwiftScanner.xcodeproj/xcuserdata/danielemm.xcuserdatad/xcschemes/SwiftScannerTests.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 16 | 18 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 40 | 41 | 42 | 43 | 49 | 50 | 52 | 53 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /SwiftScanner/SwiftScanner.xcodeproj/xcshareddata/xcschemes/SwiftScanner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 34 | 35 | 45 | 46 | 52 | 53 | 54 | 55 | 56 | 57 | 63 | 64 | 70 | 71 | 72 | 73 | 75 | 76 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /SwiftScanner/SwiftScanner.xcodeproj/xcuserdata/danielemm.xcuserdatad/xcschemes/DemoApp.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /Tests/SwiftScannerTests/NSScanner+Extenions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NSScanner+Extenions.swift 3 | // SwiftScanner 4 | // 5 | // Created by Daniele Margutti on 06/12/2016. 6 | // Copyright © 2016 Daniele Margutti. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public extension Scanner { 12 | 13 | // MARK: Strings 14 | 15 | /// Returns a string, scanned as long as characters from a given character set are encountered, or `nil` if none are found. 16 | func scanCharacters(from set: CharacterSet) -> String? { 17 | var value: NSString? = "" 18 | if scanCharacters(from: set, into: &value), 19 | let value = value as? String { 20 | return value 21 | } 22 | return nil 23 | } 24 | 25 | /// Returns a string, scanned until a character from a given character set are encountered, or the remainder of the scanner's string. Returns `nil` if the scanner is already `atEnd`. 26 | func scanUpToCharacters(from set: CharacterSet) -> String? { 27 | var value: NSString? = "" 28 | if scanUpToCharacters(from: set, into: &value), 29 | let value = value as? String { 30 | return value 31 | } 32 | return nil 33 | } 34 | 35 | /// Returns the given string if scanned, or `nil` if not found. 36 | @discardableResult func scanString(_ str: String) -> String? { 37 | var value: NSString? = "" 38 | if scanString(str, into: &value), 39 | let value = value as? String { 40 | return value 41 | } 42 | return nil 43 | } 44 | 45 | /// Returns a string, scanned until the given string is found, or the remainder of the scanner's string. Returns `nil` if the scanner is already `atEnd`. 46 | func scanUpTo(_ str: String) -> String? { 47 | var value: NSString? = "" 48 | if scanUpTo(str, into: &value), 49 | let value = value as? String { 50 | return value 51 | } 52 | return nil 53 | } 54 | 55 | // MARK: Numbers 56 | 57 | /// Returns a Double if scanned, or `nil` if not found. 58 | func scanDouble() -> Double? { 59 | var value = 0.0 60 | if scanDouble(&value) { 61 | return value 62 | } 63 | return nil 64 | } 65 | 66 | /// Returns a Float if scanned, or `nil` if not found. 67 | func scanFloat() -> Float? { 68 | var value: Float = 0.0 69 | if scanFloat(&value) { 70 | return value 71 | } 72 | return nil 73 | } 74 | 75 | /// Returns an Int if scanned, or `nil` if not found. 76 | func scanInteger() -> Int? { 77 | var value = 0 78 | if scanInt(&value) { 79 | return value 80 | } 81 | return nil 82 | } 83 | 84 | /// Returns an Int32 if scanned, or `nil` if not found. 85 | func scanInt() -> Int32? { 86 | var value: Int32 = 0 87 | if scanInt32(&value) { 88 | return value 89 | } 90 | return nil 91 | } 92 | 93 | /// Returns an Int64 if scanned, or `nil` if not found. 94 | func scanLongLong() -> Int64? { 95 | var value: Int64 = 0 96 | if scanInt64(&value) { 97 | return value 98 | } 99 | return nil 100 | } 101 | 102 | /// Returns a UInt64 if scanned, or `nil` if not found. 103 | func scanUnsignedLongLong() -> UInt64? { 104 | var value: UInt64 = 0 105 | if scanUnsignedLongLong(&value) { 106 | return value 107 | } 108 | return nil 109 | } 110 | 111 | /// Returns an NSDecimal if scanned, or `nil` if not found. 112 | func scanDecimal() -> Decimal? { 113 | var value = Decimal() 114 | if scanDecimal(&value) { 115 | return value 116 | } 117 | return nil 118 | } 119 | 120 | // MARK: Hex Numbers 121 | 122 | /// Returns a Double if scanned in hexadecimal, or `nil` if not found. 123 | func scanHexDouble() -> Double? { 124 | var value = 0.0 125 | if scanHexDouble(&value) { 126 | return value 127 | } 128 | return nil 129 | } 130 | 131 | /// Returns a Float if scanned in hexadecimal, or `nil` if not found. 132 | func scanHexFloat() -> Float? { 133 | var value: Float = 0.0 134 | if scanHexFloat(&value) { 135 | return value 136 | } 137 | return nil 138 | } 139 | 140 | /// Returns a UInt32 if scanned in hexadecimal, or `nil` if not found. 141 | func scanHexInt() -> UInt32? { 142 | var value: UInt32 = 0 143 | if scanHexInt32(&value) { 144 | return value 145 | } 146 | return nil 147 | } 148 | 149 | /// Returns a UInt64 if scanned in hexadecimal, or `nil` if not found. 150 | func scanHexLongLong() -> UInt64? { 151 | var value: UInt64 = 0 152 | if scanHexInt64(&value) { 153 | return value 154 | } 155 | return nil 156 | } 157 | 158 | } 159 | -------------------------------------------------------------------------------- /SwiftScanner/.idea/workspace.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 12 | 13 | 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 | true 44 | DEFINITION_ORDER 45 | 46 | 47 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 74 | 75 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 118 | 119 | 120 | 121 | 1480964265592 122 | 129 | 130 | 131 | 132 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 162 | 163 | 165 | 166 | 167 | 168 | 169 | file://$PROJECT_DIR$/DemoApp/ViewController.swift 170 | 23 171 | 172 | 173 | file://$PROJECT_DIR$/DemoApp/ViewController.swift 174 | 28 175 | 177 | 178 | file://$PROJECT_DIR$/DemoApp/ViewController.swift 179 | 32 180 | 182 | 183 | file://$PROJECT_DIR$/DemoApp/ViewController.swift 184 | 34 185 | 187 | 188 | 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 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | SwiftScanner 3 |

4 | 5 | [![Build Status](https://travis-ci.org/oarrabi/SwiftScanner.svg?branch=master)](https://travis-ci.org/oarrabi/SwiftScanner) 6 | [![codecov](https://codecov.io/gh/oarrabi/SwiftScanner/branch/master/graph/badge.svg)](https://codecov.io/gh/oarrabi/SwiftScanner) 7 | [![Platform](https://img.shields.io/badge/platform-osx-lightgrey.svg)](https://travis-ci.org/oarrabi/SwiftScanner) 8 | [![Platform](https://img.shields.io/badge/platform-ios-lightgrey.svg)](https://travis-ci.org/oarrabi/SwiftScanner) 9 | [![Language: Swift](https://img.shields.io/badge/language-swift-orange.svg)](https://travis-ci.org/oarrabi/SwiftScanner) 10 | [![CocoaPods](https://img.shields.io/cocoapods/v/SwiftScanner.svg)](https://cocoapods.org/pods/SwiftScanner) 11 | [![Carthage](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage) 12 | 13 | 14 | # SwiftScanner 15 | `SwiftScanner` is a pure native Swift implementation of a string scanner; with no dependecies, full unicode support (who does not love emoji?), lots of useful featurs and swift in mind, StringScanner is a good alternative to built-in Apple's `NSScanner`. 16 | 17 |

★★ Star our github repository to help us! ★★

18 | 19 | ## Related Projects 20 | I'm also working on several other projects you may like. 21 | Take a look below: 22 | 23 |

24 | 25 | | Library | Description | 26 | |-----------------|--------------------------------------------------| 27 | | [**SwiftDate**](https://github.com/malcommac/SwiftDate) | The best way to manage date/timezones in Swift | 28 | | [**Hydra**](https://github.com/malcommac/Hydra) | Write better async code: async/await & promises | 29 | | [**FlowKit**](https://github.com/malcommac/FlowKit) | A new declarative approach to table managment. Forget datasource & delegates. | 30 | | [**SwiftRichString**](https://github.com/malcommac/SwiftRichString) | Elegant & Painless NSAttributedString in Swift | 31 | | [**SwiftLocation**](https://github.com/malcommac/SwiftLocation) | Efficient location manager | 32 | | [**SwiftMsgPack**](https://github.com/malcommac/SwiftMsgPack) | Fast/efficient msgPack encoder/decoder | 33 |

34 | 35 | ## Main Features 36 | SwiftScanner is initialized with a string and mantain an internal index used to navigate backward and forward through the string using two main concepts: 37 | 38 | * `scan` to return string which also increment the internal index 39 | * `peek` to return a string without incrementing the internal index 40 | 41 | Results of these operations returns collected String or Indexes. 42 | If operation fail due to an error (ie. `eof`, `notFound`, `invalidInt`...) and exception is thrown, in pure Swift style. 43 | 44 | API Documentation 45 | ------- 46 | 47 | * **[scanChar()](#scanChar)** 48 | * **[scanInt()](#scanInt)** 49 | * **[scanFloat()](#scanFloat)** 50 | * **[scanHexInt()](#scanHexInt)** 51 | * **[scan(upTo: UnicodeScalar)](#scanUpToChar)** 52 | * **[scan(upTo: CharacterSet)](#scanUpToCharset)** 53 | * **[scan(untilIn: CharacterSet)](#scanUntilInCharset)** 54 | * **[scan(upTo: String)](#scanUpToString)** 55 | * **[scan(until: Test)](#scanUntilTrue)** 56 | * **[scan(length: Int)](#scanLenght)** 57 | * **[peek(upTo: UnicodeScalar)](#peekUpToChar)** 58 | * **[peek(upTo: CharacterSet)](#peekUpToCharset)** 59 | * **[peek(untilIn: CharacterSet)](#peekUpUntilInCharset)** 60 | * **[peek(upTo: String)](#peekUpToString)** 61 | * **[peek(until: Test)](#peekUpUntil)** 62 | * **[match(UnicodeScalar)](#matchChar)** 63 | * **[match(String)](#matchString)** 64 | * **[reset()](#reset)** 65 | * **[peekAtEnd()](#peekAtEnd)** 66 | * **[skip(length: Int)](#skipLength)** 67 | * **[back(length: Int)](#backLength)** 68 | 69 | Other 70 | ------- 71 | * **[Installation](#installation)** 72 | * **[Tests](#tests)** 73 | * **[Requirements](#requirements)** 74 | * **[Credits](#credits)** 75 | 76 | ### `scan` functions 77 | 78 | 79 | #### `func scanChar() throws -> UnicodeScalar` 80 | `scanChar` allows you to scan the next character after the current's scanner `position` and return it as `UnicodeScalar`. 81 | If operation succeded internal scanner's `position` is advanced by 1 character (as unicode). 82 | If operation fails an exception is thrown. 83 | 84 | Example: 85 | ```swift 86 | let scanner = StringScanner("Hello this is SwiftScanner") 87 | let firstChar = try! scanner.scanChar() // get 'H' 88 | ``` 89 | 90 | 91 | #### `func scanInt() throws -> Int` 92 | Scan the next integer value after the current scanner's `position`; consume scalars from {0...9} until a non numeric value is encountered. Return the integer representation in base 10. 93 | Throw `.invalidInt` if scalar at current position is not in allowed range (may also return `.eof`). 94 | If operation succeded internal scanner's `position` is advanced by the number of character which represent an integer. 95 | If operation fails an exception is thrown. 96 | 97 | Example: 98 | ```swift 99 | let scanner = StringScanner("15 apples") 100 | let parsedInt = try! scanner.scanInt() // get Int=15 101 | ``` 102 | 103 | 104 | #### `func scanFloat() throws -> Float` 105 | Scan for a float value (in format ##.##) and convert it to a valid Floast. 106 | If scan succeded scanner's `position` is updated at the end of the represented string, otherwise an exception (`.invalidFloat`, `.eof`) is thrown and index is not touched. 107 | 108 | Example: 109 | ```swift 110 | let scanner = StringScanner("45.54 $") 111 | let parsedFloat = try! scanner.scanFloat() // get Int=45.54 112 | ``` 113 | 114 | 115 | #### `func scanHexInt(digits: BitDigits) throws -> Int` 116 | Scan an HEX digit expressed in these formats: 117 | 118 | * `0x[VALUE]` (example: `0x0000000000564534`) 119 | * `0X[VALUE]` (example: `0x0929`) 120 | * `#[VALUE]` (example: `#1602`) 121 | 122 | If scan succeded scanner's `position` is updated at the end of the represented string, otherwise an exception ((`.notFound`, )`.invalidHex`, `.eof`) is thrown and index is not touched. 123 | 124 | Example: 125 | ```swift 126 | let scanner = StringScanner("#1602") 127 | let value = try! scanner.scanHexInt(.bit16) // get Int=5634 128 | 129 | let scanner = StringScanner("#0x0929") 130 | let value = try! scanner.scanHexInt(.bit16) // get Int=2345 131 | 132 | let scanner = StringScanner("#0x0000000000564534") 133 | let value = try! scanner.scanHexInt(.bit64) // get Int=5653812 134 | ``` 135 | 136 | #### `public func scan(upTo char: UnicodeScalar) throws -> String?` 137 | Scan until given character is found starting from current scanner `position` till the end of the source string. 138 | Scanner's `position` is updated only if character is found and set just before it. 139 | Throw an exception if `.eof` is reached or `.notFound` if char was not found (in this case scanner's position is not updated) 140 | 141 | Example: 142 | ```swift 143 | let scanner = StringScanner("Hello Daniele") 144 | let partialString = try! scanner.scan(upTo: "") // get "Hello " 145 | ``` 146 | 147 | 148 | #### `func scan(upTo charSet: CharacterSet) throws -> String?` 149 | Scan until given character's is found. 150 | Index is reported before the start of the sequence, scanner's `position` is updated only if sequence is found. 151 | Throw an exception if `.eof` is reached or `.notFound` if sequence was not found. 152 | 153 | Example: 154 | ```swift 155 | let scanner = StringScanner("Hello, I've at least 15 apples") 156 | let partialString = try! scanner.scan(upTo: CharacterSet.decimalDigits) // get "Hello, I've at least " 157 | ``` 158 | 159 | 160 | #### `func scan(untilIn charSet: CharacterSet) throws -> String?` 161 | Scan, starting from scanner's `position` until the next character of the scanner is contained into given character set. 162 | Scanner's `position` is updated automatically at the end of the sequence if validated, otherwise it will not touched. 163 | 164 | Example: 165 | ```swift 166 | let scanner = StringScanner("HELLO i'm mark") 167 | let partialString = try! scanner.scan(untilIn: CharacterSet.lowercaseLetters) // get "HELLO" 168 | ``` 169 | 170 | 171 | #### `func scan(upTo string: String) throws -> String?` 172 | Scan, starting from scanner's `position` until specified string is encountered. 173 | Scanner's `position` is updated automatically at the end of the sequence if validated, otherwise it will not touched. 174 | 175 | Example: 176 | ```swift 177 | let scanner = StringScanner("This is a simple test I've made") 178 | let partialString = try! scanner.scan(upTo: "I've") // get "This is a simple test " 179 | ``` 180 | 181 | 182 | #### `func scan(untilTrue test: ((UnicodeScalar) -> (Bool))) -> String` 183 | Scan and consume at the scalar starting from current `position`, testing it with function test. 184 | If test returns `true`, the `position` increased. 185 | If `false`, the function returns. 186 | 187 | Example: 188 | ```swift 189 | let scanner = StringScanner("Never be satisfied 💪 and always push yourself! 😎 Do the things people say cannot be done") 190 | let delimiters = CharacterSet(charactersIn: "💪😎") 191 | while !scanner.isAtEnd { 192 | let block = scanner.scan(untilTrue: { char in 193 | return (delimiters.contains(char) == false) 194 | }) 195 | // Print: 196 | // "Never be satisfied " (first iteration) 197 | // "and always push yourself!" (second iteration) 198 | // "Do the things people say cannot be done" (third iteration) 199 | print("Block: \(block)") 200 | try scanner.skip() // push over the character 201 | } 202 | ``` 203 | 204 | 205 | #### `func scan(length: Int=1) -> String` 206 | Read next length characters and accumulate it 207 | If operation is succeded scanner's `position` are updated according to consumed scalars. 208 | If fails an exception is thrown and `position` is not updated. 209 | 210 | Example: 211 | ```swift 212 | let scanner = StringScanner("Never be satisfied") 213 | let partialString = scanner.scan(5) // "Never" 214 | ``` 215 | ### `peek` functions 216 | 217 | Peek functions are the same as concept of `scan()` but unless it it does not update internal scanner's `position` index. 218 | These functions usually return only `starting index` of matched pattern. 219 | 220 | 221 | #### `func peek(upTo char: UnicodeScalar) -> String.UnicodeScalarView.Index` 222 | Peek until chracter is found starting from current scanner's `position`. 223 | Scanner's `position` is never updated. 224 | Throw an exception if `.eof` is reached or `.notFound` if char was not found. 225 | 226 | Example: 227 | ```swift 228 | let scanner = StringScanner("Never be satisfied") 229 | let index = try! scanner.peek(upTo: "b") // return 6 230 | ``` 231 | 232 | 233 | #### `func peek(upTo charSet: CharacterSet) -> String.UnicodeScalarView.Index` 234 | Peek until one the characters specified by set is encountered 235 | Index is reported before the start of the sequence, but scanner's `position` is never updated. 236 | Throw an exception if .eof is reached or .notFound if sequence was not found 237 | 238 | Example: 239 | ```swift 240 | let scanner = StringScanner("You are in queue: 123 is your position") 241 | let index = try! scanner.peek(upTo: CharacterSet.decimalDigits) // return 18 242 | ``` 243 | 244 | 245 | #### `func peek(untilIn charSet: CharacterSet) -> String.UnicodeScalarView.Index` 246 | Peek until the next character of the scanner is contained into given. 247 | Scanner's `position` is never updated. 248 | 249 | Example: 250 | ```swift 251 | let scanner = StringScanner("654 apples") 252 | let index = try! scanner.peek(untilIn: CharacterSet.decimalDigits) // return 3 253 | ``` 254 | 255 | 256 | #### `func peek(upTo string: String) -> String.UnicodeScalarView.Index` 257 | Iterate until specified string is encountered without updating indexes. 258 | Scanner's `position` is never updated but it's reported the index just before found occourence. 259 | 260 | Example: 261 | ```swift 262 | let scanner = StringScanner("654 apples in the bug") 263 | let index = try! scanner.peek(upTo: "in") // return 11 264 | ``` 265 | 266 | 267 | #### `func peek(untilTrue test: ((UnicodeScalar) -> (Bool))) -> String.UnicodeScalarView.Index` 268 | Peeks at the scalar at the current position, testing it with function test. 269 | It only peeks so current scanner's `position` is not increased at the end of the operation 270 | 271 | Example: 272 | ```swift 273 | let scanner = StringScanner("I'm very 💪 and 😎 Go!") 274 | let delimiters = CharacterSet(charactersIn: "💪😎") 275 | while !scanner.isAtEnd { 276 | let prevIndex = scanner.position 277 | let finalIndex = scanner.peek(untilTrue: { char in 278 | return (delimiters.contains(char) == false) 279 | }) 280 | // Distance will return: 281 | // - 9 (first iteration) 282 | // - 5 (second iteration) 283 | // - 4 (third iteration) 284 | let distance = scanner.string.distance(from: prevIndex, to: finalIndex) 285 | try scanner.skip(length: distance + 1) 286 | } 287 | ``` 288 | ### Other Functions 289 | 290 | 291 | #### `func match(_ char: UnicodeScalar) -> Bool` 292 | Return false if the scalar at the current position don't match given scalar. 293 | Advance scanner's `position` to the end of the match if match. 294 | 295 | ```swift 296 | let scanner = StringScanner("💪 and 😎") 297 | let match = scanner.match("😎") // return false 298 | ``` 299 | 300 | 301 | #### `func match(_ match: String) -> Bool` 302 | Return false if scalars starting at the current position don't match scalars in given string. 303 | Advance scanner's `position` to the end of the match string if match. 304 | 305 | ```swift 306 | let scanner = StringScanner("I'm very 💪 and 😎 Go!") 307 | scanner.match("I'm very") // return true 308 | ``` 309 | 310 | 311 | #### `func reset()` 312 | Move scanner's internal `position` to the start of the string. 313 | 314 | 315 | #### `func peekAtEnd()` 316 | Move to the index's end index. 317 | 318 | 319 | #### `func skip(length: Int = 1) throws` 320 | Attempt to advance scanner's by length 321 | If operation is not possible (reached the end of the string) it throws and current scanner's `position` of the index did not change 322 | If operation succeded scanner's `position` is updated. 323 | 324 | 325 | #### `func back(length: Int = 1) throws` 326 | Attempt to advance the position back by length 327 | If operation fails scanner's `position` is not touched 328 | If operation succeded scaner's `position` is modified according to new value 329 | 330 | 331 | ## Installation 332 | You can install Swiftline using CocoaPods, carthage and Swift package manager 333 | 334 | ### CocoaPods 335 | use_frameworks! 336 | pod 'SwiftScanner' 337 | 338 | ### Carthage 339 | github 'malcommac/SwiftScanner' 340 | 341 | ### Swift Package Manager 342 | Add swiftline as dependency in your `Package.swift` 343 | 344 | ``` 345 | import PackageDescription 346 | 347 | let package = Package(name: "YourPackage", 348 | dependencies: [ 349 | .Package(url: "https://github.com/malcommac/SwiftScanner.git", majorVersion: 0), 350 | ] 351 | ) 352 | ``` 353 | 354 | 355 | ## Tests 356 | Tests can be found [here](https://github.com/malcommac/SwiftScanner/tree/master/Tests). 357 | 358 | Run them with 359 | ``` 360 | swift test 361 | ``` 362 | 363 | 364 | ## Requirements 365 | 366 | Current version is compatible with: 367 | 368 | * **Swift 4.x** >= 1.0.4 369 | * **Swift 3.x**: up to 1.0.3 370 | 371 | * iOS 8 or later 372 | * macOS 10.10 or later 373 | * watchOS 2.0 or later 374 | * tvOS 9.0 or later 375 | * ...and virtually any platform which is compatible with Swift 3 and implements the Swift Foundation Library 376 | 377 | 378 | 379 | ## Credits & License 380 | SwiftScanner is owned and maintained by [Daniele Margutti](http://www.danielemargutti.com/). 381 | 382 | As open source creation any help is welcome! 383 | 384 | The code of this library is licensed under MIT License; you can use it in commercial products without any limitation. 385 | 386 | The only requirement is to add a line in your Credits/About section with the text below: 387 | 388 | ``` 389 | Portions SwiftScanner - http://github.com/malcommac/SwiftScanner 390 | Created by Daniele Margutti and licensed under MIT License. 391 | ``` 392 | -------------------------------------------------------------------------------- /Tests/SwiftScannerTests/TestSwiftScanner.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SwiftScannerTests.swift 3 | // SwiftScannerTests 4 | // 5 | // Created by Daniele Margutti on 06/12/2016. 6 | // Copyright © 2016 Daniele Margutti. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | import SwiftScanner 11 | 12 | class SwiftScannerTests: XCTestCase { 13 | 14 | override func setUp() { 15 | super.setUp() 16 | // Put setup code here. This method is called before the invocation of each test method in the class. 17 | } 18 | 19 | override func tearDown() { 20 | // Put teardown code here. This method is called after the invocation of each test method in the class. 21 | super.tearDown() 22 | } 23 | 24 | func testExample() { 25 | // This is an example of a functional test case. 26 | // Use XCTAssert and related functions to verify your tests produce the correct results. 27 | } 28 | 29 | func testScanChar() { 30 | let test = "next char test ⛵️" 31 | let testScalars = test.unicodeScalars 32 | var idx = testScalars.startIndex 33 | 34 | let scanner = StringScanner(test) 35 | do { 36 | while !scanner.isAtEnd { 37 | let currentScalar = try scanner.scanChar() 38 | XCTAssert( (currentScalar == testScalars[idx]) , "Failed to validate scanChar()") 39 | idx = testScalars.index(after: idx) 40 | } 41 | } catch let err { 42 | XCTFail("scanChar() does not work properly: \(err)") 43 | } 44 | } 45 | 46 | func testInt() { 47 | let test = "12,24,36,45,1" 48 | let valid = [12,24,36,45,1] 49 | let scanner = StringScanner(test) 50 | var idx = 0 51 | 52 | do { 53 | while !scanner.isAtEnd { 54 | if scanner.consumed > 0 { try scanner.skip() } 55 | let currentInt = try scanner.scanInt() 56 | XCTAssert( (currentInt == valid[idx]) , "Failed to validate scanInt()") 57 | idx += 1 58 | } 59 | } catch let err { 60 | XCTFail("scanInt() does not work properly: \(err)") 61 | } 62 | } 63 | 64 | func testFloat() { 65 | let test = "12.56,34.5,33.4,3.4" 66 | let valid: [Float] = [12.56,34.5,33.4,3.4] 67 | let scanner = StringScanner(test) 68 | var idx = 0 69 | 70 | do { 71 | while !scanner.isAtEnd { 72 | if scanner.consumed > 0 { try scanner.skip() } 73 | let currentFloat = try scanner.scanFloat() 74 | XCTAssert( (currentFloat == valid[idx]) , "Failed to validate testFloat()") 75 | idx += 1 76 | } 77 | } catch let err { 78 | XCTFail("testFloat() does not work properly: \(err)") 79 | } 80 | } 81 | 82 | func testHEX16BitString() { 83 | let test = "#1602,#04D1,0x0929" 84 | let intValues = [5634,1233,2345] 85 | validateHEXValues(name: "testHEX16BitString()", string: test, digits: .bit16, validValues: intValues) 86 | } 87 | 88 | func testHEX32BitString() { 89 | let test = "0XAF2C0155,#FF003344,0x0000F2C4" 90 | let intValues = [2938896725,4278203204,62148] 91 | validateHEXValues(name: "testHEX32BitString()", string: test, digits: .bit32, validValues: intValues) 92 | } 93 | 94 | func testHEX64BitString() { 95 | let test = "0x0000000000564534" 96 | let intValues = [5653812] 97 | validateHEXValues(name: "testHEX64BitString()", string: test, digits: .bit64, validValues: intValues) 98 | } 99 | 100 | func validateHEXValues(name: String, string: String, digits: BitDigits, validValues: [Int]) { 101 | var idx = 0 102 | let scanner = StringScanner(string) 103 | do { 104 | while !scanner.isAtEnd { 105 | if scanner.consumed > 0 { try scanner.skip() } 106 | let currentInt = try scanner.scanHexInt(digits) 107 | XCTAssert( (currentInt == validValues[idx]) , "Failed to validate scanHexInt()") 108 | idx += 1 109 | } 110 | } catch let err { 111 | XCTFail("scanHexInt() does not work properly: \(err)") 112 | } 113 | } 114 | 115 | func testScanUpToUnicodeScalar() { 116 | let test = "hello again. i'm daniele. welcome here!" 117 | let validValues:[String] = ["hello again"," i'm daniele"," welcome here!"] 118 | let scanner = StringScanner(test) 119 | var idx = 0 120 | 121 | do { 122 | while !scanner.isAtEnd { 123 | let blockValue = try scanner.scan(upTo: ".") 124 | XCTAssert( (blockValue == validValues[idx]) , "Failed to validate scan(upTo:)") 125 | idx += 1 126 | if (idx < validValues.count) { try scanner.skip() } 127 | } 128 | } catch let err { 129 | XCTFail("scan(upTo:) does not work properly: \(err)") 130 | } 131 | } 132 | 133 | func testScanUpToCharset() { 134 | let test = "this a token;that's another.third one!the last one" 135 | let validValues: [String] = ["this a token","that's another","third one","the last one"] 136 | let scanner = StringScanner(test) 137 | var idx = 0 138 | 139 | do { 140 | while !scanner.isAtEnd { 141 | let blockValue = try scanner.scan(upTo: CharacterSet(charactersIn: ";.!")) 142 | XCTAssert( (blockValue == validValues[idx]) , "Failed to validate scan(upTo:)") 143 | idx += 1 144 | if (idx < validValues.count) { try scanner.skip() } 145 | } 146 | } catch let err { 147 | XCTFail("scan(upTo:) does not work properly: \(err)") 148 | } 149 | } 150 | 151 | func testScanUpUntilCharset() { 152 | let test = "daniele;mario;john;steve" 153 | let validValues: [String] = ["daniele","mario","john","steve"] 154 | let scanner = StringScanner(test) 155 | var idx = 0 156 | 157 | do { 158 | while !scanner.isAtEnd { 159 | let blockValue = try scanner.scan(untilIn: CharacterSet.lowercaseLetters) 160 | XCTAssert( (blockValue == validValues[idx]) , "Failed to validate scan(until:)") 161 | idx += 1 162 | if (idx < validValues.count) { try scanner.skip() } 163 | } 164 | } catch let err { 165 | XCTFail("scan(until:) does not work properly: \(err)") 166 | } 167 | } 168 | 169 | func testScanUpToString() { 170 | let separator = ",\n" 171 | let test = "one\(separator)two\(separator)three\(separator)four\(separator)" 172 | let validValues: [String] = ["one","two","three","four"] 173 | let scanner = StringScanner(test) 174 | var idx = 0 175 | 176 | do { 177 | while !scanner.isAtEnd { 178 | let blockValue = try scanner.scan(upTo: separator) 179 | XCTAssert( (blockValue == validValues[idx]) , "Failed to validate scan(upTo:)") 180 | if (idx < validValues.count) { try scanner.skip(length: separator.characters.count) } 181 | idx += 1 182 | } 183 | } catch let err { 184 | XCTFail("scan(upTo:) does not work properly: \(err)") 185 | } 186 | 187 | } 188 | 189 | func testScanLength() { 190 | let separator = ",\n" 191 | let test = "123\(separator)456\(separator)678\(separator)987" 192 | let validValues: [String] = ["123","456","678","987"] 193 | 194 | let scanner = StringScanner(test) 195 | var idx = 0 196 | 197 | do { 198 | while !scanner.isAtEnd { 199 | let blockValue = try scanner.scan(length: 3) 200 | XCTAssert( (blockValue == validValues[idx]) , "Failed to validate scan(length:)") 201 | idx += 1 202 | if (idx < validValues.count) { 203 | try scanner.skip(length: separator.characters.count) 204 | } 205 | } 206 | } catch let err { 207 | XCTFail("scan(length:) does not work properly: \(err)") 208 | } 209 | 210 | } 211 | 212 | func testPeekUpToChar() { 213 | let test = "abc;defg;hilmn;" 214 | let validDistances = [3,4,5] 215 | let scanner = StringScanner(test) 216 | var idx = 0 217 | 218 | do { 219 | while !scanner.isAtEnd { 220 | let currentPosition = scanner.position 221 | let separatorPosition = try scanner.peek(upTo: ";") 222 | let distance = scanner.string.distance(from: currentPosition, to: separatorPosition) 223 | try scanner.skip(length: distance+1) 224 | XCTAssert( (distance == validDistances[idx]) , "Failed to validate peek(upTo:)") 225 | idx += 1 226 | } 227 | } catch let err { 228 | XCTFail("peek(upTo:) does not work properly: \(err)") 229 | } 230 | } 231 | 232 | func testPeekUpToCharset() { 233 | let test = "hello, again!I'm daniele.And you?" 234 | let validDistances = [5,6,11,8] 235 | let scanner = StringScanner(test) 236 | var idx = 0 237 | 238 | do { 239 | while !scanner.isAtEnd { 240 | let currentPosition = scanner.position 241 | let separatorPosition = try scanner.peek(upTo: CharacterSet(charactersIn: ",!.")) 242 | let distance = scanner.string.distance(from: currentPosition, to: separatorPosition) 243 | try scanner.skip(length: distance+1) 244 | XCTAssert( (distance == validDistances[idx]) , "Failed to validate peek(upTo:)") 245 | idx += 1 246 | } 247 | } catch let err { 248 | guard let error = err as? StringScannerError else { return } 249 | if case .eof = error { // handle last scanner.skip(length:) 250 | return 251 | } 252 | XCTFail("peek(upTo:) does not work properly: \(error)") 253 | } 254 | } 255 | 256 | func testPeekUpUntilCharset() { 257 | let test = "HELLOman!" 258 | let scanner = StringScanner(test) 259 | 260 | do { 261 | let startPosition = scanner.position 262 | let endPosition = try scanner.peek(untilIn: CharacterSet.uppercaseLetters) 263 | let distance = scanner.string.distance(from: startPosition, to: endPosition) 264 | XCTAssert( (distance == 5) , "Failed to validate peek(untilIn:)") 265 | } catch let err { 266 | XCTFail("peek(untilIn:) does not work properly: \(err)") 267 | } 268 | } 269 | 270 | func testPeekUpToString() { 271 | let test = "Never be satisfied 💪 and always push yourself!" 272 | let scanner = StringScanner(test) 273 | do { 274 | let startPosition = scanner.position 275 | let endPosition = try scanner.peek(upTo: "💪") 276 | let distance = scanner.string.distance(from: startPosition, to: endPosition) 277 | XCTAssert( (distance == 19) , "Failed to validate peek(upTo:String)") 278 | } catch let err { 279 | XCTFail("peek(upTo:String) does not work properly: \(err)") 280 | } 281 | } 282 | 283 | func testScanUntilTrueOnTest() { 284 | let test = "Never be satisfied 💪 and always push yourself! 😎 Do the things people say cannot be done" 285 | let validated = ["Never be satisfied "," and always push yourself! "," Do the things people say cannot be done"] 286 | let delimiters = CharacterSet(charactersIn: "💪😎") 287 | let scanner = StringScanner(test) 288 | var idx = 0 289 | 290 | do { 291 | while !scanner.isAtEnd { 292 | let block = scanner.scan(untilTrue: { char in 293 | return (delimiters.contains(char) == false) 294 | }) 295 | XCTAssert( (block == validated[idx]) , "Failed to validate scan(untilTrue:)") 296 | try scanner.skip() 297 | idx += 1 298 | } 299 | } catch let err { 300 | guard let error = err as? StringScannerError else { return } 301 | if case .eof = error { // handle last scanner.skip(length:) 302 | return 303 | } 304 | XCTFail("scan(untilTrue:) does not work properly: \(error)") 305 | } 306 | } 307 | 308 | func testPeekUntilTrueOnTest() { 309 | let test = "I'm very 💪 and 😎 Go!" 310 | let delimiters = CharacterSet(charactersIn: "💪😎") 311 | var validatedDistances = [9,5,4] 312 | let scanner = StringScanner(test) 313 | var idx = 0 314 | 315 | do { 316 | while !scanner.isAtEnd { 317 | let prevIndex = scanner.position 318 | let finalIndex = scanner.peek(untilTrue: { char in 319 | return (delimiters.contains(char) == false) 320 | }) 321 | let distance = scanner.string.distance(from: prevIndex, to: finalIndex) 322 | XCTAssert( (distance == validatedDistances[idx]) , "Failed to validate peek(untilTrue:)") 323 | try scanner.skip(length: distance + 1) 324 | idx += 1 325 | } 326 | } catch let err { 327 | guard let error = err as? StringScannerError else { return } 328 | if case .eof = error { // handle last scanner.skip(length:) 329 | return 330 | } 331 | XCTFail("scan(untilTrue:) does not work properly: \(error)") 332 | } 333 | } 334 | 335 | func testMatch() { 336 | let test_match = "hello man! push yourself!" 337 | let scanner = StringScanner(test_match) 338 | 339 | // test match 340 | let match = scanner.match("hello man!") 341 | XCTAssert( (match == true), "match() does not work properly") 342 | 343 | // test don't match 344 | scanner.reset() // reset from the start 345 | try! scanner.scan(upTo: "! ") // move to the next token 346 | try! scanner.skip(length: 2) 347 | let not_match = scanner.match("hello man") 348 | XCTAssert( (not_match == false), "match() does not work properly") 349 | } 350 | 351 | func testReset() { 352 | let test = "hello man! push yourself" 353 | 354 | func randomNumber(range: Range) -> Int { 355 | return Int(arc4random_uniform(UInt32(range.upperBound - range.lowerBound))) + range.lowerBound 356 | } 357 | 358 | let scanner = StringScanner(test) 359 | let length = test.characters.count 360 | 361 | var idx = 0 362 | do { 363 | while idx < 10 { 364 | let distance = randomNumber(range: 0..= validatedLines.count { 387 | break 388 | } 389 | let backLen = backLenghts[idx] 390 | try scanner.back(length: backLen) 391 | let scanner_char = try scanner.scanChar() 392 | let valid_char = validatedLines[idx] 393 | let isValid = (String(scanner_char) == valid_char) 394 | XCTAssert( isValid, "Failed to validate back()") 395 | try scanner.back() 396 | idx += 1 397 | } 398 | } catch { 399 | XCTFail("back() does not work properly") 400 | } 401 | } 402 | 403 | func testBack() { 404 | let test = "hello, i'm daniele and this is a great library!" 405 | let scanner = StringScanner(test) 406 | 407 | do { 408 | scanner.peekAtEnd() // null termination index 409 | try scanner.back() // back from null termination char 410 | var positionInChar = 1 // back from null termination char 411 | while true { 412 | let test_charIdx = test.unicodeScalars.index(test.unicodeScalars.endIndex, offsetBy: -positionInChar) 413 | let test_char = test.unicodeScalars[test_charIdx] 414 | let scanner_char = scanner.string[scanner.position] 415 | XCTAssert( (test_char == scanner_char) , "Failed to validate back()") 416 | 417 | if positionInChar < test.characters.count { 418 | try scanner.back() 419 | positionInChar += 1 420 | } else { 421 | break 422 | } 423 | } 424 | } catch { 425 | XCTFail("back() does not work properly") 426 | } 427 | } 428 | 429 | func testSkipSpaces() { 430 | let test = "1 2 3 4 5 \t 6 7" 431 | let scanner = StringScanner(test) 432 | var numbers = [Int]() 433 | do { 434 | while !scanner.isAtEnd { 435 | try scanner.skip(charactersIn: .whitespaces) 436 | numbers.append(try scanner.scanInt()) 437 | } 438 | } catch { 439 | XCTFail("skip(charactersIn:) does not work properly") 440 | } 441 | 442 | XCTAssertEqual(numbers, [1, 2, 3, 4, 5, 6, 7]) 443 | } 444 | 445 | /* 446 | func getSamplePerformanceData() -> String { 447 | // Give here a sample file to test tags performance 448 | let filePath = Bundle(for: SwiftScannerTests.self).path(forResource: "sample_file", ofType: "html")! 449 | let source = try! String(contentsOfFile: filePath, encoding: String.Encoding.isoLatin1) 450 | return source 451 | } 452 | 453 | func testPerformanceonExtractTags_NSScanner() { 454 | let scanner = Scanner(string: self.getSamplePerformanceData()) 455 | scanner.charactersToBeSkipped = nil 456 | var plain_text = "" 457 | var tagsList: [String] = [] 458 | 459 | self.measure { 460 | scanner.scanLocation = 0 461 | while !scanner.isAtEnd { 462 | if let textString = scanner.scanUpToCharacters(from: CharacterSet(charactersIn: "<")) { 463 | plain_text += textString 464 | } else { 465 | // We have encountered a special entity or an open/close tag 466 | if scanner.scanString("<") != nil { 467 | // It's an open/close tag character 468 | let tag = scanner.scanUpTo(">") // get the raw name of the tag 469 | tagsList.append(tag!) 470 | scanner.scanString(">") // go ahead 471 | } 472 | } 473 | } 474 | print("testPerformanceonExtractTags_NSScanner: \(tagsList.count) tags found") 475 | tagsList.removeAll() 476 | } 477 | } 478 | 479 | func testPerformanceOnExtractTags() { 480 | let scanner = StringScanner(self.getSamplePerformanceData()) 481 | var plain_text = "" 482 | var tagsList: [String] = [] 483 | 484 | self.measure { 485 | do { 486 | scanner.reset() 487 | while !scanner.isAtEnd { 488 | if let text = try scanner.scan(upTo: CharacterSet(charactersIn: "<")) { 489 | plain_text += text 490 | } else { 491 | try! scanner.scanChar() 492 | let endTag = try! scanner.scan(upTo: CharacterSet(charactersIn: ">")) 493 | guard let tag = endTag else { 494 | continue 495 | } 496 | tagsList.append(tag) 497 | try! scanner.scanChar() 498 | } 499 | } 500 | print("testPerformanceOnExtractTags: \(tagsList.count) tags found") 501 | tagsList.removeAll() 502 | } catch let err { 503 | print("Error: \(err)") 504 | } 505 | } 506 | } 507 | */ 508 | } 509 | 510 | -------------------------------------------------------------------------------- /SwiftScanner/SwiftScanner.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 084635E61DF6D60000BE9EF1 /* SwiftScanner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 08906C4A1DF1A90400FC4209 /* SwiftScanner.framework */; }; 11 | 084635ED1DF6D7AC00BE9EF1 /* SwiftScanner.framework in Copy Frameworks */ = {isa = PBXBuildFile; fileRef = 08906C4A1DF1A90400FC4209 /* SwiftScanner.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 12 | 08906C4F1DF1A90400FC4209 /* SwiftScanner.h in Headers */ = {isa = PBXBuildFile; fileRef = 08906C4D1DF1A90400FC4209 /* SwiftScanner.h */; settings = {ATTRIBUTES = (Public, ); }; }; 13 | 08906C601DF1BBDE00FC4209 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08906C5F1DF1BBDE00FC4209 /* AppDelegate.swift */; }; 14 | 08906C621DF1BBDE00FC4209 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08906C611DF1BBDE00FC4209 /* ViewController.swift */; }; 15 | 08906C651DF1BBDE00FC4209 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 08906C631DF1BBDE00FC4209 /* Main.storyboard */; }; 16 | 08906C671DF1BBDE00FC4209 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 08906C661DF1BBDE00FC4209 /* Assets.xcassets */; }; 17 | 08906C6A1DF1BBDE00FC4209 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 08906C681DF1BBDE00FC4209 /* LaunchScreen.storyboard */; }; 18 | 08906C711DF1BBEB00FC4209 /* SwiftScanner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 08906C4A1DF1A90400FC4209 /* SwiftScanner.framework */; }; 19 | 08906C731DF1BC4600FC4209 /* SwiftScanner.framework in Copy Frameworks */ = {isa = PBXBuildFile; fileRef = 08906C4A1DF1A90400FC4209 /* SwiftScanner.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 20 | 08906C771DF1C56C00FC4209 /* StringScanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08906C751DF1C56C00FC4209 /* StringScanner.swift */; }; 21 | 08C606ED1DF817E50014E07E /* NSScanner+Extenions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08C606EB1DF817E50014E07E /* NSScanner+Extenions.swift */; }; 22 | 08C606EE1DF817E50014E07E /* TestSwiftScanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08C606EC1DF817E50014E07E /* TestSwiftScanner.swift */; }; 23 | /* End PBXBuildFile section */ 24 | 25 | /* Begin PBXContainerItemProxy section */ 26 | 084635E71DF6D60000BE9EF1 /* PBXContainerItemProxy */ = { 27 | isa = PBXContainerItemProxy; 28 | containerPortal = 08906C411DF1A90400FC4209 /* Project object */; 29 | proxyType = 1; 30 | remoteGlobalIDString = 08906C491DF1A90400FC4209; 31 | remoteInfo = SwiftScanner; 32 | }; 33 | 08906C6F1DF1BBE800FC4209 /* PBXContainerItemProxy */ = { 34 | isa = PBXContainerItemProxy; 35 | containerPortal = 08906C411DF1A90400FC4209 /* Project object */; 36 | proxyType = 1; 37 | remoteGlobalIDString = 08906C491DF1A90400FC4209; 38 | remoteInfo = SwiftScanner; 39 | }; 40 | /* End PBXContainerItemProxy section */ 41 | 42 | /* Begin PBXCopyFilesBuildPhase section */ 43 | 084635EC1DF6D7A200BE9EF1 /* Copy Frameworks */ = { 44 | isa = PBXCopyFilesBuildPhase; 45 | buildActionMask = 2147483647; 46 | dstPath = ""; 47 | dstSubfolderSpec = 10; 48 | files = ( 49 | 084635ED1DF6D7AC00BE9EF1 /* SwiftScanner.framework in Copy Frameworks */, 50 | ); 51 | name = "Copy Frameworks"; 52 | runOnlyForDeploymentPostprocessing = 0; 53 | }; 54 | 08906C721DF1BBEE00FC4209 /* Copy Frameworks */ = { 55 | isa = PBXCopyFilesBuildPhase; 56 | buildActionMask = 2147483647; 57 | dstPath = ""; 58 | dstSubfolderSpec = 10; 59 | files = ( 60 | 08906C731DF1BC4600FC4209 /* SwiftScanner.framework in Copy Frameworks */, 61 | ); 62 | name = "Copy Frameworks"; 63 | runOnlyForDeploymentPostprocessing = 0; 64 | }; 65 | /* End PBXCopyFilesBuildPhase section */ 66 | 67 | /* Begin PBXFileReference section */ 68 | 082292671DF816260077BAE8 /* SwiftScannerTests-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "SwiftScannerTests-Bridging-Header.h"; sourceTree = ""; }; 69 | 084635E11DF6D60000BE9EF1 /* SwiftScannerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SwiftScannerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 70 | 084635E51DF6D60000BE9EF1 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 71 | 08906C4A1DF1A90400FC4209 /* SwiftScanner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SwiftScanner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 72 | 08906C4D1DF1A90400FC4209 /* SwiftScanner.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SwiftScanner.h; sourceTree = ""; }; 73 | 08906C4E1DF1A90400FC4209 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 74 | 08906C5D1DF1BBDD00FC4209 /* DemoApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = DemoApp.app; sourceTree = BUILT_PRODUCTS_DIR; }; 75 | 08906C5F1DF1BBDE00FC4209 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 76 | 08906C611DF1BBDE00FC4209 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 77 | 08906C641DF1BBDE00FC4209 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 78 | 08906C661DF1BBDE00FC4209 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 79 | 08906C691DF1BBDE00FC4209 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 80 | 08906C6B1DF1BBDE00FC4209 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 81 | 08906C751DF1C56C00FC4209 /* StringScanner.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = StringScanner.swift; path = ../Sources/SwiftScanner/StringScanner.swift; sourceTree = ""; }; 82 | 08C606EB1DF817E50014E07E /* NSScanner+Extenions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = "NSScanner+Extenions.swift"; path = "../../Tests/SwiftScannerTests/NSScanner+Extenions.swift"; sourceTree = ""; }; 83 | 08C606EC1DF817E50014E07E /* TestSwiftScanner.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = TestSwiftScanner.swift; path = ../../Tests/SwiftScannerTests/TestSwiftScanner.swift; sourceTree = ""; }; 84 | /* End PBXFileReference section */ 85 | 86 | /* Begin PBXFrameworksBuildPhase section */ 87 | 084635DE1DF6D60000BE9EF1 /* Frameworks */ = { 88 | isa = PBXFrameworksBuildPhase; 89 | buildActionMask = 2147483647; 90 | files = ( 91 | 084635E61DF6D60000BE9EF1 /* SwiftScanner.framework in Frameworks */, 92 | ); 93 | runOnlyForDeploymentPostprocessing = 0; 94 | }; 95 | 08906C461DF1A90400FC4209 /* Frameworks */ = { 96 | isa = PBXFrameworksBuildPhase; 97 | buildActionMask = 2147483647; 98 | files = ( 99 | ); 100 | runOnlyForDeploymentPostprocessing = 0; 101 | }; 102 | 08906C5A1DF1BBDD00FC4209 /* Frameworks */ = { 103 | isa = PBXFrameworksBuildPhase; 104 | buildActionMask = 2147483647; 105 | files = ( 106 | 08906C711DF1BBEB00FC4209 /* SwiftScanner.framework in Frameworks */, 107 | ); 108 | runOnlyForDeploymentPostprocessing = 0; 109 | }; 110 | /* End PBXFrameworksBuildPhase section */ 111 | 112 | /* Begin PBXGroup section */ 113 | 084635E21DF6D60000BE9EF1 /* Tests */ = { 114 | isa = PBXGroup; 115 | children = ( 116 | 084635E51DF6D60000BE9EF1 /* Info.plist */, 117 | 08C606EB1DF817E50014E07E /* NSScanner+Extenions.swift */, 118 | 08C606EC1DF817E50014E07E /* TestSwiftScanner.swift */, 119 | 082292671DF816260077BAE8 /* SwiftScannerTests-Bridging-Header.h */, 120 | ); 121 | name = Tests; 122 | path = SwiftScannerTests; 123 | sourceTree = ""; 124 | }; 125 | 08906C401DF1A90400FC4209 = { 126 | isa = PBXGroup; 127 | children = ( 128 | 08C606EF1DF818670014E07E /* Library */, 129 | 08906C4C1DF1A90400FC4209 /* Framework */, 130 | 08906C5E1DF1BBDE00FC4209 /* DemoApp */, 131 | 084635E21DF6D60000BE9EF1 /* Tests */, 132 | 08906C4B1DF1A90400FC4209 /* Products */, 133 | ); 134 | sourceTree = ""; 135 | }; 136 | 08906C4B1DF1A90400FC4209 /* Products */ = { 137 | isa = PBXGroup; 138 | children = ( 139 | 08906C4A1DF1A90400FC4209 /* SwiftScanner.framework */, 140 | 08906C5D1DF1BBDD00FC4209 /* DemoApp.app */, 141 | 084635E11DF6D60000BE9EF1 /* SwiftScannerTests.xctest */, 142 | ); 143 | name = Products; 144 | sourceTree = ""; 145 | }; 146 | 08906C4C1DF1A90400FC4209 /* Framework */ = { 147 | isa = PBXGroup; 148 | children = ( 149 | 08906C4D1DF1A90400FC4209 /* SwiftScanner.h */, 150 | 08906C4E1DF1A90400FC4209 /* Info.plist */, 151 | ); 152 | name = Framework; 153 | path = SwiftScanner; 154 | sourceTree = ""; 155 | }; 156 | 08906C5E1DF1BBDE00FC4209 /* DemoApp */ = { 157 | isa = PBXGroup; 158 | children = ( 159 | 08906C5F1DF1BBDE00FC4209 /* AppDelegate.swift */, 160 | 08906C611DF1BBDE00FC4209 /* ViewController.swift */, 161 | 08906C631DF1BBDE00FC4209 /* Main.storyboard */, 162 | 08906C661DF1BBDE00FC4209 /* Assets.xcassets */, 163 | 08906C681DF1BBDE00FC4209 /* LaunchScreen.storyboard */, 164 | 08906C6B1DF1BBDE00FC4209 /* Info.plist */, 165 | ); 166 | path = DemoApp; 167 | sourceTree = ""; 168 | }; 169 | 08C606EF1DF818670014E07E /* Library */ = { 170 | isa = PBXGroup; 171 | children = ( 172 | 08906C751DF1C56C00FC4209 /* StringScanner.swift */, 173 | ); 174 | name = Library; 175 | sourceTree = ""; 176 | }; 177 | /* End PBXGroup section */ 178 | 179 | /* Begin PBXHeadersBuildPhase section */ 180 | 08906C471DF1A90400FC4209 /* Headers */ = { 181 | isa = PBXHeadersBuildPhase; 182 | buildActionMask = 2147483647; 183 | files = ( 184 | 08906C4F1DF1A90400FC4209 /* SwiftScanner.h in Headers */, 185 | ); 186 | runOnlyForDeploymentPostprocessing = 0; 187 | }; 188 | /* End PBXHeadersBuildPhase section */ 189 | 190 | /* Begin PBXNativeTarget section */ 191 | 084635E01DF6D60000BE9EF1 /* SwiftScannerTests */ = { 192 | isa = PBXNativeTarget; 193 | buildConfigurationList = 084635EB1DF6D60000BE9EF1 /* Build configuration list for PBXNativeTarget "SwiftScannerTests" */; 194 | buildPhases = ( 195 | 084635DD1DF6D60000BE9EF1 /* Sources */, 196 | 084635DE1DF6D60000BE9EF1 /* Frameworks */, 197 | 084635DF1DF6D60000BE9EF1 /* Resources */, 198 | 084635EC1DF6D7A200BE9EF1 /* Copy Frameworks */, 199 | ); 200 | buildRules = ( 201 | ); 202 | dependencies = ( 203 | 084635E81DF6D60000BE9EF1 /* PBXTargetDependency */, 204 | ); 205 | name = SwiftScannerTests; 206 | productName = SwiftScannerTests; 207 | productReference = 084635E11DF6D60000BE9EF1 /* SwiftScannerTests.xctest */; 208 | productType = "com.apple.product-type.bundle.unit-test"; 209 | }; 210 | 08906C491DF1A90400FC4209 /* SwiftScanner */ = { 211 | isa = PBXNativeTarget; 212 | buildConfigurationList = 08906C521DF1A90400FC4209 /* Build configuration list for PBXNativeTarget "SwiftScanner" */; 213 | buildPhases = ( 214 | 08906C451DF1A90400FC4209 /* Sources */, 215 | 08906C461DF1A90400FC4209 /* Frameworks */, 216 | 08906C471DF1A90400FC4209 /* Headers */, 217 | 08906C481DF1A90400FC4209 /* Resources */, 218 | ); 219 | buildRules = ( 220 | ); 221 | dependencies = ( 222 | ); 223 | name = SwiftScanner; 224 | productName = SwiftScanner; 225 | productReference = 08906C4A1DF1A90400FC4209 /* SwiftScanner.framework */; 226 | productType = "com.apple.product-type.framework"; 227 | }; 228 | 08906C5C1DF1BBDD00FC4209 /* DemoApp */ = { 229 | isa = PBXNativeTarget; 230 | buildConfigurationList = 08906C6C1DF1BBDE00FC4209 /* Build configuration list for PBXNativeTarget "DemoApp" */; 231 | buildPhases = ( 232 | 08906C591DF1BBDD00FC4209 /* Sources */, 233 | 08906C5A1DF1BBDD00FC4209 /* Frameworks */, 234 | 08906C5B1DF1BBDD00FC4209 /* Resources */, 235 | 08906C721DF1BBEE00FC4209 /* Copy Frameworks */, 236 | ); 237 | buildRules = ( 238 | ); 239 | dependencies = ( 240 | 08906C701DF1BBE800FC4209 /* PBXTargetDependency */, 241 | ); 242 | name = DemoApp; 243 | productName = DemoApp; 244 | productReference = 08906C5D1DF1BBDD00FC4209 /* DemoApp.app */; 245 | productType = "com.apple.product-type.application"; 246 | }; 247 | /* End PBXNativeTarget section */ 248 | 249 | /* Begin PBXProject section */ 250 | 08906C411DF1A90400FC4209 /* Project object */ = { 251 | isa = PBXProject; 252 | attributes = { 253 | LastSwiftUpdateCheck = 0810; 254 | LastUpgradeCheck = 1000; 255 | ORGANIZATIONNAME = "Daniele Margutti"; 256 | TargetAttributes = { 257 | 084635E01DF6D60000BE9EF1 = { 258 | CreatedOnToolsVersion = 8.1; 259 | DevelopmentTeam = E5DU3FA699; 260 | LastSwiftMigration = 0810; 261 | ProvisioningStyle = Automatic; 262 | }; 263 | 08906C491DF1A90400FC4209 = { 264 | CreatedOnToolsVersion = 8.1; 265 | DevelopmentTeam = E5DU3FA699; 266 | LastSwiftMigration = 0900; 267 | ProvisioningStyle = Automatic; 268 | }; 269 | 08906C5C1DF1BBDD00FC4209 = { 270 | CreatedOnToolsVersion = 8.1; 271 | DevelopmentTeam = E5DU3FA699; 272 | ProvisioningStyle = Automatic; 273 | }; 274 | }; 275 | }; 276 | buildConfigurationList = 08906C441DF1A90400FC4209 /* Build configuration list for PBXProject "SwiftScanner" */; 277 | compatibilityVersion = "Xcode 3.2"; 278 | developmentRegion = English; 279 | hasScannedForEncodings = 0; 280 | knownRegions = ( 281 | en, 282 | Base, 283 | ); 284 | mainGroup = 08906C401DF1A90400FC4209; 285 | productRefGroup = 08906C4B1DF1A90400FC4209 /* Products */; 286 | projectDirPath = ""; 287 | projectRoot = ""; 288 | targets = ( 289 | 08906C491DF1A90400FC4209 /* SwiftScanner */, 290 | 08906C5C1DF1BBDD00FC4209 /* DemoApp */, 291 | 084635E01DF6D60000BE9EF1 /* SwiftScannerTests */, 292 | ); 293 | }; 294 | /* End PBXProject section */ 295 | 296 | /* Begin PBXResourcesBuildPhase section */ 297 | 084635DF1DF6D60000BE9EF1 /* Resources */ = { 298 | isa = PBXResourcesBuildPhase; 299 | buildActionMask = 2147483647; 300 | files = ( 301 | ); 302 | runOnlyForDeploymentPostprocessing = 0; 303 | }; 304 | 08906C481DF1A90400FC4209 /* Resources */ = { 305 | isa = PBXResourcesBuildPhase; 306 | buildActionMask = 2147483647; 307 | files = ( 308 | ); 309 | runOnlyForDeploymentPostprocessing = 0; 310 | }; 311 | 08906C5B1DF1BBDD00FC4209 /* Resources */ = { 312 | isa = PBXResourcesBuildPhase; 313 | buildActionMask = 2147483647; 314 | files = ( 315 | 08906C6A1DF1BBDE00FC4209 /* LaunchScreen.storyboard in Resources */, 316 | 08906C671DF1BBDE00FC4209 /* Assets.xcassets in Resources */, 317 | 08906C651DF1BBDE00FC4209 /* Main.storyboard in Resources */, 318 | ); 319 | runOnlyForDeploymentPostprocessing = 0; 320 | }; 321 | /* End PBXResourcesBuildPhase section */ 322 | 323 | /* Begin PBXSourcesBuildPhase section */ 324 | 084635DD1DF6D60000BE9EF1 /* Sources */ = { 325 | isa = PBXSourcesBuildPhase; 326 | buildActionMask = 2147483647; 327 | files = ( 328 | 08C606EE1DF817E50014E07E /* TestSwiftScanner.swift in Sources */, 329 | 08C606ED1DF817E50014E07E /* NSScanner+Extenions.swift in Sources */, 330 | ); 331 | runOnlyForDeploymentPostprocessing = 0; 332 | }; 333 | 08906C451DF1A90400FC4209 /* Sources */ = { 334 | isa = PBXSourcesBuildPhase; 335 | buildActionMask = 2147483647; 336 | files = ( 337 | 08906C771DF1C56C00FC4209 /* StringScanner.swift in Sources */, 338 | ); 339 | runOnlyForDeploymentPostprocessing = 0; 340 | }; 341 | 08906C591DF1BBDD00FC4209 /* Sources */ = { 342 | isa = PBXSourcesBuildPhase; 343 | buildActionMask = 2147483647; 344 | files = ( 345 | 08906C621DF1BBDE00FC4209 /* ViewController.swift in Sources */, 346 | 08906C601DF1BBDE00FC4209 /* AppDelegate.swift in Sources */, 347 | ); 348 | runOnlyForDeploymentPostprocessing = 0; 349 | }; 350 | /* End PBXSourcesBuildPhase section */ 351 | 352 | /* Begin PBXTargetDependency section */ 353 | 084635E81DF6D60000BE9EF1 /* PBXTargetDependency */ = { 354 | isa = PBXTargetDependency; 355 | target = 08906C491DF1A90400FC4209 /* SwiftScanner */; 356 | targetProxy = 084635E71DF6D60000BE9EF1 /* PBXContainerItemProxy */; 357 | }; 358 | 08906C701DF1BBE800FC4209 /* PBXTargetDependency */ = { 359 | isa = PBXTargetDependency; 360 | target = 08906C491DF1A90400FC4209 /* SwiftScanner */; 361 | targetProxy = 08906C6F1DF1BBE800FC4209 /* PBXContainerItemProxy */; 362 | }; 363 | /* End PBXTargetDependency section */ 364 | 365 | /* Begin PBXVariantGroup section */ 366 | 08906C631DF1BBDE00FC4209 /* Main.storyboard */ = { 367 | isa = PBXVariantGroup; 368 | children = ( 369 | 08906C641DF1BBDE00FC4209 /* Base */, 370 | ); 371 | name = Main.storyboard; 372 | sourceTree = ""; 373 | }; 374 | 08906C681DF1BBDE00FC4209 /* LaunchScreen.storyboard */ = { 375 | isa = PBXVariantGroup; 376 | children = ( 377 | 08906C691DF1BBDE00FC4209 /* Base */, 378 | ); 379 | name = LaunchScreen.storyboard; 380 | sourceTree = ""; 381 | }; 382 | /* End PBXVariantGroup section */ 383 | 384 | /* Begin XCBuildConfiguration section */ 385 | 084635E91DF6D60000BE9EF1 /* Debug */ = { 386 | isa = XCBuildConfiguration; 387 | buildSettings = { 388 | CLANG_ENABLE_MODULES = YES; 389 | DEVELOPMENT_TEAM = E5DU3FA699; 390 | INFOPLIST_FILE = SwiftScannerTests/Info.plist; 391 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 392 | PRODUCT_BUNDLE_IDENTIFIER = com.danielemargutti.SwiftScannerTests; 393 | PRODUCT_NAME = "$(TARGET_NAME)"; 394 | SWIFT_OBJC_BRIDGING_HEADER = "SwiftScannerTests/SwiftScannerTests-Bridging-Header.h"; 395 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 396 | SWIFT_VERSION = 4.2; 397 | }; 398 | name = Debug; 399 | }; 400 | 084635EA1DF6D60000BE9EF1 /* Release */ = { 401 | isa = XCBuildConfiguration; 402 | buildSettings = { 403 | CLANG_ENABLE_MODULES = YES; 404 | DEVELOPMENT_TEAM = E5DU3FA699; 405 | INFOPLIST_FILE = SwiftScannerTests/Info.plist; 406 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 407 | PRODUCT_BUNDLE_IDENTIFIER = com.danielemargutti.SwiftScannerTests; 408 | PRODUCT_NAME = "$(TARGET_NAME)"; 409 | SWIFT_OBJC_BRIDGING_HEADER = "SwiftScannerTests/SwiftScannerTests-Bridging-Header.h"; 410 | SWIFT_VERSION = 4.2; 411 | }; 412 | name = Release; 413 | }; 414 | 08906C501DF1A90400FC4209 /* Debug */ = { 415 | isa = XCBuildConfiguration; 416 | buildSettings = { 417 | ALWAYS_SEARCH_USER_PATHS = NO; 418 | CLANG_ANALYZER_NONNULL = YES; 419 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 420 | CLANG_CXX_LIBRARY = "libc++"; 421 | CLANG_ENABLE_MODULES = YES; 422 | CLANG_ENABLE_OBJC_ARC = YES; 423 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 424 | CLANG_WARN_BOOL_CONVERSION = YES; 425 | CLANG_WARN_COMMA = YES; 426 | CLANG_WARN_CONSTANT_CONVERSION = YES; 427 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 428 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 429 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 430 | CLANG_WARN_EMPTY_BODY = YES; 431 | CLANG_WARN_ENUM_CONVERSION = YES; 432 | CLANG_WARN_INFINITE_RECURSION = YES; 433 | CLANG_WARN_INT_CONVERSION = YES; 434 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 435 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 436 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 437 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 438 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 439 | CLANG_WARN_STRICT_PROTOTYPES = YES; 440 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 441 | CLANG_WARN_SUSPICIOUS_MOVES = YES; 442 | CLANG_WARN_UNREACHABLE_CODE = YES; 443 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 444 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 445 | COPY_PHASE_STRIP = NO; 446 | CURRENT_PROJECT_VERSION = 1; 447 | DEBUG_INFORMATION_FORMAT = dwarf; 448 | ENABLE_STRICT_OBJC_MSGSEND = YES; 449 | ENABLE_TESTABILITY = YES; 450 | GCC_C_LANGUAGE_STANDARD = gnu99; 451 | GCC_DYNAMIC_NO_PIC = NO; 452 | GCC_NO_COMMON_BLOCKS = YES; 453 | GCC_OPTIMIZATION_LEVEL = 0; 454 | GCC_PREPROCESSOR_DEFINITIONS = ( 455 | "DEBUG=1", 456 | "$(inherited)", 457 | ); 458 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 459 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 460 | GCC_WARN_UNDECLARED_SELECTOR = YES; 461 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 462 | GCC_WARN_UNUSED_FUNCTION = YES; 463 | GCC_WARN_UNUSED_VARIABLE = YES; 464 | IPHONEOS_DEPLOYMENT_TARGET = 10.1; 465 | MTL_ENABLE_DEBUG_INFO = YES; 466 | ONLY_ACTIVE_ARCH = YES; 467 | SDKROOT = iphoneos; 468 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 469 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 470 | SWIFT_SWIFT3_OBJC_INFERENCE = Off; 471 | TARGETED_DEVICE_FAMILY = "1,2"; 472 | VERSIONING_SYSTEM = "apple-generic"; 473 | VERSION_INFO_PREFIX = ""; 474 | }; 475 | name = Debug; 476 | }; 477 | 08906C511DF1A90400FC4209 /* Release */ = { 478 | isa = XCBuildConfiguration; 479 | buildSettings = { 480 | ALWAYS_SEARCH_USER_PATHS = NO; 481 | CLANG_ANALYZER_NONNULL = YES; 482 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 483 | CLANG_CXX_LIBRARY = "libc++"; 484 | CLANG_ENABLE_MODULES = YES; 485 | CLANG_ENABLE_OBJC_ARC = YES; 486 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 487 | CLANG_WARN_BOOL_CONVERSION = YES; 488 | CLANG_WARN_COMMA = YES; 489 | CLANG_WARN_CONSTANT_CONVERSION = YES; 490 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 491 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 492 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 493 | CLANG_WARN_EMPTY_BODY = YES; 494 | CLANG_WARN_ENUM_CONVERSION = YES; 495 | CLANG_WARN_INFINITE_RECURSION = YES; 496 | CLANG_WARN_INT_CONVERSION = YES; 497 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 498 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 499 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 500 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 501 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 502 | CLANG_WARN_STRICT_PROTOTYPES = YES; 503 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 504 | CLANG_WARN_SUSPICIOUS_MOVES = YES; 505 | CLANG_WARN_UNREACHABLE_CODE = YES; 506 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 507 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 508 | COPY_PHASE_STRIP = NO; 509 | CURRENT_PROJECT_VERSION = 1; 510 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 511 | ENABLE_NS_ASSERTIONS = NO; 512 | ENABLE_STRICT_OBJC_MSGSEND = YES; 513 | GCC_C_LANGUAGE_STANDARD = gnu99; 514 | GCC_NO_COMMON_BLOCKS = YES; 515 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 516 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 517 | GCC_WARN_UNDECLARED_SELECTOR = YES; 518 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 519 | GCC_WARN_UNUSED_FUNCTION = YES; 520 | GCC_WARN_UNUSED_VARIABLE = YES; 521 | IPHONEOS_DEPLOYMENT_TARGET = 10.1; 522 | MTL_ENABLE_DEBUG_INFO = NO; 523 | SDKROOT = iphoneos; 524 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 525 | SWIFT_SWIFT3_OBJC_INFERENCE = Off; 526 | TARGETED_DEVICE_FAMILY = "1,2"; 527 | VALIDATE_PRODUCT = YES; 528 | VERSIONING_SYSTEM = "apple-generic"; 529 | VERSION_INFO_PREFIX = ""; 530 | }; 531 | name = Release; 532 | }; 533 | 08906C531DF1A90400FC4209 /* Debug */ = { 534 | isa = XCBuildConfiguration; 535 | buildSettings = { 536 | CLANG_ENABLE_MODULES = YES; 537 | CODE_SIGN_IDENTITY = ""; 538 | DEFINES_MODULE = YES; 539 | DEVELOPMENT_TEAM = E5DU3FA699; 540 | DYLIB_COMPATIBILITY_VERSION = 1; 541 | DYLIB_CURRENT_VERSION = 1; 542 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 543 | INFOPLIST_FILE = SwiftScanner/Info.plist; 544 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 545 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 546 | PRODUCT_BUNDLE_IDENTIFIER = com.danielemargutti.SwiftScanner; 547 | PRODUCT_NAME = "$(TARGET_NAME)"; 548 | SKIP_INSTALL = YES; 549 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 550 | SWIFT_SWIFT3_OBJC_INFERENCE = Off; 551 | SWIFT_VERSION = 4.2; 552 | }; 553 | name = Debug; 554 | }; 555 | 08906C541DF1A90400FC4209 /* Release */ = { 556 | isa = XCBuildConfiguration; 557 | buildSettings = { 558 | CLANG_ENABLE_MODULES = YES; 559 | CODE_SIGN_IDENTITY = ""; 560 | DEFINES_MODULE = YES; 561 | DEVELOPMENT_TEAM = E5DU3FA699; 562 | DYLIB_COMPATIBILITY_VERSION = 1; 563 | DYLIB_CURRENT_VERSION = 1; 564 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 565 | INFOPLIST_FILE = SwiftScanner/Info.plist; 566 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 567 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 568 | PRODUCT_BUNDLE_IDENTIFIER = com.danielemargutti.SwiftScanner; 569 | PRODUCT_NAME = "$(TARGET_NAME)"; 570 | SKIP_INSTALL = YES; 571 | SWIFT_SWIFT3_OBJC_INFERENCE = Off; 572 | SWIFT_VERSION = 4.2; 573 | }; 574 | name = Release; 575 | }; 576 | 08906C6D1DF1BBDE00FC4209 /* Debug */ = { 577 | isa = XCBuildConfiguration; 578 | buildSettings = { 579 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 580 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 581 | DEVELOPMENT_TEAM = E5DU3FA699; 582 | INFOPLIST_FILE = DemoApp/Info.plist; 583 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 584 | PRODUCT_BUNDLE_IDENTIFIER = com.danielemargutti.DemoApp; 585 | PRODUCT_NAME = "$(TARGET_NAME)"; 586 | SWIFT_VERSION = 4.2; 587 | }; 588 | name = Debug; 589 | }; 590 | 08906C6E1DF1BBDE00FC4209 /* Release */ = { 591 | isa = XCBuildConfiguration; 592 | buildSettings = { 593 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 594 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 595 | DEVELOPMENT_TEAM = E5DU3FA699; 596 | INFOPLIST_FILE = DemoApp/Info.plist; 597 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 598 | PRODUCT_BUNDLE_IDENTIFIER = com.danielemargutti.DemoApp; 599 | PRODUCT_NAME = "$(TARGET_NAME)"; 600 | SWIFT_VERSION = 4.2; 601 | }; 602 | name = Release; 603 | }; 604 | /* End XCBuildConfiguration section */ 605 | 606 | /* Begin XCConfigurationList section */ 607 | 084635EB1DF6D60000BE9EF1 /* Build configuration list for PBXNativeTarget "SwiftScannerTests" */ = { 608 | isa = XCConfigurationList; 609 | buildConfigurations = ( 610 | 084635E91DF6D60000BE9EF1 /* Debug */, 611 | 084635EA1DF6D60000BE9EF1 /* Release */, 612 | ); 613 | defaultConfigurationIsVisible = 0; 614 | defaultConfigurationName = Release; 615 | }; 616 | 08906C441DF1A90400FC4209 /* Build configuration list for PBXProject "SwiftScanner" */ = { 617 | isa = XCConfigurationList; 618 | buildConfigurations = ( 619 | 08906C501DF1A90400FC4209 /* Debug */, 620 | 08906C511DF1A90400FC4209 /* Release */, 621 | ); 622 | defaultConfigurationIsVisible = 0; 623 | defaultConfigurationName = Release; 624 | }; 625 | 08906C521DF1A90400FC4209 /* Build configuration list for PBXNativeTarget "SwiftScanner" */ = { 626 | isa = XCConfigurationList; 627 | buildConfigurations = ( 628 | 08906C531DF1A90400FC4209 /* Debug */, 629 | 08906C541DF1A90400FC4209 /* Release */, 630 | ); 631 | defaultConfigurationIsVisible = 0; 632 | defaultConfigurationName = Release; 633 | }; 634 | 08906C6C1DF1BBDE00FC4209 /* Build configuration list for PBXNativeTarget "DemoApp" */ = { 635 | isa = XCConfigurationList; 636 | buildConfigurations = ( 637 | 08906C6D1DF1BBDE00FC4209 /* Debug */, 638 | 08906C6E1DF1BBDE00FC4209 /* Release */, 639 | ); 640 | defaultConfigurationIsVisible = 0; 641 | defaultConfigurationName = Release; 642 | }; 643 | /* End XCConfigurationList section */ 644 | }; 645 | rootObject = 08906C411DF1A90400FC4209 /* Project object */; 646 | } 647 | -------------------------------------------------------------------------------- /Sources/SwiftScanner/StringScanner.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StringScanner.swift 3 | // StringScanner 4 | // 5 | // Created by Daniele Margutti on 03/12/2016. 6 | // Web: http://www.danielemargutti.com 7 | // Mail: hello@danielemargutti.com 8 | // Twitter: @danielemargutti 9 | // Copyright © 2016 Daniele Margutti. All rights reserved. 10 | // 11 | // The MIT License (MIT) 12 | // 13 | // Permission is hereby granted, free of charge, to any person obtaining a copy 14 | // of this software and associated documentation files (the "Software"), to deal 15 | // in the Software without restriction, including without limitation the rights 16 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 17 | // copies of the Software, and to permit persons to whom the Software is 18 | // furnished to do so, subject to the following conditions: 19 | // 20 | // The above copyright notice and this permission notice shall be included in all 21 | // copies or substantial portions of the Software. 22 | // 23 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 24 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 25 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 26 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 27 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 28 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 29 | // SOFTWARE. 30 | 31 | 32 | import Foundation 33 | 34 | /// Throwable errors of the scanner 35 | /// 36 | /// - eof: end of file is reached 37 | /// - invalidInput: invalid input passed to caller function 38 | /// - notFound: failed to match or search value into the scanner's source string 39 | /// - invalidInt: invalid int found, not the expected format 40 | /// - invalidFloat: invalid float found, not the expected format 41 | /// - invalidHEX: invalid HEX found, not the expected format 42 | public enum StringScannerError: Error { 43 | case eof 44 | case notFound 45 | case invalidInput 46 | case invalidInt 47 | case invalidFloat 48 | case invalidHEX 49 | } 50 | 51 | /// Bit digits representation for HEX values 52 | public enum BitDigits: Int { 53 | case bit8 = 8 54 | case bit16 = 16 55 | case bit32 = 32 56 | case bit64 = 64 57 | 58 | var length: Int { 59 | switch self { 60 | case .bit8: return 2 61 | case .bit16: return 4 62 | case .bit32: return 8 63 | case .bit64: return 16 64 | } 65 | } 66 | } 67 | 68 | public class StringScanner { 69 | 70 | public typealias SIndex = String.UnicodeScalarView.Index 71 | public typealias SString = String.UnicodeScalarView 72 | 73 | //--------------- 74 | //MARK: Variables 75 | //--------------- 76 | 77 | /// Thats the current data of the scanner. 78 | /// Internally it's represented by `String.UnicodeScalarView` 79 | public fileprivate(set) var string: SString 80 | 81 | /// Current scanner's index position 82 | public fileprivate(set) var position: SIndex 83 | 84 | /// Get the first index of the source string 85 | public var startIndex: SIndex { 86 | return self.string.startIndex 87 | } 88 | 89 | /// Get the last index of the source string 90 | public var endIndex: SIndex { 91 | return self.string.endIndex 92 | } 93 | 94 | /// Number of scalars consumed up to `position` 95 | /// Since String.UnicodeScalarView.Index is not a RandomAccessIndex, 96 | /// this makes determining the position *much* easier) 97 | public fileprivate(set) var consumed: Int 98 | 99 | /// Return true if scanner reached the end of the string 100 | public var isAtEnd: Bool { 101 | return (self.position == string.endIndex) 102 | } 103 | 104 | /// Returns the content of scanner's string from current value of the position till the end 105 | /// `position` its not updated by the operation. 106 | /// 107 | /// - Returns: remainder string till the end of source's data 108 | public var remainder: String { 109 | var remString: String = "" 110 | var idx = self.position 111 | while idx != self.string.endIndex { 112 | remString.unicodeScalars.append(self.string[idx]) 113 | idx = self.string.index(after:idx) 114 | } 115 | return remString 116 | } 117 | 118 | //----------------- 119 | //MARK: Init 120 | //----------------- 121 | 122 | /// Initialize a new scanner instance from given source string 123 | /// 124 | /// - Parameter string: source string 125 | public init(_ string: String) { 126 | self.string = string.unicodeScalars 127 | self.position = self.string.startIndex 128 | self.consumed = 0 129 | } 130 | 131 | //----------------- 132 | //MARK: Scan Values 133 | //----------------- 134 | 135 | /// If the current scanner position is not at eof return the next scalar at position and move `position` to the next index 136 | /// Otherwise it throws with .eof and `position` is not updated. 137 | /// 138 | /// - Returns: the next scalar 139 | /// - Throws: throw .eof if reached 140 | @discardableResult 141 | public func scanChar() throws -> UnicodeScalar { 142 | guard self.position != self.string.endIndex else { 143 | throw StringScannerError.eof 144 | } 145 | let char = self.string[self.position] 146 | self.position = self.string.index(after: self.position) 147 | self.consumed += 1 148 | return char 149 | } 150 | 151 | /// Scan the next integer value after the current scalar position; consume scalars from {0..9} until a non numeric 152 | /// value is encountered. Return the integer representation in base 10. 153 | /// `position` index is advanced to the end of the number. 154 | /// Throws .invalidInt if scalar at the current `position` is not in the range `"0"` to `"9"` 155 | /// 156 | /// - Returns: read integer in base 10 157 | /// - Throws: throw .invalidInt if non numeric value is encountered 158 | public func scanInt() throws -> Int { 159 | var parsedInt = 0 160 | try self.session(peek: true, accumulate: false, block: { i,c in 161 | while i != self.string.endIndex && self.string[i] >= "0" && self.string[i] <= "9" { 162 | parsedInt = parsedInt * 10 + Int(self.string[i].value - UnicodeScalar("0").value ) 163 | i = self.string.index(after: i) 164 | c += 1 165 | } 166 | if i == self.position { 167 | throw StringScannerError.invalidInt 168 | } 169 | // okay valid, store changes to index 170 | self.position = i 171 | self.consumed = c 172 | }) 173 | return parsedInt 174 | } 175 | 176 | /// Scan for float value (xx.xx) and convert it into Float. 177 | /// Return the float representation in base 10. 178 | /// `position` index is advanced to the end of the number only if conversion works, otherwise it will be not updated and 179 | /// .invalidFloat is thrown. 180 | /// 181 | /// - Returns: parsed float value 182 | /// - Throws: throw an exception .invalidFloat or .eof according to the error 183 | public func scanFloat() throws -> Float { 184 | let prevConsumed = self.consumed 185 | 186 | func throwAndBackBy(length: Int) { 187 | self.position = self.string.index(self.position, offsetBy: -length) 188 | self.consumed -= length 189 | } 190 | 191 | guard let strValue = try self.scan(untilIn: CharacterSet(charactersIn: "-+0123456789.")) else { 192 | throw StringScannerError.invalidFloat 193 | } 194 | guard let value = Float(strValue) else { 195 | throw StringScannerError.invalidFloat 196 | } 197 | return value 198 | } 199 | 200 | /// Scan an HEX digit expressed as 0x/0X or # where number is the value expressed with according bit number 201 | /// If scan succeded it return parsed value and `position` is updated at the end of the value. 202 | /// If scan fail function throws and no values are updated 203 | /// 204 | /// - Parameter digits: type of digits to parse (8,16,32 or 64 bits) 205 | /// - Returns: the int value in base 10 converted from HEX base 206 | /// - Throws: throw .hexExpected if value is not expressed in HEX string according to specified digits 207 | public func scanHexInt(_ digits: BitDigits = .bit16) throws -> Int { 208 | 209 | func parseHexInt(_ digits: BitDigits, _ consumed: inout Int) throws -> Int { 210 | let strValue = try self.scan(length: digits.length) 211 | consumed += digits.length 212 | guard let parsedInt = Int(strValue, radix: 16) else { 213 | throw StringScannerError.invalidHEX 214 | } 215 | return parsedInt 216 | } 217 | 218 | var value: Int = 0 219 | var consumed: Int = 0 220 | if self.string[self.position] == "#" { // the format start with #, numbers is following 221 | try self.move(1, accumulate: false) 222 | consumed += 1 // move on to digits and parse them 223 | value = try parseHexInt(digits, &consumed) 224 | } else { 225 | do { 226 | let prefix = try self.scan(length: 2).uppercased() 227 | consumed += 2 228 | guard prefix == "0X" else { // hex is in 0x or 0X format followed by values 229 | throw StringScannerError.invalidHEX 230 | } 231 | value = try parseHexInt(digits, &consumed) 232 | } catch { 233 | // something went wrong and value cannot be parsed, 234 | // go back with the `position` index and throw an errro 235 | try! self.back(length: consumed) 236 | throw StringScannerError.invalidHEX 237 | } 238 | } 239 | return value 240 | } 241 | 242 | /// Scan until given character is found starting from current scanner `position` till the end of the source string. 243 | /// Scanner's `position` is updated only if character is found and set just before it. 244 | /// Throw an exception if .eof is reached or .notFound if char was not found (in this case scanner's position is not updated) 245 | /// 246 | /// - Parameter char: scalar to search 247 | /// - Returns: the string until the character (excluded) 248 | /// - Throws: throw an exception on .eof or .notFound 249 | public func scan(upTo char: UnicodeScalar) throws -> String? { 250 | return try self.move(peek: false, upTo: char).string 251 | } 252 | 253 | /// Scan until given character's is found. 254 | /// Index is reported before the start of the sequence, scanner's `position` is updated only if sequence is found. 255 | /// Throw an exception if .eof is reached or .notFound if sequence was not found 256 | /// 257 | /// - Parameter charSet: character set to search 258 | /// - Returns: found index 259 | /// - Throws: throw .eof or .notFound 260 | public func scan(upTo charSet: CharacterSet) throws -> String? { 261 | return try self.move(peek: false, accumulate: true, upToCharSet: charSet).string 262 | } 263 | 264 | /// Scan until the next character of the scanner is contained into given character set 265 | /// Scanner's position is updated automatically at the end of the sequence if validated, otherwise it will not touched. 266 | /// 267 | /// - Parameter charSet: chracters set 268 | /// - Returns: the string accumulated scanning until chars set is evaluated 269 | /// - Throws: throw .eof or .notFound 270 | public func scan(untilIn charSet: CharacterSet) throws -> String? { 271 | return try self.move(peek: false, accumulate: true, untilIn: charSet).string 272 | } 273 | 274 | /// Scan until specified string is encountered and update indexes if found 275 | /// Throw an exception if .eof is reached or string is not found 276 | /// 277 | /// - Parameter string: string to search 278 | /// - Returns: string up to search string (excluded) 279 | /// - Throws: throw .eof or .notFound 280 | @discardableResult 281 | public func scan(upTo string: String) throws -> String? { 282 | return try self.move(peek: false, upTo: string).string 283 | } 284 | 285 | /// Scan and consume at the scalar starting from current position, testing it with function test. 286 | /// If test returns `true`, the `position` increased. If `false`, the function returns. 287 | /// 288 | /// - Parameter test: test to pass 289 | /// - Returns: accumulated string 290 | @discardableResult 291 | public func scan(untilTrue test: ((UnicodeScalar) -> (Bool)) ) -> String { 292 | return self.move(peek: false, accumulate: true, untilTrue: test).string! 293 | } 294 | 295 | /// Read next length characters and accumulate it 296 | /// If operation is succeded scanner's `position` are updated according to consumed scalars. 297 | /// If fails an exception is thrown and `position` is not updated 298 | /// 299 | /// - Parameter length: number of scalar to ad 300 | /// - Returns: captured string 301 | /// - Throws: throw an .eof exception 302 | @discardableResult 303 | public func scan(length: Int = 1) throws -> String { 304 | return try self.move(length, accumulate: true).string! 305 | } 306 | 307 | //----------------- 308 | //MARK: Peek Values 309 | //----------------- 310 | 311 | /// Peek until chracter is found starting from current scanner's `position`. 312 | /// Scanner's position is never updated. 313 | /// Throw an exception if .eof is reached or .notFound if char was not found. 314 | /// 315 | /// - Parameter char: scalar to search 316 | /// - Returns: the index found 317 | /// - Throws: throw an exception on .eof or .notFound 318 | public func peek(upTo char: UnicodeScalar) throws -> SIndex { 319 | return try self.move(peek: true, upTo: char).index 320 | } 321 | 322 | /// Peek until one the characters specified by set is encountered 323 | /// Index is reported before the start of the sequence, but scanner's `position` is never updated. 324 | /// Throw an exception if .eof is reached or .notFound if sequence was not found 325 | /// 326 | /// - Parameter charSet: scalar set to search 327 | /// - Returns: found index 328 | /// - Throws: throw .eof or .notFound 329 | public func peek(upTo charSet: CharacterSet) throws -> SIndex { 330 | return try self.move(peek: true, accumulate: false, upToCharSet: charSet).index 331 | } 332 | 333 | /// Peek until the next character of the scanner is contained into given. 334 | /// Scanner's `position` is never updated. 335 | /// 336 | /// - Parameter charSet: characters set to evaluate 337 | /// - Returns: the index at the end of the sequence 338 | /// - Throws: throw .eof or .notFound 339 | public func peek(untilIn charSet: CharacterSet) throws -> SIndex { 340 | let (endIndex,_) = try self.move(peek: true, accumulate: false, untilIn: charSet) 341 | return endIndex 342 | } 343 | 344 | /// Iterate until specified string is encountered without updating indexes. 345 | /// Scanner's `position` is never updated but it's reported the index just before found occourence. 346 | /// 347 | /// - Parameter string: string to search 348 | /// - Returns: index where found string was found 349 | /// - Throws: throw .eof or .notFound 350 | public func peek(upTo string: String) throws -> SIndex { 351 | return try self.move(peek: true, upTo: string).index 352 | } 353 | 354 | /// Peeks at the scalar at the current position, testing it with function test. 355 | /// It only peeks so current scanner's `position` is not increased at the end of the operation 356 | /// 357 | /// - Parameter test: test to pass 358 | public func peek(untilTrue test: ((UnicodeScalar) -> (Bool)) ) -> SIndex { 359 | return self.move(peek: true, accumulate: false, untilTrue: test).index 360 | } 361 | 362 | //----------- 363 | //MARK: Match 364 | //----------- 365 | 366 | /// Throw if the scalar at the current position don't match given scalar. 367 | /// Advance scanner's `position` to the end of the match. 368 | /// 369 | /// - Parameter char: scalar to match 370 | /// - Throws: throw if does not match or index reaches eof 371 | @discardableResult 372 | public func match(_ char: UnicodeScalar) -> Bool { 373 | guard self.position != self.string.endIndex else { 374 | return false 375 | } 376 | if self.string[self.position] != char { 377 | return false 378 | } 379 | // Advance by one scalar, the one we matched 380 | self.position = self.string.index(after: self.position) 381 | self.consumed += 1 382 | return true 383 | } 384 | 385 | /// Throw if scalars starting at the current position don't match scalars in given string. 386 | /// Advance scanner's `position` to the end of the match string. 387 | /// 388 | /// - Parameter string: string to match 389 | /// - Throws: throw if does not match or index reaches eof 390 | public func match(_ match: String) -> Bool { 391 | var result: Bool = true 392 | try! self.session(peek: false, accumulate: false, block: { i,c in 393 | for char in match.unicodeScalars { 394 | if i == self.string.endIndex { 395 | result = false 396 | break 397 | } 398 | if self.string[i] != char { 399 | result = false 400 | break 401 | } 402 | i = self.string.index(after: i) 403 | c += 1 404 | } 405 | }) 406 | return result 407 | } 408 | 409 | //------------ 410 | //MARK: Others 411 | //------------ 412 | 413 | /// Move scanner's `position` to the start of the string 414 | public func reset() { 415 | self.position = self.string.startIndex 416 | self.consumed = 0 417 | } 418 | 419 | /// Move to the index's end index 420 | public func peekAtEnd() { 421 | self.position = self.string.endIndex 422 | let distance = self.string.distance(from: self.string.startIndex, to: self.position) 423 | self.consumed = distance 424 | } 425 | 426 | /// Attempt to advance scanner's by length 427 | /// If operation is not possible (reached the end of the string) it throws and current scanner's `position` of the index did not change 428 | /// If operation succeded scanner's `position` is updated. 429 | /// 430 | /// - Parameter length: length to advance 431 | /// - Throws: throw if .eof 432 | public func skip(length: Int = 1) throws { 433 | try self.move(length, accumulate: false) 434 | } 435 | 436 | /// Attempt to advance scanner past all characters in the provided 437 | /// character set. 438 | /// If the operation is not possible (reached the end of the string), 439 | /// it throws and current scanner's `position` of the index did not change 440 | /// If operation succeded, the scanner's `position` is updated. 441 | /// 442 | /// - Parameter characterSet: The set of characters to skip. 443 | /// - Throws: throw if .eof 444 | public func skip(charactersIn characterSet: CharacterSet) throws { 445 | _ = try self.move(peek: false, accumulate: false, whileIn: characterSet) 446 | } 447 | 448 | /// Attempt to advance the position back by length 449 | /// If operation fails scanner's `position` is not touched 450 | /// If operation succeded scaner's`position` is modified according to new value 451 | /// 452 | /// - Parameter length: length to move 453 | /// - Throws: throw if .eof 454 | public func back(length: Int = 1) throws { 455 | guard length <= self.consumed else { // more than we can go back 456 | throw StringScannerError.invalidInput 457 | } 458 | if length == 1 { 459 | self.position = self.string.index(self.position, offsetBy: -1) 460 | self.consumed -= 1 461 | return 462 | } 463 | 464 | var l = length 465 | while l > 0 { 466 | self.position = self.string.index(self.position, offsetBy: -1) 467 | self.consumed -= 1 468 | l -= 1 469 | } 470 | } 471 | 472 | //-------------------- 473 | //MARK: Private Funcs 474 | //-------------------- 475 | 476 | @discardableResult 477 | private func move(_ length: Int = 1, accumulate: Bool) throws -> (index: SIndex, string: String?) { 478 | 479 | if length == 1 && self.position != self.string.endIndex { 480 | // Special case if proposed length is a single character 481 | self.position = self.string.index(after: self.position) 482 | self.consumed += 1 483 | return (self.position, String(self.string[self.string.index(before: self.position)])) 484 | } 485 | 486 | // Use temporary indexes and don't touch the real ones until we are 487 | // sure the operation succeded 488 | var proposedIdx = self.position 489 | var initialPosition = self.position 490 | var proposedConsumed = 0 491 | 492 | var remaining = length 493 | while remaining > 0 { 494 | if proposedIdx == self.string.endIndex { 495 | throw StringScannerError.eof 496 | } 497 | proposedIdx = self.string.index(after: proposedIdx) 498 | proposedConsumed += 1 499 | remaining -= 1 500 | } 501 | 502 | var result: String? = nil 503 | if accumulate == true { // if user need accumulate string we want to provide it 504 | result = "" 505 | result!.reserveCapacity( (proposedConsumed - self.consumed) ) // just an optimization 506 | while initialPosition != proposedIdx { 507 | result!.unicodeScalars.append(self.string[initialPosition]) 508 | initialPosition = self.string.index(after: initialPosition) 509 | } 510 | } 511 | // Write changes only if skip operation succeded 512 | self.position = proposedIdx 513 | self.consumed = proposedConsumed 514 | return (self.position,result) 515 | } 516 | 517 | 518 | /// Move the index until scalar at given index is part of passed char set, then return the index til it and accumulated string (if requested) 519 | /// 520 | /// - Parameters: 521 | /// - peek: peek to perform a non destructive operation to scanner's `position` 522 | /// - accumulate: accumulate return a valid string in output sum of the scan operation 523 | /// - charSet: character set target of the operation 524 | /// - Returns: index and content of the string 525 | /// - Throws: throw .notFound if string is not found or .eof if end of file is reached 526 | private func move(peek: Bool, accumulate: Bool, untilIn charSet: CharacterSet) throws -> (index: SIndex, string: String?) { 527 | if charSet.contains(self.string[self.position]) == false { // ops 528 | throw StringScannerError.notFound 529 | } 530 | 531 | return try self.session(peek: peek, accumulate: accumulate, block: { i,c in 532 | while i != self.string.endIndex && charSet.contains(self.string[i]) { 533 | i = self.string.index(after: i) 534 | c += 1 535 | } 536 | }) 537 | } 538 | 539 | /// Move the index while scalar at current index is part of passed char set, 540 | /// then return the index after it and the accumulated string (if requested) 541 | /// 542 | /// - Parameters: 543 | /// - peek: peek to perform a non destructive operation to scanner's `position` 544 | /// - accumulate: accumulate return a valid string in output sum of the scan operation 545 | /// - charSet: character set target of the operation 546 | /// - Returns: index and content of the string 547 | /// - Throws: throw .notFound if string is not found or .eof if end of file is reached 548 | private func move(peek: Bool, accumulate: Bool, 549 | whileIn charSet: CharacterSet) throws -> (index: SIndex, string: String?) { 550 | return try self.session(peek: peek, accumulate: accumulate, block: { i,c in 551 | while i != self.string.endIndex && charSet.contains(self.string[i]) { 552 | i = self.string.index(after: i) 553 | c += 1 554 | } 555 | }) 556 | } 557 | 558 | /// Move up to passed scalar is found 559 | /// 560 | /// - Parameters: 561 | /// - peek: peek to perform a non destructive operation to scanner's `position` 562 | /// - char: given scalar to search 563 | /// - Returns: index til found character and accumulated string 564 | /// - Throws: throw .notFound or .eof 565 | @discardableResult 566 | private func move(peek: Bool, upTo char: UnicodeScalar) throws -> (index: SIndex, string: String?) { 567 | return try self.session(peek: peek, accumulate: true, block: { i,c in 568 | // continue moving forward until we reach the end of scanner's string 569 | // or current character at scanner's string current position differs from we are searching for 570 | while i != self.string.endIndex && self.string[i] != char { 571 | i = self.string.index(after: i) 572 | c += 1 573 | } 574 | }) 575 | } 576 | 577 | /// Move next scalar until a character specified in character set is found and return the index and accumulated string (if requested) 578 | /// 579 | /// - Parameters: 580 | /// - peek: peek to perform a non destructive operation to scanner's `position` 581 | /// - accumulate: true to get accumulated string til found index 582 | /// - charSet: character set target of the operation 583 | /// - Returns: index and accumulated string 584 | /// - Throws: throw .eof or .notFound 585 | @discardableResult 586 | private func move(peek: Bool, accumulate: Bool, upToCharSet charSet: CharacterSet) throws -> (index: SIndex, string: String?) { 587 | return try self.session(peek: peek, accumulate: accumulate, block: { i,c in 588 | // continue moving forward until we reach the end of scanner's string 589 | // or current character at scanner's string current position differs from we are searching for 590 | while i != self.string.endIndex && charSet.contains(self.string[i]) == false { 591 | i = self.string.index(after: i) 592 | c += 1 593 | } 594 | }) 595 | } 596 | 597 | /// Move next scalar until specified test for current scalar return true, then get the index and accumulated string 598 | /// 599 | /// - Parameters: 600 | /// - peek: peek to perform a non destructive operation to scanner's `position` 601 | /// - accumulate: true to get accumulated string until test return true 602 | /// - test: test 603 | /// - Returns: throw .eof or .notFound 604 | @discardableResult 605 | private func move(peek: Bool, accumulate: Bool, untilTrue test: ((UnicodeScalar) -> (Bool)) ) -> (index: SIndex, string: String?) { 606 | return try! self.session(peek: peek, accumulate: accumulate, block: { i,c in 607 | while i != self.string.endIndex { 608 | if test(self.string[i]) == false { // test is not passed, we return 609 | return 610 | } 611 | i = self.string.index(after: i) 612 | c += 1 613 | } 614 | }) 615 | } 616 | 617 | 618 | /// Move next scalar until specified string is found, then get the index and accumulated string 619 | /// 620 | /// - Parameters: 621 | /// - peek: peek to perform a non destructive operation to scanner's `position` 622 | /// - string: string to search 623 | /// - Returns: index and string 624 | /// - Throws: throw .eof or .notFound 625 | @discardableResult 626 | private func move(peek: Bool, upTo string: String) throws -> (index: SIndex, string: String?) { 627 | let search = string.unicodeScalars 628 | guard let firstSearchChar = search.first else { // Invalid search string 629 | throw StringScannerError.invalidInput 630 | } 631 | if search.count == 1 { // If we are searching for a single char we want to forward call to the specific func 632 | return try self.move(peek: peek, upTo: firstSearchChar) 633 | } 634 | 635 | return try self.session(peek: peek, accumulate: true, block: { i,c in 636 | 637 | let remainderSearch = search[search.index(after: search.startIndex).. (Void) ) 703 | throws -> (index: SIndex, string: String?) { 704 | // Keep in track with status of the position and consumed indexes before anything change 705 | var initialPosition = self.position 706 | let initialConsumed = self.consumed 707 | 708 | var sessionPosition = self.position 709 | var sessionConsumed = 0 710 | 711 | // execute the real code into block 712 | try block(&sessionPosition,&sessionConsumed) 713 | 714 | if sessionConsumed == 0 { 715 | return (sessionPosition,nil) 716 | } 717 | 718 | var result: String? = nil 719 | if accumulate == true { 720 | result = "" 721 | result!.reserveCapacity( (sessionConsumed - initialConsumed) ) // just an optimization 722 | while initialPosition != sessionPosition { 723 | result!.unicodeScalars.append(self.string[initialPosition]) 724 | initialPosition = self.string.index(after: initialPosition) 725 | } 726 | } 727 | 728 | if peek == false { // Write changes to the main scanner's indexes 729 | self.position = sessionPosition 730 | self.consumed += sessionConsumed 731 | } 732 | return (sessionPosition,result) 733 | } 734 | 735 | } 736 | --------------------------------------------------------------------------------