diff --git a/.swift-version b/.swift-version new file mode 100644 index 0000000..f398a20 --- /dev/null +++ b/.swift-version @@ -0,0 +1 @@ +3.0 \ No newline at end of file diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..aa19da3 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,7 @@ +language: objective-c + +osx_image: xcode8 +xcode_sdk: iphonesimulator9.0 + +script: +- xcodebuild -project CheatyXML.xcodeproj -scheme "CheatyXML" -destination "platform=iOS Simulator,name=iPhone 6" test \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..51aa995 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,10 @@ +## 2.0.0 (September 19, 2016) + +- Swift 3 Update +- Fix small memory leak +- Add type casting for tags/attributes +- Change the way to retrieve attributes +- All classes are now prefixed by a 'C' (CXMLParser, ...) to clearly identify CheatyXML +- XMLElement has been renamed to CXMLTag which inherit of CXMLElement +- Code refactoring (files now well separated) +- Add Unit Test diff --git a/CheatyXML.podspec b/CheatyXML.podspec index deca0f7..c9103c9 100644 --- a/CheatyXML.podspec +++ b/CheatyXML.podspec @@ -16,7 +16,7 @@ Pod::Spec.new do |s| # s.name = "CheatyXML" - s.version = "1.2" + s.version = "2.0.0" s.summary = "CheatyXML" s.description = <<-DESC @@ -49,7 +49,7 @@ Pod::Spec.new do |s| # s.author = "Louis Bodart" - s.social_media_url = "http://twitter.com/lobodart" + s.social_media_url = "https://twitter.com/lobodart" # ――― Platform Specifics ――――――――――――――――――――――――――――――――――――――――――――――――――――――― # # @@ -66,7 +66,7 @@ Pod::Spec.new do |s| # Supports git, hg, bzr, svn and HTTP. # - s.source = { :git => "https://github.com/lobodart/CheatyXML.git", :tag => "v1.2" } + s.source = { :git => "https://github.com/lobodart/CheatyXML.git", :tag => "v2.0.0" } # ――― Source Code ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # diff --git a/CheatyXML.xcodeproj/project.pbxproj b/CheatyXML.xcodeproj/project.pbxproj index 0787e66..b3c7f18 100644 --- a/CheatyXML.xcodeproj/project.pbxproj +++ b/CheatyXML.xcodeproj/project.pbxproj @@ -10,7 +10,14 @@ 486D48811AB4A19D004F6258 /* CheatyXML.h in Headers */ = {isa = PBXBuildFile; fileRef = 486D48801AB4A19D004F6258 /* CheatyXML.h */; settings = {ATTRIBUTES = (Public, ); }; }; 486D48871AB4A19D004F6258 /* CheatyXML.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 486D487B1AB4A19D004F6258 /* CheatyXML.framework */; }; 486D488E1AB4A19D004F6258 /* CheatyXMLTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 486D488D1AB4A19D004F6258 /* CheatyXMLTests.swift */; }; - 486D48981AB4A1AF004F6258 /* CheatyXML.swift in Sources */ = {isa = PBXBuildFile; fileRef = 486D48971AB4A1AF004F6258 /* CheatyXML.swift */; }; + 486D48981AB4A1AF004F6258 /* CXMLParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 486D48971AB4A1AF004F6258 /* CXMLParser.swift */; }; + 4894F4EB1D3B5C6100BF093C /* TestFail.xml in Resources */ = {isa = PBXBuildFile; fileRef = 4894F4EA1D3B5C6100BF093C /* TestFail.xml */; }; + 48B018D71D3B629D00E31BE4 /* CXMLTag.swift in Sources */ = {isa = PBXBuildFile; fileRef = 48B018D61D3B629D00E31BE4 /* CXMLTag.swift */; }; + 48B018D91D3B62CB00E31BE4 /* CXMLNullTag.swift in Sources */ = {isa = PBXBuildFile; fileRef = 48B018D81D3B62CB00E31BE4 /* CXMLNullTag.swift */; }; + 48B018DB1D3B636900E31BE4 /* CXMLElement.swift in Sources */ = {isa = PBXBuildFile; fileRef = 48B018DA1D3B636900E31BE4 /* CXMLElement.swift */; }; + 48B018DD1D3B643800E31BE4 /* CXMLAttribute.swift in Sources */ = {isa = PBXBuildFile; fileRef = 48B018DC1D3B643800E31BE4 /* CXMLAttribute.swift */; }; + 48B018DF1D3B6E3500E31BE4 /* CXMLNullAttribute.swift in Sources */ = {isa = PBXBuildFile; fileRef = 48B018DE1D3B6E3500E31BE4 /* CXMLNullAttribute.swift */; }; + 48CD10F61CCFEAD800A8CFAD /* Test.xml in Resources */ = {isa = PBXBuildFile; fileRef = 48CD10F51CCFEAD800A8CFAD /* Test.xml */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -30,7 +37,14 @@ 486D48861AB4A19D004F6258 /* CheatyXMLTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = CheatyXMLTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 486D488C1AB4A19D004F6258 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 486D488D1AB4A19D004F6258 /* CheatyXMLTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheatyXMLTests.swift; sourceTree = ""; }; - 486D48971AB4A1AF004F6258 /* CheatyXML.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CheatyXML.swift; sourceTree = ""; }; + 486D48971AB4A1AF004F6258 /* CXMLParser.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CXMLParser.swift; sourceTree = ""; }; + 4894F4EA1D3B5C6100BF093C /* TestFail.xml */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = TestFail.xml; sourceTree = ""; }; + 48B018D61D3B629D00E31BE4 /* CXMLTag.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CXMLTag.swift; sourceTree = ""; }; + 48B018D81D3B62CB00E31BE4 /* CXMLNullTag.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CXMLNullTag.swift; sourceTree = ""; }; + 48B018DA1D3B636900E31BE4 /* CXMLElement.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CXMLElement.swift; sourceTree = ""; }; + 48B018DC1D3B643800E31BE4 /* CXMLAttribute.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CXMLAttribute.swift; sourceTree = ""; }; + 48B018DE1D3B6E3500E31BE4 /* CXMLNullAttribute.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CXMLNullAttribute.swift; sourceTree = ""; }; + 48CD10F51CCFEAD800A8CFAD /* Test.xml */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = Test.xml; sourceTree = ""; }; 48F5B5041AB4ADAB00E68B10 /* Test.playground */ = {isa = PBXFileReference; lastKnownFileType = file.playground; path = Test.playground; sourceTree = ""; }; /* End PBXFileReference section */ @@ -76,7 +90,12 @@ isa = PBXGroup; children = ( 486D48801AB4A19D004F6258 /* CheatyXML.h */, - 486D48971AB4A1AF004F6258 /* CheatyXML.swift */, + 486D48971AB4A1AF004F6258 /* CXMLParser.swift */, + 48B018D61D3B629D00E31BE4 /* CXMLTag.swift */, + 48B018DA1D3B636900E31BE4 /* CXMLElement.swift */, + 48B018DC1D3B643800E31BE4 /* CXMLAttribute.swift */, + 48B018DE1D3B6E3500E31BE4 /* CXMLNullAttribute.swift */, + 48B018D81D3B62CB00E31BE4 /* CXMLNullTag.swift */, 486D487E1AB4A19D004F6258 /* Supporting Files */, ); path = CheatyXML; @@ -94,6 +113,8 @@ isa = PBXGroup; children = ( 486D488D1AB4A19D004F6258 /* CheatyXMLTests.swift */, + 48CD10F51CCFEAD800A8CFAD /* Test.xml */, + 4894F4EA1D3B5C6100BF093C /* TestFail.xml */, 486D488B1AB4A19D004F6258 /* Supporting Files */, ); path = CheatyXMLTests; @@ -170,9 +191,12 @@ TargetAttributes = { 486D487A1AB4A19D004F6258 = { CreatedOnToolsVersion = 6.2; + DevelopmentTeam = 58G6JMC92R; + LastSwiftMigration = 0800; }; 486D48851AB4A19D004F6258 = { CreatedOnToolsVersion = 6.2; + LastSwiftMigration = 0800; }; }; }; @@ -206,6 +230,8 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + 48CD10F61CCFEAD800A8CFAD /* Test.xml in Resources */, + 4894F4EB1D3B5C6100BF093C /* TestFail.xml in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -216,7 +242,12 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 486D48981AB4A1AF004F6258 /* CheatyXML.swift in Sources */, + 48B018D71D3B629D00E31BE4 /* CXMLTag.swift in Sources */, + 48B018DF1D3B6E3500E31BE4 /* CXMLNullAttribute.swift in Sources */, + 486D48981AB4A1AF004F6258 /* CXMLParser.swift in Sources */, + 48B018DB1D3B636900E31BE4 /* CXMLElement.swift in Sources */, + 48B018DD1D3B643800E31BE4 /* CXMLAttribute.swift in Sources */, + 48B018D91D3B62CB00E31BE4 /* CXMLNullTag.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -328,7 +359,9 @@ isa = XCBuildConfiguration; buildSettings = { CLANG_ENABLE_MODULES = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = 58G6JMC92R; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; @@ -336,9 +369,11 @@ INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 8.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = fr.louisbodart.CheatyXML; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 3.0; }; name = Debug; }; @@ -347,6 +382,7 @@ buildSettings = { CLANG_ENABLE_MODULES = YES; DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = 58G6JMC92R; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; @@ -354,8 +390,10 @@ INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 8.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = fr.louisbodart.CheatyXML; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; + SWIFT_VERSION = 3.0; }; name = Release; }; @@ -373,6 +411,7 @@ INFOPLIST_FILE = CheatyXMLTests/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 3.0; }; name = Debug; }; @@ -386,6 +425,7 @@ INFOPLIST_FILE = CheatyXMLTests/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 3.0; }; name = Release; }; diff --git a/CheatyXML.xcodeproj/project.xcworkspace/xcshareddata/CheatyXML.xccheckout b/CheatyXML.xcodeproj/project.xcworkspace/xcshareddata/CheatyXML.xccheckout new file mode 100644 index 0000000..13b209b --- /dev/null +++ b/CheatyXML.xcodeproj/project.xcworkspace/xcshareddata/CheatyXML.xccheckout @@ -0,0 +1,41 @@ + + + + + IDESourceControlProjectFavoriteDictionaryKey + + IDESourceControlProjectIdentifier + 2F50476B-C33B-4B0E-8BCA-763A4B422BA9 + IDESourceControlProjectName + project + IDESourceControlProjectOriginsDictionary + + 3F4DDE99D53BFFA66E50FFB6AAFA303FD81D596A + github.com:lobodart/CheatyXML.git + + IDESourceControlProjectPath + CheatyXML.xcodeproj/project.xcworkspace + IDESourceControlProjectRelativeInstallPathDictionary + + 3F4DDE99D53BFFA66E50FFB6AAFA303FD81D596A + ../.. + + IDESourceControlProjectURL + github.com:lobodart/CheatyXML.git + IDESourceControlProjectVersion + 111 + IDESourceControlProjectWCCIdentifier + 3F4DDE99D53BFFA66E50FFB6AAFA303FD81D596A + IDESourceControlProjectWCConfigurations + + + IDESourceControlRepositoryExtensionIdentifierKey + public.vcs.git + IDESourceControlWCCIdentifierKey + 3F4DDE99D53BFFA66E50FFB6AAFA303FD81D596A + IDESourceControlWCCName + CheatyXML + + + + diff --git a/CheatyXML.xcodeproj/xcuserdata/louisbodart.xcuserdatad/xcschemes/CheatyXML.xcscheme b/CheatyXML.xcodeproj/xcshareddata/xcschemes/CheatyXML.xcscheme similarity index 95% rename from CheatyXML.xcodeproj/xcuserdata/louisbodart.xcuserdatad/xcschemes/CheatyXML.xcscheme rename to CheatyXML.xcodeproj/xcshareddata/xcschemes/CheatyXML.xcscheme index 0102d0e..f2c2e9f 100644 --- a/CheatyXML.xcodeproj/xcuserdata/louisbodart.xcuserdatad/xcschemes/CheatyXML.xcscheme +++ b/CheatyXML.xcodeproj/xcshareddata/xcschemes/CheatyXML.xcscheme @@ -37,10 +37,10 @@ + shouldUseLaunchSchemeArgsEnv = "YES"> @@ -62,15 +62,18 @@ ReferencedContainer = "container:CheatyXML.xcodeproj"> + + - - - - - - diff --git a/CheatyXML.xcodeproj/xcuserdata/louisbodart.xcuserdatad/xcschemes/xcschememanagement.plist b/CheatyXML.xcodeproj/xcuserdata/louisbodart.xcuserdatad/xcschemes/xcschememanagement.plist index 825c0c1..b7840c3 100644 --- a/CheatyXML.xcodeproj/xcuserdata/louisbodart.xcuserdatad/xcschemes/xcschememanagement.plist +++ b/CheatyXML.xcodeproj/xcuserdata/louisbodart.xcuserdatad/xcschemes/xcschememanagement.plist @@ -4,7 +4,7 @@ SchemeUserState - CheatyXML.xcscheme + CheatyXML.xcscheme_^#shared#^_ orderHint 0 diff --git a/CheatyXML/CXMLAttribute.swift b/CheatyXML/CXMLAttribute.swift new file mode 100644 index 0000000..fe3ae44 --- /dev/null +++ b/CheatyXML/CXMLAttribute.swift @@ -0,0 +1,23 @@ +// +// CXMLAttribute.swift +// CheatyXML +// +// Created by Louis BODART on 16/07/2016. +// Copyright © 2016 Louis BODART. All rights reserved. +// + +import Foundation + +open class CXMLAttribute: CXMLElement { + + open let name: String! + + init(name: String!, value: String?) { + self.name = name + super.init(content: value) + } + + open override var description: String { + return "CXMLAttribute" + } +} diff --git a/CheatyXML/CXMLElement.swift b/CheatyXML/CXMLElement.swift new file mode 100644 index 0000000..d335a9f --- /dev/null +++ b/CheatyXML/CXMLElement.swift @@ -0,0 +1,28 @@ +// +// CXMLElement.swift +// CheatyXML +// +// Created by Louis BODART on 16/07/2016. +// Copyright © 2016 Louis BODART. All rights reserved. +// + +import Foundation + +open class CXMLElement: NSObject { + + internal var _content: String? + + open var string: String? { get { return self._content } } + open var stringValue: String { get { return self.string! } } + open var int: Int? { get { return Int(self._content ?? "") ?? (self.double != nil ? Int(self.doubleValue) : nil) } } + open var intValue: Int { get { return self.int! } } + open var float: Float? { get { return Float(self._content ?? "") } } + open var floatValue: Float { get { return self.float! } } + open var double: Double? { get { return Double(self._content ?? "") } } + open var doubleValue: Double { get { return self.double! } } + + internal init(content: String?) { + self._content = content + super.init() + } +} diff --git a/CheatyXML/CXMLNullAttribute.swift b/CheatyXML/CXMLNullAttribute.swift new file mode 100644 index 0000000..2edd0ac --- /dev/null +++ b/CheatyXML/CXMLNullAttribute.swift @@ -0,0 +1,19 @@ +// +// CXMLNullAttribute.swift +// CheatyXML +// +// Created by Louis BODART on 17/07/2016. +// Copyright © 2016 Louis BODART. All rights reserved. +// + +import Foundation + +// MARK: - CXMLNullAttribute Class +open class CXMLNullAttribute: CXMLAttribute { + + open override var description: String { + return "CXMLNullAttribute" + } + + init() { super.init(name: nil, value: nil) } +} diff --git a/CheatyXML/CXMLNullTag.swift b/CheatyXML/CXMLNullTag.swift new file mode 100644 index 0000000..aeeacc3 --- /dev/null +++ b/CheatyXML/CXMLNullTag.swift @@ -0,0 +1,31 @@ +// +// CXMLNullTag.swift +// CheatyXML +// +// Created by Louis BODART on 16/07/2016. +// Copyright © 2016 Louis BODART. All rights reserved. +// + +import Foundation + +// MARK: - CXMLNullTag Class +open class CXMLNullTag: CXMLTag { + + open override var description: String { + return "CXMLNullTag" + } + + init() { super.init(tagName: nil) } + + open override subscript(tagName: String) -> CXMLTag! { + get { return CXMLNullTag() } + } + + open override subscript(index: Int) -> CXMLTag! { + get { return CXMLNullTag() } + } + + open override subscript(tagName: String, index: Int) -> CXMLTag! { + get { return CXMLNullTag() } + } +} diff --git a/CheatyXML/CXMLParser.swift b/CheatyXML/CXMLParser.swift new file mode 100644 index 0000000..14f0840 --- /dev/null +++ b/CheatyXML/CXMLParser.swift @@ -0,0 +1,122 @@ +// +// CXMLParser.swift +// CheatyXML +// +// Created by Louis BODART on 14/03/2015. +// Copyright (c) 2015 Louis BODART. All rights reserved. +// + +import Foundation + +// MARK: - CXMLParser Class +public class CXMLParser: NSObject, XMLParserDelegate { + + open var rootElement: CXMLTag! { + get { + return self._rootElement + } + } + + fileprivate let _xmlParser: Foundation.XMLParser! + fileprivate var _pointerTree: [OpaquePointer]! + fileprivate var _rootElement: CXMLTag! + fileprivate var _allocatedPointersList: [UnsafeMutablePointer] = [] + + public init?(contentsOfURL: URL) { + self._xmlParser = Foundation.XMLParser(contentsOf: contentsOfURL) + + super.init() + if !self.initXMLParser() { + return nil + } + } + + public init?(data: Data?) { + guard let data = data else { + return nil + } + + self._xmlParser = Foundation.XMLParser(data: data) + + super.init() + guard let _ = self._xmlParser else { + return nil + } + + if !self.initXMLParser() { + return nil + } + } + + public convenience init?(string: String) { + self.init(data: string.data(using: String.Encoding.utf8)) + } + + deinit { + for pointer in self._allocatedPointersList { + pointer.deallocate(capacity: 1) + } + } + + fileprivate func initXMLParser() -> Bool { + guard let _ = self._xmlParser else { + return false + } + + self._xmlParser.delegate = self + return self._xmlParser.parse() + } + + public final func parser(_ parser: Foundation.XMLParser, didStartElement elementName: String, namespaceURI: String?, qualifiedName qName: String?, attributes attributeDict: [String : String]) { + + let newElement: CXMLTag = CXMLTag(tagName: elementName) + if self._rootElement == nil { + self._rootElement = newElement + } + + if !attributeDict.isEmpty { + newElement._attributes = attributeDict.map({ (name: String, value: String) -> CXMLAttribute in + return CXMLAttribute(name: name, value: value) + }) + } + + if self._pointerTree == nil { + self._pointerTree = [] + } else { + let nps = UnsafeMutablePointer(self._pointerTree.last!) + newElement._parentElement = nps.pointee + nps.pointee.addSubElement(newElement) + } + + let ps = UnsafeMutablePointer.allocate(capacity: 1) + ps.initialize(to: newElement) + self._allocatedPointersList.append(ps) + let cps = OpaquePointer(ps) + self._pointerTree.append(cps) + } + + public final func parser(_ parser: Foundation.XMLParser, didEndElement elementName: String, namespaceURI: String?, qualifiedName qName: String?) { + self._pointerTree.removeLast() + } + + public final func parser(_ parser: Foundation.XMLParser, foundCharacters string: String) { + let nps = UnsafeMutablePointer(self._pointerTree.last!) + var tmpString: String! = nps.pointee._content ?? "" + tmpString! += string + + let regex: NSRegularExpression = try! NSRegularExpression(pattern: "[^\\n\\s]+", options: []) + if regex.matches(in: tmpString, options: [], range: NSMakeRange(0, tmpString.characters.count)).count <= 0 { + tmpString = nil + } + + nps.pointee._content = tmpString + } + + open subscript(tagName: String) -> CXMLTag! { + return self._rootElement[tagName] + } + + open subscript(tagName: String, index: Int) -> CXMLTag! { + return self._rootElement[tagName, index] + } +} diff --git a/CheatyXML/CXMLTag.swift b/CheatyXML/CXMLTag.swift new file mode 100644 index 0000000..98e2a30 --- /dev/null +++ b/CheatyXML/CXMLTag.swift @@ -0,0 +1,152 @@ +// +// CXMLTag.swift +// CheatyXML +// +// Created by Louis BODART on 16/07/2016. +// Copyright © 2016 Louis BODART. All rights reserved. +// + +import Foundation + +// MARK: - CXMLTag Class +open class CXMLTag: CXMLElement, Sequence, IteratorProtocol { + + open let tagName: String! + open var attributes: [CXMLAttribute] { + get { + return self._attributes + } + } + + open var count: Int { + get { + guard let _ = self._parentElement else { + return 1 + } + + let array: [CXMLTag] = self._parentElement.arrayOfElementsNamed(self.tagName) + return array.count + } + } + + open var numberOfChildElements: Int { + get { + return self._subElements.count + } + } + + internal var _subElements: [CXMLTag] = [] + internal var _parentElement: CXMLTag! + internal var _attributes: [CXMLAttribute] = [] + internal var _generatorIndex: Int = 0 + + open override var debugDescription: String { get { return self.description } } + open override var description: String { + return "CXMLTag <\(self.tagName)>, attributes(\(self.attributes.count)): \(self.attributes), children: \(self._subElements.count)" + } + + open var exists: Bool { get { return !(self is CXMLNullTag) } } + + open var array: [CXMLTag] { get { return self._parentElement?.arrayOfElementsNamed(self.tagName) ?? self._subElements } } + + open func date(_ format: String) -> Date? { + guard let content = self._content else { + return nil + } + + let dateFormat = DateFormatter() + dateFormat.dateFormat = format + return dateFormat.date(from: content) + } + + open func dateValue(_ format: String) -> Date { + return self.date(format)! + } + + open func makeIterator() -> CXMLTag { + self._generatorIndex = 0 + return self + } + + open func next() -> CXMLTag? { + if self._generatorIndex < 0 || self._generatorIndex >= self._subElements.count { + return nil + } + + defer { self._generatorIndex += 1 } + + return self._subElements[self._generatorIndex] + } + + open func elementsNamed(_ name: String) -> [CXMLTag] { + return self.arrayOfElementsNamed(name) + } + + open func attribute(_ name: String) -> CXMLAttribute { + guard let index = self._attributes.index(where: { (attribute) -> Bool in + return attribute.name == name + }) else { + return CXMLNullAttribute() + } + + return self._attributes[index] + } + + fileprivate final func arrayOfElementsNamed(_ tagName: String) -> [CXMLTag] { + return self._subElements.filter({(element: CXMLTag) -> Bool in + return element.tagName == tagName + }) + } + + internal final func addSubElement(_ subElement: CXMLTag) { + self._subElements.append(subElement) + } + + init(tagName: String!) { + self.tagName = tagName + super.init(content: nil) + } + + open subscript(tagName: String) -> CXMLTag! { + get { + return self[tagName, 0] + } + } + + open subscript(index: Int) -> CXMLTag! { + get { + return self._parentElement[self.tagName, index] + } + } + + open subscript(tagName: String, index: Int) -> CXMLTag! { + get { + if index < 0 { + return CXMLNullTag() + } + + let array = self._subElements.filter({(element: CXMLTag) -> Bool in + return element.tagName == tagName + }) + + if index >= array.count { + return CXMLNullTag() + } + + return array[index] + } + } +} + +public extension Sequence where Iterator.Element: CXMLAttribute { + + public final var dictionary: [String: String] { + get { + return self.reduce([:], { (dict: [String: String], value: CXMLAttribute) -> [String: String] in + var newDict = dict + newDict[value.name] = value._content ?? "" + return newDict + }) + } + } +} diff --git a/CheatyXML/CheatyXML.swift b/CheatyXML/CheatyXML.swift deleted file mode 100644 index e84cb6b..0000000 --- a/CheatyXML/CheatyXML.swift +++ /dev/null @@ -1,242 +0,0 @@ -// -// CheatyXML.swift -// CheatyXML -// -// Created by Louis BODART on 14/03/2015. -// Copyright (c) 2015 Louis BODART. All rights reserved. -// - -import Foundation - -public class XMLParser: NSObject, NSXMLParserDelegate { - - public class XMLElement: NSObject, SequenceType, GeneratorType { - - public let tagName: String! - public var attributes: [NSObject: AnyObject]! { - get { - return self._attributes - } - } - - public var count: Int { - get { - if self._parentElement == nil { - return 1 - } - - let array: [XMLElement] = self._parentElement.arrayOfElementsNamed(self.tagName) - return array.count - } - } - - public var numberOfChildElements: Int { - get { - return self._subElements?.count ?? 0 - } - } - - private var _subElements: [XMLElement]! - private var _content: String? - private var _parentElement: XMLElement! - private var _attributes: [NSObject: AnyObject]! - private var _generatorIndex: Int = 0 - - //override public var debugDescription: String { get { return self.description() } } - - public var stringValue: String! { - get { return self._content! } - } - - public var string: String? { - get { return self._content } - } - - init(tagName: String!) { - self.tagName = tagName - } - - // Problem with Swift 1.2 - /*public func description() -> String { - return "XMLElement <\(self.tagName)>, attributes(\(self.attributes?.count ?? 0)): \(self.attributes), children: \(self._subElements?.count ?? 0)" - }*/ - - public func generate() -> XMLElement { - self._generatorIndex = 0 - return self - } - - public func next() -> XMLElement? { - if self._generatorIndex < 0 || self._generatorIndex >= self._subElements.count { - return nil - } - - return self._subElements[self._generatorIndex++] - } - - public func elementsNamed(name: String) -> [XMLElement] { - return self.arrayOfElementsNamed(name) - } - - private final func arrayOfElementsNamed(tagName: String) -> [XMLElement]! { - return self._subElements.filter({(element: XMLElement) -> Bool in - return element.tagName == tagName - }) - } - - private final func addSubElement(subElement: XMLElement) { - if self._subElements == nil { - self._subElements = [] - } - - self._subElements.append(subElement) - } - - public subscript(tagName: String) -> XMLElement! { - get { - return self[tagName, 0] - } - } - - public subscript(index: Int) -> XMLElement! { - get { - return self._parentElement[self.tagName, index] - } - } - - public subscript(tagName: String, index: Int) -> XMLElement! { - get { - if index < 0 { - return XMLNullElement() - } - - let array = self._subElements.filter({(element: XMLElement) -> Bool in - return element.tagName == tagName - }) - - if index >= array.count { - return XMLNullElement() - } - - return array[index] - } - } - } - - public class XMLNullElement: XMLElement { - - init() { super.init(tagName: nil) } - - // Problem with Swift 1.2 - /*public override func description() -> String { - return "XMLNullElement" - }*/ - - public override subscript(tagName: String) -> XMLElement! { - get { return XMLNullElement() } - } - - public override subscript(index: Int) -> XMLElement! { - get { return XMLNullElement() } - } - - public override subscript(tagName: String, index: Int) -> XMLElement! { - get { return XMLNullElement() } - } - } - - public var rootElement: XMLElement! { - get { - return self._rootElement - } - } - - private let _xmlParser: NSXMLParser! - private var _pointerTree: [COpaquePointer]! - private var _rootElement: XMLElement! - - public init?(contentsOfURL: NSURL) { - self._xmlParser = NSXMLParser(contentsOfURL: contentsOfURL) - - super.init() - if !self.initXMLParser() { - return nil - } - } - - public init?(data: NSData!) { - self._xmlParser = data != nil ? NSXMLParser(data: data) : nil - - super.init() - if self._xmlParser == nil { - return nil - } - - if !self.initXMLParser() { - return nil - } - } - - public convenience init?(string: String) { - self.init(data: NSString(string: string).dataUsingEncoding(NSUTF8StringEncoding)) - } - - private func initXMLParser() -> Bool { - if self._xmlParser == nil { - return false - } - - self._xmlParser.delegate = self - return self._xmlParser.parse() - } - - public final func parser(parser: NSXMLParser, didStartElement elementName: String, namespaceURI: String?, qualifiedName qName: String?, attributes attributeDict: [String : String]) { - - let newElement: XMLElement = XMLElement(tagName: elementName) - if self._rootElement == nil { - self._rootElement = newElement - } - - if !attributeDict.isEmpty { - newElement._attributes = attributeDict - } - - if self._pointerTree == nil { - self._pointerTree = [] - } else { - let nps = UnsafeMutablePointer(self._pointerTree.last!) - newElement._parentElement = nps.memory - nps.memory.addSubElement(newElement) - } - - let ps = UnsafeMutablePointer.alloc(1) - ps.initialize(newElement) - let cps = COpaquePointer(ps) - self._pointerTree.append(cps) - } - - public final func parser(parser: NSXMLParser, didEndElement elementName: String, namespaceURI: String?, qualifiedName qName: String?) { - self._pointerTree.removeLast() - } - - public final func parser(parser: NSXMLParser, foundCharacters string: String) { - let nps = UnsafeMutablePointer(self._pointerTree.last!) - var tmpString: String! = nps.memory._content ?? "" - tmpString! += string - - let regex: NSRegularExpression = try! NSRegularExpression(pattern: "[^\\n\\s]+", options: []) - if regex.matchesInString(tmpString, options: [], range: NSMakeRange(0, tmpString.characters.count)).count <= 0 { - tmpString = nil - } - - nps.memory._content = tmpString - } - - public subscript(tagName: String) -> XMLElement! { - return self._rootElement[tagName] - } - - public subscript(tagName: String, index: Int) -> XMLElement! { - return self._rootElement[tagName, index] - } -} \ No newline at end of file diff --git a/CheatyXML/Info.plist b/CheatyXML/Info.plist index 74ff320..24ee2dd 100644 --- a/CheatyXML/Info.plist +++ b/CheatyXML/Info.plist @@ -7,7 +7,7 @@ CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier - RealmTeam.$(PRODUCT_NAME:rfc1034identifier) + $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 1.0 + 2.0.0 CFBundleSignature ???? CFBundleVersion diff --git a/CheatyXMLTests/CheatyXMLTests.swift b/CheatyXMLTests/CheatyXMLTests.swift index d569a53..397f1ee 100644 --- a/CheatyXMLTests/CheatyXMLTests.swift +++ b/CheatyXMLTests/CheatyXMLTests.swift @@ -8,12 +8,23 @@ import UIKit import XCTest +import CheatyXML class CheatyXMLTests: XCTestCase { + var filePath: String! + var failFilePath: String! + override func setUp() { super.setUp() - // Put setup code here. This method is called before the invocation of each test method in the class. + + self.filePath = Bundle(for: type(of: self)).path(forResource: "Test", ofType: "xml") + XCTAssert(self.filePath != nil) + + self.failFilePath = Bundle(for: type(of: self)).path(forResource: "TestFail", ofType: "xml") + XCTAssert(self.failFilePath != nil) + + self.continueAfterFailure = false } override func tearDown() { @@ -21,16 +32,87 @@ class CheatyXMLTests: XCTestCase { super.tearDown() } - func testExample() { - // This is an example of a functional test case. - XCTAssert(true, "Pass") + func testConstructor() { + let failParser: CXMLParser? = CXMLParser(contentsOfURL: URL(fileURLWithPath: self.failFilePath)) + XCTAssert(failParser == nil) + + let url: URL = URL(fileURLWithPath: self.filePath) + let urlParser: CXMLParser! = CXMLParser(contentsOfURL: url) + XCTAssert(urlParser != nil) + + do { + let content: String = try String(contentsOfFile: self.filePath) + let stringParser: CXMLParser! = CXMLParser(string: content) + XCTAssert(stringParser != nil) + + let data: Data! = try? Data(contentsOf: url) + XCTAssert(data != nil) + + let dataParser: CheatyXML.XMLParser! = CheatyXML.XMLParser(data: data) + XCTAssert(dataParser != nil) + } catch { + XCTAssert(false) + } } - func testPerformanceExample() { - // This is an example of a performance test case. - self.measureBlock() { - // Put the code you want to measure the time of here. - } + func testTagRetrieving() { + let url: URL = URL(fileURLWithPath: self.filePath) + let parser: CXMLParser = CXMLParser(contentsOfURL: url)! + + let blogName: String! = parser["name"].stringValue + XCTAssert(blogName == "MyAwesomeBlog!") + + let admin: String! = parser["users"]["admin"].stringValue + XCTAssert(admin == "lobodart") + + let article: String! = parser["article"][0]["title"].stringValue + XCTAssert(article == "My first article") + + let article2: String! = parser["article"][1]["title"].stringValue + XCTAssert(article2 == "Another article") + + let articleOtherNotation: String! = parser["article", 0]["title"].stringValue + XCTAssert(articleOtherNotation == "My first article") + + let article2OtherNotation: String! = parser["article", 1]["title"].stringValue + XCTAssert(article2OtherNotation == "Another article") } + func testTypeCasts() { + let url: URL = URL(fileURLWithPath: self.filePath) + let parser: CXMLParser = CXMLParser(contentsOfURL: url)! + + let articles = parser["article"].array + XCTAssert(articles.count == 2) + + let article = articles[0] + + XCTAssert(article["title"].stringValue == "My first article") + XCTAssert(article["read"].intValue == 324) + XCTAssert(article["rate"].floatValue == 4.3) + XCTAssert(article["rate"].doubleValue == 4.3) + XCTAssert(article["date"].dateValue("yyyy-MM-dd HH:mm:ss") is Date) + XCTAssert(article["title"].exists == true) + + + XCTAssert(article["foo"].string == nil) + XCTAssert(article["bar"].int == nil) + XCTAssert(article["john"].float == nil) + XCTAssert(article["doe"].double == nil) + XCTAssert(article["date"].date("failFormat") == nil) + XCTAssert(article["failDate"].date("yyyy-MM-dd HH:mm:ss") == nil) + XCTAssert(article["42"].exists == false) + } + + func testAttributeRetrieving() { + let url: URL = URL(fileURLWithPath: self.filePath) + let parser: CXMLParser = CXMLParser(contentsOfURL: url)! + + XCTAssert(parser.rootElement.attributes.count == 2) + XCTAssert(parser.rootElement.attribute("version").stringValue == "1.0") + XCTAssert(parser.rootElement.attribute("version").intValue == 1) + XCTAssert(parser.rootElement.attribute("version").doubleValue == 1.0) + XCTAssert(parser.rootElement.attribute("version").floatValue == 1.0) + XCTAssert(parser.rootElement.attribute("creator").stringValue == "lobodart") + } } diff --git a/CheatyXMLTests/Info.plist b/CheatyXMLTests/Info.plist index 30a7804..1657f2a 100644 --- a/CheatyXMLTests/Info.plist +++ b/CheatyXMLTests/Info.plist @@ -3,7 +3,7 @@ NSPrincipalClass - CheatyXML + CheatyXMLTests CFBundleDevelopmentRegion en CFBundleExecutable diff --git a/CheatyXMLTests/Test.xml b/CheatyXMLTests/Test.xml new file mode 100644 index 0000000..271e66f --- /dev/null +++ b/CheatyXMLTests/Test.xml @@ -0,0 +1,22 @@ + + MyAwesomeBlog! + + lobodart + slash705 + +
+ My first article + This is the first article + 2015-03-15 15:42:42 + 324 + 4.3 + true + false +
+
+ Another article + This is another article + 2015-04-15 15:42:42 + 42 +
+
\ No newline at end of file diff --git a/CheatyXMLTests/TestFail.xml b/CheatyXMLTests/TestFail.xml new file mode 100644 index 0000000..90fd96b --- /dev/null +++ b/CheatyXMLTests/TestFail.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/README.md b/README.md index 9b79c61..207b714 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,17 @@ # CheatyXML + +[![CocoaPods](https://img.shields.io/cocoapods/v/CheatyXML.svg?maxAge=2592000)](https://cocoapods.org/pods/CheatyXML) +[![Build Status](https://travis-ci.org/lobodart/CheatyXML.svg?branch=master)](https://travis-ci.org/lobodart/CheatyXML) + CheatyXML is a Swift framework designed to manage XML easily. ## Installation -To install this, simply add the **.xcodeproj** to your project, and do not forget to link the **.framework**. - +### Cocoapods If you're using **cocoapods**, just add `pod 'CheatyXML'` into your `Podfile` file. +### Manual +To install this, simply add the **.xcodeproj** to your project, and do not forget to link the **.framework**. + Whenever you want to use it in your code, simply type : ```swift import CheatyXML @@ -27,6 +33,7 @@ Let's take the following XML content for all of our examples : This is the first article 2015-03-15 15:42:42 + 42 ... @@ -37,18 +44,18 @@ Let's take the following XML content for all of our examples : ``` -There are different ways to create an instance of XMLParser. +There are different ways to create an instance of CXMLParser. ##### Using an URL ```swift -let parser: XMLParser! = XMLParser(contentsOfURL: ...) // NSURL +let parser: CXMLParser! = CXMLParser(contentsOfURL: ...) // NSURL ``` ##### Using a string ```swift -let parser: XMLParser! = XMLParser(string: ...) // String +let parser: CXMLParser! = CXMLParser(string: ...) // String ``` ##### Using data ```swift -let parser: XMLParser! = XMLParser(data: ...) // NSData +let parser: CXMLParser! = CXMLParser(data: ...) // NSData ``` -- ### Retrieving an element using tags @@ -58,27 +65,27 @@ let blogName: String! = parser["name"].stringValue // Returns a String let blogName: String? = parser["name"].string // Returns an optional String ``` > ###### Note -> You don't have to worry about the root element, but if you want to clarify your code, you can add `rootElement` : +> If you want to clarify your code, you can add `rootElement` : ```swift let element = parser.rootElement["name"] // is the same as the notation seen before ``` -If you want to access to deeper elements like `admin` for example, just chain : +If you want to access to deeper elements like `admin`, just chain : ```swift let blogAdmin: String! = parser["users"]["admin"].stringValue print(blogAdmin) // lobodart ``` -- ### Working with multiple elements -Now let's take a look at the `article` element. Our `blog` element contains a couple of articles. +Now let's take a look at the `article` element. We can see that our `blog` contains a few articles. #### Get an element using its index If we want to get the title of the first article, we can do it like this : ```swift let firstArticleTitle: String! = parser["article", 0]["title"].stringValue let firstArticleTitle: String! = parser["article"][0]["title"].stringValue ``` -Both notation have the same effect. Choose the one you like most. +Both notations have the same effect. Choose the one you like most. #### Browse children of an element To iterate over **all** children of an element, just use the `for in` classic syntax : ```swift @@ -108,11 +115,11 @@ article ``` Of course, you can use this method on any deeper elements (like `users` for example). #### Number of children of an element -If you want to get the total number of child elements contained in an element, you can use this code : +If you want to get the total number of children contained in an element, you can use this code : ```swift // Suppose we have 3 moderators in our example let numberOfElements: Int = parser["users"].numberOfChildElements -print(numberOfElements) // 4 +print(numberOfElements) // 4 (3 moderators + 1 admin) ``` Note that this code counts **all** child elements contained in `users`. Now suppose we want to get the number of moderators **only**. There are 2 different syntaxes. Once again, choose your favorite one : ```swift @@ -120,8 +127,26 @@ let numberOfElements: Int = parser["users"]["moderator"].count let numberOfElements: Int = parser["users"].elementsNamed("moderator").count ``` -- +### Type casting (>= 2.0.0) +CheatyXML allows you to cast tag/attribute values into some common types. You can get either optional or non-optional value for your cast. +```swift +let firstArticleRate = parser["article", 0]["rate"] +firstArticleRate.int // Optional(42) +firstArticleRate.intValue // 42 +firstArticleRate.float // Optional(42.0) +firstArticleRate.floatValue // 42.0 +``` +If you are not sure about the type, use the optional caster. If you try to cast a value with an inappropriate caster, your app will crash. +```swift +let firstArticleTitle = parser["article", 0]["title"] +firstArticleTitle.string // Optional("My first article") +firstArticleTitle.stringValue // "My first article" +firstArticleTitle.int // nil +firstArticleTitle.intValue // CRASH! +``` +-- ### Missing tags -Until now, we always retrieved existing tags but what would happen if a tag doesn't exist ? Fortunaly for us, CheatyXML manages this case. Let's take an example : +Until now, we always retrieved existing tags but what would happen if a tag doesn't exist ? Fortunately for us, CheatyXML can handle this case. Let's take an example : ```swift let articleDate: String! = parser["article", 0]["infos"]["date"].stringValue print(articleDate) // 2015-03-15 15:42:42 @@ -129,18 +154,46 @@ let articleDateFail: String! = parser["articles", 0]["infos"]["date"].string // print(articleDateFail) // nil ``` > ###### Note -If you have doubts on your chain, keep in mind that using `.string` is safer than using `.stringValue`. In the previous example, using `.stringValue` on `articleDateFail` will result in your application to crash. +If you have any doubt, keep in mind that using `.string` is safer than using `.stringValue`. In the previous example, using `.stringValue` on `articleDateFail` will result in your application to crash. In sum, you can make mistakes without worrying about your application crash as long as you don't use `.stringValue`. -- ### Attributes +#### Get one With CheatyXML, getting any attribute is very simple. +##### >= 2.0.0 ```swift -let blogVersion = parser.rootElement.attributes["version"] +let blogVersion = parser.rootElement.attribute("version") +let adminIsActive = parser["users"]["admin"].attribute("is_active") ``` -Another example using a deeper element : +##### Earlier ```swift +let blogVersion = parser.rootElement.attributes["version"] let adminIsActive = parser["users"]["admin"].attributes["is_active"] ``` +As mentionned above, if you are using a version **>= 2.0.0**, you can also use the type casting on attributes. +```swift +let blogVersion = parser.rootElement.attribute("version").floatValue // 1.0 +let creator = parser.rootElement.attribute("creator").stringValue // "lobodart" +``` +> ###### Note +For more information about the optional/non-optional casting, please read the **Type casting** part. + +#### Get all +Once uppon a time, it is very easy to get all the tag attributes. +##### >= 2.0.0 +```swift +let attributes = parser.rootElement.attributes // Will give you a [CXMLAttribute] +let dic = attributes.dictionary // Will give you a [String: String] +``` +##### Earlier +```swift +let attributes = parser.rootElement.attributes // Will give you a [String: String] +``` + +### TO-DO +- [ ] Add more Unit Tests +- [ ] Class mapping +- [ ] XML Generator diff --git a/scripts/ci.sh b/scripts/ci.sh new file mode 100644 index 0000000..0008aea --- /dev/null +++ b/scripts/ci.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash + +set -e + +xcodebuild -project CheatyXML.xcodeproj -scheme "CheatyXML" -destination "platform=iOS Simulator,name=iPhone 6" test \ No newline at end of file