diff --git a/Docsis Toolkit.xcodeproj/project.pbxproj b/Docsis Toolkit.xcodeproj/project.pbxproj index 7cea3d2..b2f1f6e 100644 --- a/Docsis Toolkit.xcodeproj/project.pbxproj +++ b/Docsis Toolkit.xcodeproj/project.pbxproj @@ -8,12 +8,13 @@ /* Begin PBXBuildFile section */ E2218C9B1EF2D345004298F6 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2218C9A1EF2D345004298F6 /* AppDelegate.swift */; }; - E2218C9D1EF2D345004298F6 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2218C9C1EF2D345004298F6 /* ViewController.swift */; }; E2218CA91EF2D37B004298F6 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = E2218CA81EF2D37B004298F6 /* Assets.xcassets */; }; E2218CDE1EF2D461004298F6 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = E2218CDC1EF2D461004298F6 /* MainMenu.xib */; }; E2218CDF1EF2D461004298F6 /* SettingsWindowController.xib in Resources */ = {isa = PBXBuildFile; fileRef = E2218CDD1EF2D461004298F6 /* SettingsWindowController.xib */; }; E2218CE31EF2D54A004298F6 /* buildnum.ver in Resources */ = {isa = PBXBuildFile; fileRef = E2218CE11EF2D54A004298F6 /* buildnum.ver */; }; E2218CE41EF2D54A004298F6 /* BumpBuildNumber.py in Resources */ = {isa = PBXBuildFile; fileRef = E2218CE21EF2D54A004298F6 /* BumpBuildNumber.py */; }; + E2218D0D1EF2D68F004298F6 /* XMLDictionary.m in Sources */ = {isa = PBXBuildFile; fileRef = E2218D0A1EF2D68F004298F6 /* XMLDictionary.m */; }; + E2218D111EF2D6F1004298F6 /* SettingsWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2218D101EF2D6F1004298F6 /* SettingsWindowController.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -29,13 +30,16 @@ /* Begin PBXFileReference section */ E2218C981EF2D345004298F6 /* Logger4.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Logger4.app; sourceTree = BUILT_PRODUCTS_DIR; }; E2218C9A1EF2D345004298F6 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; - E2218C9C1EF2D345004298F6 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; E2218CA31EF2D345004298F6 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; E2218CA81EF2D37B004298F6 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; E2218CDC1EF2D461004298F6 /* MainMenu.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = MainMenu.xib; sourceTree = ""; }; E2218CDD1EF2D461004298F6 /* SettingsWindowController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = SettingsWindowController.xib; sourceTree = ""; }; E2218CE11EF2D54A004298F6 /* buildnum.ver */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = buildnum.ver; sourceTree = ""; }; E2218CE21EF2D54A004298F6 /* BumpBuildNumber.py */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.python; path = BumpBuildNumber.py; sourceTree = ""; }; + E2218D051EF2D68F004298F6 /* Logger4-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Logger4-Bridging-Header.h"; sourceTree = ""; }; + E2218D081EF2D68F004298F6 /* XMLDictionary.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = XMLDictionary.h; sourceTree = ""; }; + E2218D0A1EF2D68F004298F6 /* XMLDictionary.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = XMLDictionary.m; sourceTree = ""; }; + E2218D101EF2D6F1004298F6 /* SettingsWindowController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsWindowController.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -69,10 +73,8 @@ E2218C991EF2D345004298F6 /* Logger4 */ = { isa = PBXGroup; children = ( + E2218CEB1EF2D648004298F6 /* Swift */, E2218CDA1EF2D43A004298F6 /* Support Files */, - E2218C9A1EF2D345004298F6 /* AppDelegate.swift */, - E2218C9C1EF2D345004298F6 /* ViewController.swift */, - E2218CA31EF2D345004298F6 /* Info.plist */, ); path = Logger4; sourceTree = ""; @@ -81,6 +83,7 @@ isa = PBXGroup; children = ( E2218CA81EF2D37B004298F6 /* Assets.xcassets */, + E2218D041EF2D67B004298F6 /* Obj-C */, ); name = "Shared Resources"; sourceTree = ""; @@ -88,8 +91,9 @@ E2218CDA1EF2D43A004298F6 /* Support Files */ = { isa = PBXGroup; children = ( - E2218CE01EF2D53D004298F6 /* Versioning */, + E2218CA31EF2D345004298F6 /* Info.plist */, E2218CDB1EF2D443004298F6 /* UI */, + E2218CE01EF2D53D004298F6 /* Versioning */, ); name = "Support Files"; sourceTree = ""; @@ -112,12 +116,47 @@ name = Versioning; sourceTree = ""; }; + E2218CEB1EF2D648004298F6 /* Swift */ = { + isa = PBXGroup; + children = ( + E2218D121EF2D754004298F6 /* Delegates */, + E2218D0F1EF2D6E5004298F6 /* Views */, + ); + name = Swift; + sourceTree = ""; + }; + E2218D041EF2D67B004298F6 /* Obj-C */ = { + isa = PBXGroup; + children = ( + E2218D081EF2D68F004298F6 /* XMLDictionary.h */, + E2218D0A1EF2D68F004298F6 /* XMLDictionary.m */, + E2218D051EF2D68F004298F6 /* Logger4-Bridging-Header.h */, + ); + name = "Obj-C"; + sourceTree = ""; + }; + E2218D0F1EF2D6E5004298F6 /* Views */ = { + isa = PBXGroup; + children = ( + E2218D101EF2D6F1004298F6 /* SettingsWindowController.swift */, + ); + name = Views; + sourceTree = ""; + }; + E2218D121EF2D754004298F6 /* Delegates */ = { + isa = PBXGroup; + children = ( + E2218C9A1EF2D345004298F6 /* AppDelegate.swift */, + ); + name = Delegates; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXLegacyTarget section */ E2218CE51EF2D577004298F6 /* Bump Logger4 */ = { isa = PBXLegacyTarget; - buildArgumentsString = "$PROJECT_DIR/Logger4/BumpBuildNumber.py $PROJECT_DIR/Logger4/buildnum.ver $PROJECT_DIR/Logger3/Info.plist"; + buildArgumentsString = "$PROJECT_DIR/Logger4/BumpBuildNumber.py $PROJECT_DIR/Logger4/buildnum.ver $PROJECT_DIR/Logger4/Info.plist"; buildConfigurationList = E2218CE61EF2D577004298F6 /* Build configuration list for PBXLegacyTarget "Bump Logger4" */; buildPhases = ( ); @@ -163,6 +202,7 @@ E2218C971EF2D345004298F6 = { CreatedOnToolsVersion = 8.3.3; DevelopmentTeam = 795KPDV76S; + LastSwiftMigration = 0830; ProvisioningStyle = Automatic; }; E2218CE51EF2D577004298F6 = { @@ -211,7 +251,8 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - E2218C9D1EF2D345004298F6 /* ViewController.swift in Sources */, + E2218D0D1EF2D68F004298F6 /* XMLDictionary.m in Sources */, + E2218D111EF2D6F1004298F6 /* SettingsWindowController.swift in Sources */, E2218C9B1EF2D345004298F6 /* AppDelegate.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -323,6 +364,7 @@ isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = "AppIcon-L4"; + CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = "Mac Developer"; COMBINE_HIDPI_IMAGES = YES; DEVELOPMENT_TEAM = 795KPDV76S; @@ -331,6 +373,8 @@ PRODUCT_BUNDLE_IDENTIFIER = com.weebly.alikja.Logger4; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_OBJC_BRIDGING_HEADER = "Logger4-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 3.0; }; name = Debug; @@ -339,6 +383,7 @@ isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = "AppIcon-L4"; + CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = "Mac Developer"; COMBINE_HIDPI_IMAGES = YES; DEVELOPMENT_TEAM = 795KPDV76S; @@ -347,6 +392,7 @@ PRODUCT_BUNDLE_IDENTIFIER = com.weebly.alikja.Logger4; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_OBJC_BRIDGING_HEADER = "Logger4-Bridging-Header.h"; SWIFT_VERSION = 3.0; }; name = Release; diff --git a/Logger4-Bridging-Header.h b/Logger4-Bridging-Header.h new file mode 100644 index 0000000..39c04d8 --- /dev/null +++ b/Logger4-Bridging-Header.h @@ -0,0 +1,6 @@ +// +// Use this file to import your target's public headers that you would like to +// expose to Swift. +// + +#import "XMLDictionary.h" diff --git a/Logger4/AppDelegate.swift b/Logger4/AppDelegate.swift index 8fe8625..4bcd735 100644 --- a/Logger4/AppDelegate.swift +++ b/Logger4/AppDelegate.swift @@ -11,16 +11,110 @@ import Cocoa @NSApplicationMain class AppDelegate: NSObject, NSApplicationDelegate { + // MARK: - Things that have to be kept alive for the application lifetime + // Status Item + var statusItem: NSStatusItem = NSStatusItem() + // URL Session for DOCSIS data and Internet check + let urlSession: URLSession = URLSession(configuration: .default) + // Preferences + let pref: NSWindowController = NSWindowController(windowNibName: "SettingsWindowController") + // Timers + var timerFreqs: Timer = Timer() + var timerFails: Timer = Timer() + // Document content list + var content: NSMutableDictionary = NSMutableDictionary() - + // MARK: - Application Lifecycle + func applicationDidFinishLaunching(_ aNotification: Notification) { - // Insert code here to initialize your application + // Show Preferneces if defaults are empty + UserDefaults.standard.register(defaults: ["upstream" : "", "downstream" : ""]) + if UserDefaults.standard.string(forKey: "upstream") == nil || UserDefaults.standard.string(forKey: "downstream") == nil { + pref.showWindow(nil) + } + + // Set status item + statusItem = NSStatusBar.system().statusItem(withLength: -2) + statusItem.title = "AL" + // Create status item menu + let menu: NSMenu = NSMenu.init() + menu.autoenablesItems = false + let menuAbout: NSMenuItem = NSMenuItem(title: "About Logger", action: #selector(NSApp.orderFrontStandardAboutPanel(_:)), keyEquivalent: "") + let menuQuit: NSMenuItem = NSMenuItem.init(title: "Quit", action: #selector(NSApp.terminate(_:)), keyEquivalent: "q") + let menuPref: NSMenuItem = NSMenuItem(title: "Preferences", action: #selector(self.preferences(_:)), keyEquivalent: "") + menuPref.image = NSImage(named: NSImageNameActionTemplate) + // Layout menu + menu.addItem(menuAbout) + menu.addItem(NSMenuItem.separator()) + menu.addItem(menuPref) + menu.addItem(NSMenuItem.separator()) + menu.addItem(menuQuit) + // Add menu + statusItem.menu = menu + + // Check if directory exists, creat or exit + let fileManager = FileManager.default + var directory: ObjCBool = ObjCBool(false) + let exists: Bool = fileManager.fileExists(atPath: ("~/KDLog" as NSString).expandingTildeInPath, isDirectory: &directory) + if exists && directory.boolValue { + } else if exists { + NSLog("FILE WITH NAME KDLog EXISTS, REMOVE IT") + exit(1); + } + else { + do { + try fileManager.createDirectory(atPath: ("~/KDLog" as NSString).expandingTildeInPath, withIntermediateDirectories: false, attributes: nil) + } + catch let error as NSError { + NSLog("ERROR ON DIRECTORY CREATION: \(error.localizedDescription)") + exit(1); + } + } + + // Init timers + if UserDefaults.standard.string(forKey: "upstream") != nil || UserDefaults.standard.string(forKey: "downstream") != nil { + initTimers() + } } - func applicationWillTerminate(_ aNotification: Notification) { - // Insert code here to tear down your application + func applicationShouldTerminate(_ sender: NSApplication) -> NSApplicationTerminateReply { + urlSession.invalidateAndCancel() + return .terminateNow + } + + // MARK: - Logging Functions + + func logFreqs(_: Timer) { +// let frequencyOperation: FrequencyOperation = FrequencyOperation() +// OperationQueue.main.addOperation(frequencyOperation) + } + + func logFails(_: Timer) { +// let connectionLossOperation: ConnectionLossOperation = ConnectionLossOperation() +// OperationQueue.main.addOperation(connectionLossOperation) + } + + // MARK: - General functions + + func preferences(_ sender: NSMenuItem) { + timerFreqs.invalidate() + timerFails.invalidate() + + pref.showWindow(sender) + pref.window?.makeKeyAndOrderFront(self) + } + + func initTimers() { + timerFreqs.invalidate() + timerFails.invalidate() + + timerFreqs = Timer.scheduledTimer(timeInterval: 10, target: self, selector: #selector(logFreqs), userInfo: nil, repeats: true) + timerFails = Timer.scheduledTimer(timeInterval: 60, target: self, selector: #selector(logFails), userInfo: nil, repeats: true) + } + + @IBAction func close(_ sender: NSButton){ + initTimers() + sender.superview?.window?.close() } - - } diff --git a/Logger4/Info.plist b/Logger4/Info.plist index 29bbd05..0f8561e 100644 --- a/Logger4/Info.plist +++ b/Logger4/Info.plist @@ -15,9 +15,9 @@ CFBundlePackageType APPL CFBundleShortVersionString - 1.0 + 4.0 CFBundleVersion - 1 + 6 LSMinimumSystemVersion $(MACOSX_DEPLOYMENT_TARGET) LSUIElement diff --git a/Logger4/SettingsWindowController.swift b/Logger4/SettingsWindowController.swift new file mode 100644 index 0000000..5ac8409 --- /dev/null +++ b/Logger4/SettingsWindowController.swift @@ -0,0 +1,19 @@ +// +// SettingsWindowController.swift +// Logger3 +// +// Created by Kilian Hofmann on 15.08.16. +// Copyright © 2016 Kilian Hofmann. All rights reserved. +// + +import Cocoa + +class SettingsWindowController: NSWindowController { + + override func windowDidLoad() { + super.windowDidLoad() + + // Implement this method to handle any initialization after your window controller's window has been loaded from its nib file. + } + +} diff --git a/Logger4/SettingsWindowController.xib b/Logger4/SettingsWindowController.xib index 6fc0040..74731d5 100644 --- a/Logger4/SettingsWindowController.xib +++ b/Logger4/SettingsWindowController.xib @@ -1,11 +1,12 @@ - + - + + - + @@ -15,14 +16,14 @@ - - + + - + - - + + @@ -33,8 +34,8 @@ - - + + @@ -42,8 +43,8 @@ - - + + @@ -51,8 +52,8 @@ - - + + @@ -63,40 +64,8 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - + diff --git a/Logger4/ViewController.swift b/Logger4/ViewController.swift deleted file mode 100644 index 1901cf8..0000000 --- a/Logger4/ViewController.swift +++ /dev/null @@ -1,27 +0,0 @@ -// -// ViewController.swift -// Logger4 -// -// Created by Kilian Hofmann on 15.06.17. -// Copyright © 2017 Kilian Hofmann. All rights reserved. -// - -import Cocoa - -class ViewController: NSViewController { - - override func viewDidLoad() { - super.viewDidLoad() - - // Do any additional setup after loading the view. - } - - override var representedObject: Any? { - didSet { - // Update the view, if already loaded. - } - } - - -} - diff --git a/Logger4/buildnum.ver b/Logger4/buildnum.ver index 14927ff..76a5037 100644 --- a/Logger4/buildnum.ver +++ b/Logger4/buildnum.ver @@ -1,2 +1,2 @@ version 4.0 -build 1 +build 6 diff --git a/XMLDictionary.h b/XMLDictionary.h new file mode 100755 index 0000000..94c3f9f --- /dev/null +++ b/XMLDictionary.h @@ -0,0 +1,103 @@ +// +// XMLDictionary.h +// +// Version 1.4 +// +// Created by Nick Lockwood on 15/11/2010. +// Copyright 2010 Charcoal Design. All rights reserved. +// +// Get the latest version of XMLDictionary from here: +// +// https://github.com/nicklockwood/XMLDictionary +// +// This software is provided 'as-is', without any express or implied +// warranty. In no event will the authors be held liable for any damages +// arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it +// freely, subject to the following restrictions: +// +// 1. The origin of this software must not be misrepresented; you must not +// claim that you wrote the original software. If you use this software +// in a product, an acknowledgment in the product documentation would be +// appreciated but is not required. +// +// 2. Altered source versions must be plainly marked as such, and must not be +// misrepresented as being the original software. +// +// 3. This notice may not be removed or altered from any source distribution. +// + +#import +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wobjc-missing-property-synthesis" + +typedef NS_ENUM(NSInteger, XMLDictionaryAttributesMode) { + XMLDictionaryAttributesModePrefixed = 0, // default + XMLDictionaryAttributesModeDictionary, + XMLDictionaryAttributesModeUnprefixed, + XMLDictionaryAttributesModeDiscard +}; + +typedef NS_ENUM(NSInteger, XMLDictionaryNodeNameMode) { + XMLDictionaryNodeNameModeRootOnly = 0, // default + XMLDictionaryNodeNameModeAlways, + XMLDictionaryNodeNameModeNever +}; + +static NSString *const XMLDictionaryAttributesKey = @"__attributes"; +static NSString *const XMLDictionaryCommentsKey = @"__comments"; +static NSString *const XMLDictionaryTextKey = @"__text"; +static NSString *const XMLDictionaryNodeNameKey = @"__name"; +static NSString *const XMLDictionaryAttributePrefix = @"_"; + +@interface XMLDictionaryParser : NSObject + ++ (XMLDictionaryParser *)sharedInstance; + +@property(nonatomic, assign) BOOL collapseTextNodes; // defaults to YES +@property(nonatomic, assign) BOOL stripEmptyNodes; // defaults to YES +@property(nonatomic, assign) BOOL trimWhiteSpace; // defaults to YES +@property(nonatomic, assign) BOOL alwaysUseArrays; // defaults to NO +@property(nonatomic, assign) BOOL preserveComments; // defaults to NO +@property(nonatomic, assign) BOOL wrapRootNode; // defaults to NO + +@property(nonatomic, assign) XMLDictionaryAttributesMode attributesMode; +@property(nonatomic, assign) XMLDictionaryNodeNameMode nodeNameMode; + +- (NSDictionary *)dictionaryWithParser:(NSXMLParser *)parser; +- (NSDictionary *)dictionaryWithData:(NSData *)data; +- (NSDictionary *)dictionaryWithString:(NSString *)string; +- (NSDictionary *)dictionaryWithFile:(NSString *)path; + +@end + +@interface NSDictionary (XMLDictionary) + ++ (NSDictionary *)dictionaryWithXMLParser:(NSXMLParser *)parser; ++ (NSDictionary *)dictionaryWithXMLData:(NSData *)data; ++ (NSDictionary *)dictionaryWithXMLString:(NSString *)string; ++ (NSDictionary *)dictionaryWithXMLFile:(NSString *)path; + +- (NSDictionary *)attributes; +- (NSDictionary *)childNodes; +- (NSArray *)comments; +- (NSString *)nodeName; +- (NSString *)innerText; +- (NSString *)innerXML; +- (NSString *)XMLString; + +- (NSArray *)arrayValueForKeyPath:(NSString *)keyPath; +- (NSString *)stringValueForKeyPath:(NSString *)keyPath; +- (NSDictionary *)dictionaryValueForKeyPath:(NSString *)keyPath; + +@end + +@interface NSString (XMLDictionary) + +- (NSString *)XMLEncodedString; + +@end + +#pragma GCC diagnostic pop diff --git a/XMLDictionary.m b/XMLDictionary.m new file mode 100755 index 0000000..aaec341 --- /dev/null +++ b/XMLDictionary.m @@ -0,0 +1,485 @@ +// +// XMLDictionary.m +// +// Version 1.4 +// +// Created by Nick Lockwood on 15/11/2010. +// Copyright 2010 Charcoal Design. All rights reserved. +// +// Get the latest version of XMLDictionary from here: +// +// https://github.com/nicklockwood/XMLDictionary +// +// This software is provided 'as-is', without any express or implied +// warranty. In no event will the authors be held liable for any damages +// arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it +// freely, subject to the following restrictions: +// +// 1. The origin of this software must not be misrepresented; you must not +// claim that you wrote the original software. If you use this software +// in a product, an acknowledgment in the product documentation would be +// appreciated but is not required. +// +// 2. Altered source versions must be plainly marked as such, and must not be +// misrepresented as being the original software. +// +// 3. This notice may not be removed or altered from any source distribution. +// + +#import "XMLDictionary.h" + +#pragma GCC diagnostic ignored "-Wobjc-missing-property-synthesis" +#pragma GCC diagnostic ignored "-Wdirect-ivar-access" +#pragma GCC diagnostic ignored "-Wformat-non-iso" +#pragma GCC diagnostic ignored "-Wgnu" + +#import +#if !__has_feature(objc_arc) +#error This class requires automatic reference counting +#endif + +@interface XMLDictionaryParser () + +@property(nonatomic, strong) NSMutableDictionary *root; +@property(nonatomic, strong) NSMutableArray *stack; +@property(nonatomic, strong) NSMutableString *text; + +@end + +@implementation XMLDictionaryParser + ++ (XMLDictionaryParser *)sharedInstance { + static dispatch_once_t once; + static XMLDictionaryParser *sharedInstance; + dispatch_once(&once, ^{ + + sharedInstance = [[XMLDictionaryParser alloc] init]; + }); + return sharedInstance; +} + +- (id)init { + if ((self = [super init])) { + _collapseTextNodes = YES; + _stripEmptyNodes = YES; + _trimWhiteSpace = YES; + _alwaysUseArrays = NO; + _preserveComments = NO; + _wrapRootNode = NO; + } + return self; +} + +- (id)copyWithZone:(NSZone *)zone { + XMLDictionaryParser *copy = [[[self class] allocWithZone:zone] init]; + copy.collapseTextNodes = _collapseTextNodes; + copy.stripEmptyNodes = _stripEmptyNodes; + copy.trimWhiteSpace = _trimWhiteSpace; + copy.alwaysUseArrays = _alwaysUseArrays; + copy.preserveComments = _preserveComments; + copy.attributesMode = _attributesMode; + copy.nodeNameMode = _nodeNameMode; + copy.wrapRootNode = _wrapRootNode; + return copy; +} + +- (NSDictionary *)dictionaryWithParser:(NSXMLParser *)parser { + [parser setDelegate:self]; + [parser parse]; + id result = _root; + _root = nil; + _stack = nil; + _text = nil; + return result; +} + +- (NSDictionary *)dictionaryWithData:(NSData *)data { + NSXMLParser *parser = [[NSXMLParser alloc] initWithData:data]; + return [self dictionaryWithParser:parser]; +} + +- (NSDictionary *)dictionaryWithString:(NSString *)string { + NSData *data = [string dataUsingEncoding:NSUTF8StringEncoding]; + return [self dictionaryWithData:data]; +} + +- (NSDictionary *)dictionaryWithFile:(NSString *)path { + NSData *data = [NSData dataWithContentsOfFile:path]; + return [self dictionaryWithData:data]; +} + ++ (NSString *)XMLStringForNode:(id)node withNodeName:(NSString *)nodeName { + if ([node isKindOfClass:[NSArray class]]) { + NSMutableArray *nodes = [NSMutableArray arrayWithCapacity:[node count]]; + for (id individualNode in node) { + [nodes addObject:[self XMLStringForNode:individualNode + withNodeName:nodeName]]; + } + return [nodes componentsJoinedByString:@"\n"]; + } else if ([node isKindOfClass:[NSDictionary class]]) { + NSDictionary *attributes = [(NSDictionary *)node attributes]; + NSMutableString *attributeString = [NSMutableString string]; + for (NSString *key in [attributes allKeys]) { + [attributeString + appendFormat:@" %@=\"%@\"", + [[key description] XMLEncodedString], + [[attributes[key] description] XMLEncodedString]]; + } + + NSString *innerXML = [node innerXML]; + if ([innerXML length]) { + return + [NSString stringWithFormat:@"<%1$@%2$@>%3$@", nodeName, + attributeString, innerXML]; + } else { + return [NSString + stringWithFormat:@"<%@%@/>", nodeName, attributeString]; + } + } else { + return + [NSString stringWithFormat:@"<%1$@>%2$@", nodeName, + [[node description] XMLEncodedString]]; + } +} + +- (void)endText { + if (_trimWhiteSpace) { + _text = [[_text stringByTrimmingCharactersInSet: + [NSCharacterSet whitespaceAndNewlineCharacterSet]] + mutableCopy]; + } + if ([_text length]) { + NSMutableDictionary *top = [_stack lastObject]; + id existing = top[XMLDictionaryTextKey]; + if ([existing isKindOfClass:[NSArray class]]) { + [existing addObject:_text]; + } else if (existing) { + top[XMLDictionaryTextKey] = [@[ existing, _text ] mutableCopy]; + } else { + top[XMLDictionaryTextKey] = _text; + } + } + _text = nil; +} + +- (void)addText:(NSString *)text { + if (!_text) { + _text = [NSMutableString stringWithString:text]; + } else { + [_text appendString:text]; + } +} + +- (void)parser:(__unused NSXMLParser *)parser + didStartElement:(NSString *)elementName + namespaceURI:(__unused NSString *)namespaceURI + qualifiedName:(__unused NSString *)qName + attributes:(NSDictionary *)attributeDict { + [self endText]; + + NSMutableDictionary *node = [NSMutableDictionary dictionary]; + switch (_nodeNameMode) { + case XMLDictionaryNodeNameModeRootOnly: { + if (!_root) { + node[XMLDictionaryNodeNameKey] = elementName; + } + break; + } + case XMLDictionaryNodeNameModeAlways: { + node[XMLDictionaryNodeNameKey] = elementName; + break; + } + case XMLDictionaryNodeNameModeNever: { + break; + } + } + + if ([attributeDict count]) { + switch (_attributesMode) { + case XMLDictionaryAttributesModePrefixed: { + for (NSString *key in [attributeDict allKeys]) { + node[[XMLDictionaryAttributePrefix + stringByAppendingString:key]] = attributeDict[key]; + } + break; + } + case XMLDictionaryAttributesModeDictionary: { + node[XMLDictionaryAttributesKey] = attributeDict; + break; + } + case XMLDictionaryAttributesModeUnprefixed: { + [node addEntriesFromDictionary:attributeDict]; + break; + } + case XMLDictionaryAttributesModeDiscard: { + break; + } + } + } + + if (!_root) { + _root = node; + _stack = [NSMutableArray arrayWithObject:node]; + if (_wrapRootNode) { + _root = [NSMutableDictionary dictionaryWithObject:_root + forKey:elementName]; + [_stack insertObject:_root atIndex:0]; + } + } else { + NSMutableDictionary *top = [_stack lastObject]; + id existing = top[elementName]; + if ([existing isKindOfClass:[NSArray class]]) { + [existing addObject:node]; + } else if (existing) { + top[elementName] = [@[ existing, node ] mutableCopy]; + } else if (_alwaysUseArrays) { + top[elementName] = [NSMutableArray arrayWithObject:node]; + } else { + top[elementName] = node; + } + [_stack addObject:node]; + } +} + +- (NSString *)nameForNode:(NSDictionary *)node + inDictionary:(NSDictionary *)dict { + if (node.nodeName) { + return node.nodeName; + } else { + for (NSString *name in dict) { + id object = dict[name]; + if (object == node) { + return name; + } else if ([object isKindOfClass:[NSArray class]] && + [object containsObject:node]) { + return name; + } + } + } + return nil; +} + +- (void)parser:(__unused NSXMLParser *)parser + didEndElement:(__unused NSString *)elementName + namespaceURI:(__unused NSString *)namespaceURI + qualifiedName:(__unused NSString *)qName { + [self endText]; + + NSMutableDictionary *top = [_stack lastObject]; + [_stack removeLastObject]; + + if (!top.attributes && !top.childNodes && !top.comments) { + NSMutableDictionary *newTop = [_stack lastObject]; + NSString *nodeName = [self nameForNode:top inDictionary:newTop]; + if (nodeName) { + id parentNode = newTop[nodeName]; + if (top.innerText && _collapseTextNodes) { + if ([parentNode isKindOfClass:[NSArray class]]) { + parentNode[[parentNode count] - 1] = top.innerText; + } else { + newTop[nodeName] = top.innerText; + } + } else if (!top.innerText && _stripEmptyNodes) { + if ([parentNode isKindOfClass:[NSArray class]]) { + [parentNode removeLastObject]; + } else { + [newTop removeObjectForKey:nodeName]; + } + } else if (!top.innerText && !_collapseTextNodes && + !_stripEmptyNodes) { + top[XMLDictionaryTextKey] = @""; + } + } + } +} + +- (void)parser:(__unused NSXMLParser *)parser + foundCharacters:(NSString *)string { + [self addText:string]; +} + +- (void)parser:(__unused NSXMLParser *)parser foundCDATA:(NSData *)CDATABlock { + [self addText:[[NSString alloc] initWithData:CDATABlock + encoding:NSUTF8StringEncoding]]; +} + +- (void)parser:(__unused NSXMLParser *)parser foundComment:(NSString *)comment { + if (_preserveComments) { + NSMutableDictionary *top = [_stack lastObject]; + NSMutableArray *comments = top[XMLDictionaryCommentsKey]; + if (!comments) { + comments = [@[ comment ] mutableCopy]; + top[XMLDictionaryCommentsKey] = comments; + } else { + [comments addObject:comment]; + } + } +} + +@end + +@implementation NSDictionary (XMLDictionary) + ++ (NSDictionary *)dictionaryWithXMLParser:(NSXMLParser *)parser { + return [[[XMLDictionaryParser sharedInstance] copy] + dictionaryWithParser:parser]; +} + ++ (NSDictionary *)dictionaryWithXMLData:(NSData *)data { + return + [[[XMLDictionaryParser sharedInstance] copy] dictionaryWithData:data]; +} + ++ (NSDictionary *)dictionaryWithXMLString:(NSString *)string { + return [[[XMLDictionaryParser sharedInstance] copy] + dictionaryWithString:string]; +} + ++ (NSDictionary *)dictionaryWithXMLFile:(NSString *)path { + return + [[[XMLDictionaryParser sharedInstance] copy] dictionaryWithFile:path]; +} + +- (NSDictionary *)attributes { + NSDictionary *attributes = self[XMLDictionaryAttributesKey]; + if (attributes) { + return [attributes count] ? attributes : nil; + } else { + NSMutableDictionary *filteredDict = + [NSMutableDictionary dictionaryWithDictionary:self]; + [filteredDict removeObjectsForKeys:@[ + XMLDictionaryCommentsKey, + XMLDictionaryTextKey, + XMLDictionaryNodeNameKey + ]]; + for (NSString *key in [filteredDict allKeys]) { + [filteredDict removeObjectForKey:key]; + if ([key hasPrefix:XMLDictionaryAttributePrefix]) { + filteredDict[[key + substringFromIndex:[XMLDictionaryAttributePrefix length]]] = + self[key]; + } + } + return [filteredDict count] ? filteredDict : nil; + } + return nil; +} + +- (NSDictionary *)childNodes { + NSMutableDictionary *filteredDict = [self mutableCopy]; + [filteredDict removeObjectsForKeys:@[ + XMLDictionaryAttributesKey, + XMLDictionaryCommentsKey, + XMLDictionaryTextKey, + XMLDictionaryNodeNameKey + ]]; + for (NSString *key in [filteredDict allKeys]) { + if ([key hasPrefix:XMLDictionaryAttributePrefix]) { + [filteredDict removeObjectForKey:key]; + } + } + return [filteredDict count] ? filteredDict : nil; +} + +- (NSArray *)comments { + return self[XMLDictionaryCommentsKey]; +} + +- (NSString *)nodeName { + return self[XMLDictionaryNodeNameKey]; +} + +- (id)innerText { + id text = self[XMLDictionaryTextKey]; + if ([text isKindOfClass:[NSArray class]]) { + return [text componentsJoinedByString:@"\n"]; + } else { + return text; + } +} + +- (NSString *)innerXML { + NSMutableArray *nodes = [NSMutableArray array]; + + for (NSString *comment in [self comments]) { + [nodes + addObject:[NSString stringWithFormat:@"", + [comment XMLEncodedString]]]; + } + + NSDictionary *childNodes = [self childNodes]; + for (NSString *key in childNodes) { + [nodes addObject:[XMLDictionaryParser XMLStringForNode:childNodes[key] + withNodeName:key]]; + } + + NSString *text = [self innerText]; + if (text) { + [nodes addObject:[text XMLEncodedString]]; + } + + return [nodes componentsJoinedByString:@"\n"]; +} + +- (NSString *)XMLString { + if ([self count] == 1 && ![self nodeName]) { + // ignore outermost dictionary + return [self innerXML]; + } else { + return + [XMLDictionaryParser XMLStringForNode:self + withNodeName:[self nodeName] ?: @"root"]; + } +} + +- (NSArray *)arrayValueForKeyPath:(NSString *)keyPath { + id value = [self valueForKeyPath:keyPath]; + if (value && ![value isKindOfClass:[NSArray class]]) { + return @[ value ]; + } + return value; +} + +- (NSString *)stringValueForKeyPath:(NSString *)keyPath { + id value = [self valueForKeyPath:keyPath]; + if ([value isKindOfClass:[NSArray class]]) { + value = [value count] ? value[0] : nil; + } + if ([value isKindOfClass:[NSDictionary class]]) { + return [(NSDictionary *)value innerText]; + } + return value; +} + +- (NSDictionary *)dictionaryValueForKeyPath:(NSString *)keyPath { + id value = [self valueForKeyPath:keyPath]; + if ([value isKindOfClass:[NSArray class]]) { + value = [value count] ? value[0] : nil; + } + if ([value isKindOfClass:[NSString class]]) { + return @{XMLDictionaryTextKey : value}; + } + return value; +} + +@end + +@implementation NSString (XMLDictionary) + +- (NSString *)XMLEncodedString { + return + [[[[[self stringByReplacingOccurrencesOfString:@"&" withString:@"&"] + stringByReplacingOccurrencesOfString:@"<" + withString:@"<"] + stringByReplacingOccurrencesOfString:@">" + withString:@">"] + stringByReplacingOccurrencesOfString:@"\"" + withString:@"""] + stringByReplacingOccurrencesOfString:@"\'" + withString:@"'"]; +} + +@end