├── MarkNoteParser ├── MarkNoteParser.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ ├── xcshareddata │ │ │ └── MarkNoteParser.xccheckout │ │ └── xcuserdata │ │ │ └── bill.xcuserdatad │ │ │ └── UserInterfaceState.xcuserstate │ └── xcuserdata │ │ └── bill.xcuserdatad │ │ ├── xcdebugger │ │ └── Breakpoints_v2.xcbkptlist │ │ └── xcschemes │ │ ├── MarkNoteParser.xcscheme │ │ └── xcschememanagement.plist ├── MarkNoteParser │ ├── AppDelegate.swift │ ├── Base.lproj │ │ ├── LaunchScreen.xib │ │ └── Main.storyboard │ ├── Images.xcassets │ │ └── AppIcon.appiconset │ │ │ └── Contents.json │ ├── Info.plist │ ├── MarkNoteParser.swift │ ├── ViewController.swift │ └── util │ │ └── StringExtensions.swift └── MarkNoteParserTests │ ├── Info.plist │ └── MarkNoteParserTests.swift └── README.md /MarkNoteParser/MarkNoteParser.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | A9983A841B6A642D00B0AB99 /* MarkNoteParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9983A831B6A642D00B0AB99 /* MarkNoteParser.swift */; }; 11 | A9C1B7441B63295C00A89EFD /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9C1B7431B63295C00A89EFD /* AppDelegate.swift */; }; 12 | A9C1B7461B63295C00A89EFD /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9C1B7451B63295C00A89EFD /* ViewController.swift */; }; 13 | A9C1B7491B63295C00A89EFD /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = A9C1B7471B63295C00A89EFD /* Main.storyboard */; }; 14 | A9C1B74B1B63295C00A89EFD /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A9C1B74A1B63295C00A89EFD /* Images.xcassets */; }; 15 | A9C1B74E1B63295C00A89EFD /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = A9C1B74C1B63295C00A89EFD /* LaunchScreen.xib */; }; 16 | A9C1B75A1B63295C00A89EFD /* MarkNoteParserTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9C1B7591B63295C00A89EFD /* MarkNoteParserTests.swift */; }; 17 | A9C1B7671B63899E00A89EFD /* StringExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9C1B7661B63899E00A89EFD /* StringExtensions.swift */; }; 18 | /* End PBXBuildFile section */ 19 | 20 | /* Begin PBXContainerItemProxy section */ 21 | A9C1B7541B63295C00A89EFD /* PBXContainerItemProxy */ = { 22 | isa = PBXContainerItemProxy; 23 | containerPortal = A9C1B7361B63295C00A89EFD /* Project object */; 24 | proxyType = 1; 25 | remoteGlobalIDString = A9C1B73D1B63295C00A89EFD; 26 | remoteInfo = MarkNoteParser; 27 | }; 28 | /* End PBXContainerItemProxy section */ 29 | 30 | /* Begin PBXFileReference section */ 31 | A9983A831B6A642D00B0AB99 /* MarkNoteParser.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MarkNoteParser.swift; sourceTree = ""; }; 32 | A9C1B73E1B63295C00A89EFD /* MarkNoteParser.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = MarkNoteParser.app; sourceTree = BUILT_PRODUCTS_DIR; }; 33 | A9C1B7421B63295C00A89EFD /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 34 | A9C1B7431B63295C00A89EFD /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 35 | A9C1B7451B63295C00A89EFD /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 36 | A9C1B7481B63295C00A89EFD /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 37 | A9C1B74A1B63295C00A89EFD /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; 38 | A9C1B74D1B63295C00A89EFD /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/LaunchScreen.xib; sourceTree = ""; }; 39 | A9C1B7531B63295C00A89EFD /* MarkNoteParserTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = MarkNoteParserTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 40 | A9C1B7581B63295C00A89EFD /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 41 | A9C1B7591B63295C00A89EFD /* MarkNoteParserTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MarkNoteParserTests.swift; sourceTree = ""; }; 42 | A9C1B7661B63899E00A89EFD /* StringExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = StringExtensions.swift; path = util/StringExtensions.swift; sourceTree = ""; }; 43 | /* End PBXFileReference section */ 44 | 45 | /* Begin PBXFrameworksBuildPhase section */ 46 | A9C1B73B1B63295C00A89EFD /* Frameworks */ = { 47 | isa = PBXFrameworksBuildPhase; 48 | buildActionMask = 2147483647; 49 | files = ( 50 | ); 51 | runOnlyForDeploymentPostprocessing = 0; 52 | }; 53 | A9C1B7501B63295C00A89EFD /* Frameworks */ = { 54 | isa = PBXFrameworksBuildPhase; 55 | buildActionMask = 2147483647; 56 | files = ( 57 | ); 58 | runOnlyForDeploymentPostprocessing = 0; 59 | }; 60 | /* End PBXFrameworksBuildPhase section */ 61 | 62 | /* Begin PBXGroup section */ 63 | A9C1B7351B63295C00A89EFD = { 64 | isa = PBXGroup; 65 | children = ( 66 | A9C1B7401B63295C00A89EFD /* MarkNoteParser */, 67 | A9C1B7561B63295C00A89EFD /* MarkNoteParserTests */, 68 | A9C1B73F1B63295C00A89EFD /* Products */, 69 | ); 70 | sourceTree = ""; 71 | }; 72 | A9C1B73F1B63295C00A89EFD /* Products */ = { 73 | isa = PBXGroup; 74 | children = ( 75 | A9C1B73E1B63295C00A89EFD /* MarkNoteParser.app */, 76 | A9C1B7531B63295C00A89EFD /* MarkNoteParserTests.xctest */, 77 | ); 78 | name = Products; 79 | sourceTree = ""; 80 | }; 81 | A9C1B7401B63295C00A89EFD /* MarkNoteParser */ = { 82 | isa = PBXGroup; 83 | children = ( 84 | A9C1B7661B63899E00A89EFD /* StringExtensions.swift */, 85 | A9983A831B6A642D00B0AB99 /* MarkNoteParser.swift */, 86 | A9C1B7431B63295C00A89EFD /* AppDelegate.swift */, 87 | A9C1B7451B63295C00A89EFD /* ViewController.swift */, 88 | A9C1B7471B63295C00A89EFD /* Main.storyboard */, 89 | A9C1B74A1B63295C00A89EFD /* Images.xcassets */, 90 | A9C1B74C1B63295C00A89EFD /* LaunchScreen.xib */, 91 | A9C1B7411B63295C00A89EFD /* Supporting Files */, 92 | ); 93 | path = MarkNoteParser; 94 | sourceTree = ""; 95 | }; 96 | A9C1B7411B63295C00A89EFD /* Supporting Files */ = { 97 | isa = PBXGroup; 98 | children = ( 99 | A9C1B7421B63295C00A89EFD /* Info.plist */, 100 | ); 101 | name = "Supporting Files"; 102 | sourceTree = ""; 103 | }; 104 | A9C1B7561B63295C00A89EFD /* MarkNoteParserTests */ = { 105 | isa = PBXGroup; 106 | children = ( 107 | A9C1B7591B63295C00A89EFD /* MarkNoteParserTests.swift */, 108 | A9C1B7571B63295C00A89EFD /* Supporting Files */, 109 | ); 110 | path = MarkNoteParserTests; 111 | sourceTree = ""; 112 | }; 113 | A9C1B7571B63295C00A89EFD /* Supporting Files */ = { 114 | isa = PBXGroup; 115 | children = ( 116 | A9C1B7581B63295C00A89EFD /* Info.plist */, 117 | ); 118 | name = "Supporting Files"; 119 | sourceTree = ""; 120 | }; 121 | /* End PBXGroup section */ 122 | 123 | /* Begin PBXNativeTarget section */ 124 | A9C1B73D1B63295C00A89EFD /* MarkNoteParser */ = { 125 | isa = PBXNativeTarget; 126 | buildConfigurationList = A9C1B75D1B63295C00A89EFD /* Build configuration list for PBXNativeTarget "MarkNoteParser" */; 127 | buildPhases = ( 128 | A9C1B73A1B63295C00A89EFD /* Sources */, 129 | A9C1B73B1B63295C00A89EFD /* Frameworks */, 130 | A9C1B73C1B63295C00A89EFD /* Resources */, 131 | ); 132 | buildRules = ( 133 | ); 134 | dependencies = ( 135 | ); 136 | name = MarkNoteParser; 137 | productName = MarkNoteParser; 138 | productReference = A9C1B73E1B63295C00A89EFD /* MarkNoteParser.app */; 139 | productType = "com.apple.product-type.application"; 140 | }; 141 | A9C1B7521B63295C00A89EFD /* MarkNoteParserTests */ = { 142 | isa = PBXNativeTarget; 143 | buildConfigurationList = A9C1B7601B63295C00A89EFD /* Build configuration list for PBXNativeTarget "MarkNoteParserTests" */; 144 | buildPhases = ( 145 | A9C1B74F1B63295C00A89EFD /* Sources */, 146 | A9C1B7501B63295C00A89EFD /* Frameworks */, 147 | A9C1B7511B63295C00A89EFD /* Resources */, 148 | ); 149 | buildRules = ( 150 | ); 151 | dependencies = ( 152 | A9C1B7551B63295C00A89EFD /* PBXTargetDependency */, 153 | ); 154 | name = MarkNoteParserTests; 155 | productName = MarkNoteParserTests; 156 | productReference = A9C1B7531B63295C00A89EFD /* MarkNoteParserTests.xctest */; 157 | productType = "com.apple.product-type.bundle.unit-test"; 158 | }; 159 | /* End PBXNativeTarget section */ 160 | 161 | /* Begin PBXProject section */ 162 | A9C1B7361B63295C00A89EFD /* Project object */ = { 163 | isa = PBXProject; 164 | attributes = { 165 | LastSwiftUpdateCheck = 0700; 166 | LastUpgradeCheck = 0640; 167 | ORGANIZATIONNAME = MarkNote; 168 | TargetAttributes = { 169 | A9C1B73D1B63295C00A89EFD = { 170 | CreatedOnToolsVersion = 6.4; 171 | }; 172 | A9C1B7521B63295C00A89EFD = { 173 | CreatedOnToolsVersion = 6.4; 174 | TestTargetID = A9C1B73D1B63295C00A89EFD; 175 | }; 176 | }; 177 | }; 178 | buildConfigurationList = A9C1B7391B63295C00A89EFD /* Build configuration list for PBXProject "MarkNoteParser" */; 179 | compatibilityVersion = "Xcode 3.2"; 180 | developmentRegion = English; 181 | hasScannedForEncodings = 0; 182 | knownRegions = ( 183 | en, 184 | Base, 185 | ); 186 | mainGroup = A9C1B7351B63295C00A89EFD; 187 | productRefGroup = A9C1B73F1B63295C00A89EFD /* Products */; 188 | projectDirPath = ""; 189 | projectRoot = ""; 190 | targets = ( 191 | A9C1B73D1B63295C00A89EFD /* MarkNoteParser */, 192 | A9C1B7521B63295C00A89EFD /* MarkNoteParserTests */, 193 | ); 194 | }; 195 | /* End PBXProject section */ 196 | 197 | /* Begin PBXResourcesBuildPhase section */ 198 | A9C1B73C1B63295C00A89EFD /* Resources */ = { 199 | isa = PBXResourcesBuildPhase; 200 | buildActionMask = 2147483647; 201 | files = ( 202 | A9C1B7491B63295C00A89EFD /* Main.storyboard in Resources */, 203 | A9C1B74E1B63295C00A89EFD /* LaunchScreen.xib in Resources */, 204 | A9C1B74B1B63295C00A89EFD /* Images.xcassets in Resources */, 205 | ); 206 | runOnlyForDeploymentPostprocessing = 0; 207 | }; 208 | A9C1B7511B63295C00A89EFD /* Resources */ = { 209 | isa = PBXResourcesBuildPhase; 210 | buildActionMask = 2147483647; 211 | files = ( 212 | ); 213 | runOnlyForDeploymentPostprocessing = 0; 214 | }; 215 | /* End PBXResourcesBuildPhase section */ 216 | 217 | /* Begin PBXSourcesBuildPhase section */ 218 | A9C1B73A1B63295C00A89EFD /* Sources */ = { 219 | isa = PBXSourcesBuildPhase; 220 | buildActionMask = 2147483647; 221 | files = ( 222 | A9C1B7671B63899E00A89EFD /* StringExtensions.swift in Sources */, 223 | A9983A841B6A642D00B0AB99 /* MarkNoteParser.swift in Sources */, 224 | A9C1B7461B63295C00A89EFD /* ViewController.swift in Sources */, 225 | A9C1B7441B63295C00A89EFD /* AppDelegate.swift in Sources */, 226 | ); 227 | runOnlyForDeploymentPostprocessing = 0; 228 | }; 229 | A9C1B74F1B63295C00A89EFD /* Sources */ = { 230 | isa = PBXSourcesBuildPhase; 231 | buildActionMask = 2147483647; 232 | files = ( 233 | A9C1B75A1B63295C00A89EFD /* MarkNoteParserTests.swift in Sources */, 234 | ); 235 | runOnlyForDeploymentPostprocessing = 0; 236 | }; 237 | /* End PBXSourcesBuildPhase section */ 238 | 239 | /* Begin PBXTargetDependency section */ 240 | A9C1B7551B63295C00A89EFD /* PBXTargetDependency */ = { 241 | isa = PBXTargetDependency; 242 | target = A9C1B73D1B63295C00A89EFD /* MarkNoteParser */; 243 | targetProxy = A9C1B7541B63295C00A89EFD /* PBXContainerItemProxy */; 244 | }; 245 | /* End PBXTargetDependency section */ 246 | 247 | /* Begin PBXVariantGroup section */ 248 | A9C1B7471B63295C00A89EFD /* Main.storyboard */ = { 249 | isa = PBXVariantGroup; 250 | children = ( 251 | A9C1B7481B63295C00A89EFD /* Base */, 252 | ); 253 | name = Main.storyboard; 254 | sourceTree = ""; 255 | }; 256 | A9C1B74C1B63295C00A89EFD /* LaunchScreen.xib */ = { 257 | isa = PBXVariantGroup; 258 | children = ( 259 | A9C1B74D1B63295C00A89EFD /* Base */, 260 | ); 261 | name = LaunchScreen.xib; 262 | sourceTree = ""; 263 | }; 264 | /* End PBXVariantGroup section */ 265 | 266 | /* Begin XCBuildConfiguration section */ 267 | A9C1B75B1B63295C00A89EFD /* Debug */ = { 268 | isa = XCBuildConfiguration; 269 | buildSettings = { 270 | ALWAYS_SEARCH_USER_PATHS = NO; 271 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 272 | CLANG_CXX_LIBRARY = "libc++"; 273 | CLANG_ENABLE_MODULES = YES; 274 | CLANG_ENABLE_OBJC_ARC = YES; 275 | CLANG_WARN_BOOL_CONVERSION = YES; 276 | CLANG_WARN_CONSTANT_CONVERSION = YES; 277 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 278 | CLANG_WARN_EMPTY_BODY = YES; 279 | CLANG_WARN_ENUM_CONVERSION = YES; 280 | CLANG_WARN_INT_CONVERSION = YES; 281 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 282 | CLANG_WARN_UNREACHABLE_CODE = YES; 283 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 284 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 285 | COPY_PHASE_STRIP = NO; 286 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 287 | ENABLE_STRICT_OBJC_MSGSEND = YES; 288 | GCC_C_LANGUAGE_STANDARD = gnu99; 289 | GCC_DYNAMIC_NO_PIC = NO; 290 | GCC_NO_COMMON_BLOCKS = YES; 291 | GCC_OPTIMIZATION_LEVEL = 0; 292 | GCC_PREPROCESSOR_DEFINITIONS = ( 293 | "DEBUG=1", 294 | "$(inherited)", 295 | ); 296 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 297 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 298 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 299 | GCC_WARN_UNDECLARED_SELECTOR = YES; 300 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 301 | GCC_WARN_UNUSED_FUNCTION = YES; 302 | GCC_WARN_UNUSED_VARIABLE = YES; 303 | IPHONEOS_DEPLOYMENT_TARGET = 8.4; 304 | MTL_ENABLE_DEBUG_INFO = YES; 305 | ONLY_ACTIVE_ARCH = YES; 306 | SDKROOT = iphoneos; 307 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 308 | }; 309 | name = Debug; 310 | }; 311 | A9C1B75C1B63295C00A89EFD /* Release */ = { 312 | isa = XCBuildConfiguration; 313 | buildSettings = { 314 | ALWAYS_SEARCH_USER_PATHS = NO; 315 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 316 | CLANG_CXX_LIBRARY = "libc++"; 317 | CLANG_ENABLE_MODULES = YES; 318 | CLANG_ENABLE_OBJC_ARC = YES; 319 | CLANG_WARN_BOOL_CONVERSION = YES; 320 | CLANG_WARN_CONSTANT_CONVERSION = YES; 321 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 322 | CLANG_WARN_EMPTY_BODY = YES; 323 | CLANG_WARN_ENUM_CONVERSION = YES; 324 | CLANG_WARN_INT_CONVERSION = YES; 325 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 326 | CLANG_WARN_UNREACHABLE_CODE = YES; 327 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 328 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 329 | COPY_PHASE_STRIP = NO; 330 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 331 | ENABLE_NS_ASSERTIONS = NO; 332 | ENABLE_STRICT_OBJC_MSGSEND = YES; 333 | GCC_C_LANGUAGE_STANDARD = gnu99; 334 | GCC_NO_COMMON_BLOCKS = YES; 335 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 336 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 337 | GCC_WARN_UNDECLARED_SELECTOR = YES; 338 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 339 | GCC_WARN_UNUSED_FUNCTION = YES; 340 | GCC_WARN_UNUSED_VARIABLE = YES; 341 | IPHONEOS_DEPLOYMENT_TARGET = 8.4; 342 | MTL_ENABLE_DEBUG_INFO = NO; 343 | SDKROOT = iphoneos; 344 | VALIDATE_PRODUCT = YES; 345 | }; 346 | name = Release; 347 | }; 348 | A9C1B75E1B63295C00A89EFD /* Debug */ = { 349 | isa = XCBuildConfiguration; 350 | buildSettings = { 351 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 352 | INFOPLIST_FILE = MarkNoteParser/Info.plist; 353 | IPHONEOS_DEPLOYMENT_TARGET = 7.1; 354 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 355 | PRODUCT_NAME = "$(TARGET_NAME)"; 356 | }; 357 | name = Debug; 358 | }; 359 | A9C1B75F1B63295C00A89EFD /* Release */ = { 360 | isa = XCBuildConfiguration; 361 | buildSettings = { 362 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 363 | INFOPLIST_FILE = MarkNoteParser/Info.plist; 364 | IPHONEOS_DEPLOYMENT_TARGET = 7.1; 365 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 366 | PRODUCT_NAME = "$(TARGET_NAME)"; 367 | }; 368 | name = Release; 369 | }; 370 | A9C1B7611B63295C00A89EFD /* Debug */ = { 371 | isa = XCBuildConfiguration; 372 | buildSettings = { 373 | BUNDLE_LOADER = "$(TEST_HOST)"; 374 | FRAMEWORK_SEARCH_PATHS = ( 375 | "$(SDKROOT)/Developer/Library/Frameworks", 376 | "$(inherited)", 377 | ); 378 | GCC_PREPROCESSOR_DEFINITIONS = ( 379 | "DEBUG=1", 380 | "$(inherited)", 381 | ); 382 | INFOPLIST_FILE = MarkNoteParserTests/Info.plist; 383 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 384 | PRODUCT_NAME = "$(TARGET_NAME)"; 385 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/MarkNoteParser.app/MarkNoteParser"; 386 | }; 387 | name = Debug; 388 | }; 389 | A9C1B7621B63295C00A89EFD /* Release */ = { 390 | isa = XCBuildConfiguration; 391 | buildSettings = { 392 | BUNDLE_LOADER = "$(TEST_HOST)"; 393 | FRAMEWORK_SEARCH_PATHS = ( 394 | "$(SDKROOT)/Developer/Library/Frameworks", 395 | "$(inherited)", 396 | ); 397 | INFOPLIST_FILE = MarkNoteParserTests/Info.plist; 398 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 399 | PRODUCT_NAME = "$(TARGET_NAME)"; 400 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/MarkNoteParser.app/MarkNoteParser"; 401 | }; 402 | name = Release; 403 | }; 404 | /* End XCBuildConfiguration section */ 405 | 406 | /* Begin XCConfigurationList section */ 407 | A9C1B7391B63295C00A89EFD /* Build configuration list for PBXProject "MarkNoteParser" */ = { 408 | isa = XCConfigurationList; 409 | buildConfigurations = ( 410 | A9C1B75B1B63295C00A89EFD /* Debug */, 411 | A9C1B75C1B63295C00A89EFD /* Release */, 412 | ); 413 | defaultConfigurationIsVisible = 0; 414 | defaultConfigurationName = Release; 415 | }; 416 | A9C1B75D1B63295C00A89EFD /* Build configuration list for PBXNativeTarget "MarkNoteParser" */ = { 417 | isa = XCConfigurationList; 418 | buildConfigurations = ( 419 | A9C1B75E1B63295C00A89EFD /* Debug */, 420 | A9C1B75F1B63295C00A89EFD /* Release */, 421 | ); 422 | defaultConfigurationIsVisible = 0; 423 | defaultConfigurationName = Release; 424 | }; 425 | A9C1B7601B63295C00A89EFD /* Build configuration list for PBXNativeTarget "MarkNoteParserTests" */ = { 426 | isa = XCConfigurationList; 427 | buildConfigurations = ( 428 | A9C1B7611B63295C00A89EFD /* Debug */, 429 | A9C1B7621B63295C00A89EFD /* Release */, 430 | ); 431 | defaultConfigurationIsVisible = 0; 432 | defaultConfigurationName = Release; 433 | }; 434 | /* End XCConfigurationList section */ 435 | }; 436 | rootObject = A9C1B7361B63295C00A89EFD /* Project object */; 437 | } 438 | -------------------------------------------------------------------------------- /MarkNoteParser/MarkNoteParser.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /MarkNoteParser/MarkNoteParser.xcodeproj/project.xcworkspace/xcshareddata/MarkNoteParser.xccheckout: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDESourceControlProjectFavoriteDictionaryKey 6 | 7 | IDESourceControlProjectIdentifier 8 | E5FACBFE-E429-4C26-A35F-8ADB1FD92F92 9 | IDESourceControlProjectName 10 | MarkNoteParser 11 | IDESourceControlProjectOriginsDictionary 12 | 13 | 13482B32E7921A09FAE3E7576EA70FE0A3EF5154 14 | https://marknote%2540aliyun.com:ylbylb%254020YLB@github.com/marknote/MarknoteParser.git 15 | 16 | IDESourceControlProjectPath 17 | MarkNoteParser/MarkNoteParser.xcodeproj 18 | IDESourceControlProjectRelativeInstallPathDictionary 19 | 20 | 13482B32E7921A09FAE3E7576EA70FE0A3EF5154 21 | ../../.. 22 | 23 | IDESourceControlProjectURL 24 | https://marknote%2540aliyun.com:ylbylb%254020YLB@github.com/marknote/MarknoteParser.git 25 | IDESourceControlProjectVersion 26 | 111 27 | IDESourceControlProjectWCCIdentifier 28 | 13482B32E7921A09FAE3E7576EA70FE0A3EF5154 29 | IDESourceControlProjectWCConfigurations 30 | 31 | 32 | IDESourceControlRepositoryExtensionIdentifierKey 33 | public.vcs.git 34 | IDESourceControlWCCIdentifierKey 35 | 13482B32E7921A09FAE3E7576EA70FE0A3EF5154 36 | IDESourceControlWCCName 37 | MarkNoteParser 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /MarkNoteParser/MarkNoteParser.xcodeproj/project.xcworkspace/xcuserdata/bill.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marknote/MarknoteParser/3897f14b98d2eb4a28921dde5ea3a3d38f0e9729/MarkNoteParser/MarkNoteParser.xcodeproj/project.xcworkspace/xcuserdata/bill.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /MarkNoteParser/MarkNoteParser.xcodeproj/xcuserdata/bill.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | -------------------------------------------------------------------------------- /MarkNoteParser/MarkNoteParser.xcodeproj/xcuserdata/bill.xcuserdatad/xcschemes/MarkNoteParser.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 38 | 39 | 44 | 45 | 47 | 53 | 54 | 55 | 56 | 57 | 63 | 64 | 65 | 66 | 75 | 77 | 83 | 84 | 85 | 86 | 87 | 88 | 94 | 96 | 102 | 103 | 104 | 105 | 107 | 108 | 111 | 112 | 113 | -------------------------------------------------------------------------------- /MarkNoteParser/MarkNoteParser.xcodeproj/xcuserdata/bill.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | MarkNoteParser.xcscheme 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | SuppressBuildableAutocreation 14 | 15 | A9C1B73D1B63295C00A89EFD 16 | 17 | primary 18 | 19 | 20 | A9C1B7521B63295C00A89EFD 21 | 22 | primary 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /MarkNoteParser/MarkNoteParser/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | /** 2 | * MarknoteParser- a markdown parser 3 | * Copyright (c) 2015, MarkNote. (MIT Licensed) 4 | * https://github.com/marknote/MarknoteParser 5 | */ 6 | 7 | import UIKit 8 | 9 | @UIApplicationMain 10 | class AppDelegate: UIResponder, UIApplicationDelegate { 11 | 12 | var window: UIWindow? 13 | 14 | 15 | func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool { 16 | // Override point for customization after application launch. 17 | return true 18 | } 19 | 20 | func applicationWillResignActive(application: UIApplication) { 21 | // 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. 22 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. 23 | } 24 | 25 | func applicationDidEnterBackground(application: UIApplication) { 26 | // 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. 27 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 28 | } 29 | 30 | func applicationWillEnterForeground(application: UIApplication) { 31 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. 32 | } 33 | 34 | func applicationDidBecomeActive(application: UIApplication) { 35 | // 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. 36 | } 37 | 38 | func applicationWillTerminate(application: UIApplication) { 39 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 40 | } 41 | 42 | 43 | } 44 | 45 | -------------------------------------------------------------------------------- /MarkNoteParser/MarkNoteParser/Base.lproj/LaunchScreen.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 20 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /MarkNoteParser/MarkNoteParser/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 | -------------------------------------------------------------------------------- /MarkNoteParser/MarkNoteParser/Images.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 | "info" : { 35 | "version" : 1, 36 | "author" : "xcode" 37 | } 38 | } -------------------------------------------------------------------------------- /MarkNoteParser/MarkNoteParser/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | MarkNote.$(PRODUCT_NAME:rfc1034identifier) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UIRequiredDeviceCapabilities 30 | 31 | armv7 32 | 33 | UISupportedInterfaceOrientations 34 | 35 | UIInterfaceOrientationPortrait 36 | UIInterfaceOrientationLandscapeLeft 37 | UIInterfaceOrientationLandscapeRight 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /MarkNoteParser/MarkNoteParser/MarkNoteParser.swift: -------------------------------------------------------------------------------- 1 | /** 2 | * MarknoteParser- a markdown parser 3 | * Copyright (c) 2015, MarkNote. (MIT Licensed) 4 | * https://github.com/marknote/MarknoteParser 5 | */ 6 | 7 | import Foundation 8 | 9 | public class MarkNoteParser: NSObject { 10 | 11 | //var nOldBulletLevel = 0 12 | var nCurrentBulletLevel = 0 13 | var bInTable = false 14 | var output = "" 15 | var isInParagraph = false 16 | var isAfterEmptyLine = false 17 | var tableColsAlignment = [String]() 18 | let headerChar:Character = "#" 19 | var blockEndTags = [String]() 20 | var isCurrentLineNeedBr = true 21 | var arrReferenceInfo = [ReferenceDefinition]() 22 | var arrReferenceUsage = [ReferenceUsageInfo]() 23 | 24 | public static func toHtml(input:String) -> String{ 25 | let instance = MarkNoteParser() 26 | instance.output = "" 27 | instance.parse(input) 28 | return instance.output 29 | } 30 | 31 | 32 | func parse (input:String){ 33 | proceedHTMLTags(input) 34 | proceedReference() 35 | } 36 | 37 | func proceedReference(){ 38 | for refer in self.arrReferenceUsage { 39 | let hitted = arrReferenceInfo.filter{ $0.key.lowercaseString == refer.key.lowercaseString } 40 | if hitted.count > 0 { 41 | let found = hitted[0] 42 | var actual = "" 43 | switch refer.type { 44 | case .Link: 45 | if found.url._title.length > 0 { 46 | actual = "\(refer.title)" 47 | } else { 48 | actual = "\(refer.title)" 49 | } 50 | case .Image: 51 | if found.url._title.length > 0 { 52 | actual = "\"\(refer.title)\"" 53 | 54 | } else { 55 | actual = "\"\(refer.title)\"/" 56 | } 57 | } 58 | output = output.replaceAll(refer.placeHolder(), toStr: actual) 59 | } 60 | } 61 | } 62 | 63 | 64 | func proceedHTMLTags(input:String){ 65 | var currentPos = 0 66 | let tagBegin = input.indexOf("<") 67 | if tagBegin >= 0 { 68 | if tagBegin >= 1 { 69 | proceedNoHtml(input.substring(currentPos, end: tagBegin - 1)) 70 | } 71 | //currentPos = tagBegin 72 | if tagBegin < input.length - 1 { 73 | var left = input.substring(tagBegin, end: input.length - 1) 74 | var endTag = left.indexOf(">") 75 | if endTag > 0 { 76 | // found 77 | if left[endTag - 1] == "/" { 78 | //auto close: 79 | self.output += left.substringToIndex(left.startIndex.advancedBy( endTag + 1)) 80 | if endTag < left.length - 2 { 81 | proceedHTMLTags(left.substringFromIndex(left.startIndex.advancedBy( endTag + 1 ))) 82 | } 83 | } else { 84 | // there is a close tag 85 | currentPos = endTag 86 | if endTag <= left.length - 1 { 87 | left = left.substringFromIndex(left.startIndex.advancedBy( endTag + 1 )) 88 | endTag = left.indexOf(">") 89 | if endTag > 0 { 90 | self.output += input.substring(tagBegin, end: tagBegin + endTag + currentPos + 1) //+ left.substringToIndex(advance(left.startIndex,endTag )) 91 | if endTag < left.length - 1 { 92 | left = left.substringFromIndex(left.startIndex.advancedBy( endTag + 1 )) 93 | proceedHTMLTags(left) 94 | return 95 | } 96 | } else { 97 | proceedNoHtml(input) 98 | return 99 | } 100 | } else { 101 | output += input 102 | return 103 | } 104 | } 105 | }else { 106 | // not found 107 | proceedNoHtml(left) 108 | } 109 | } 110 | }else { 111 | proceedNoHtml(input) 112 | } 113 | } 114 | func proceedNoHtml (input:String){ 115 | let preProceeded = input.replaceAll("\r\n", toStr: "\n").replaceAll("\n", toStr:" \n") 116 | 117 | 118 | //let lines = split(preProceeded){$0 == "\n"} 119 | let lines = preProceeded.componentsSeparatedByString("\n") 120 | var isInCodeBlock:Bool = false 121 | 122 | 123 | //for rawline in lines { 124 | for var i = 0; i < lines.count; i++ { 125 | isCurrentLineNeedBr = true 126 | 127 | let line = lines[i].trim() 128 | 129 | if isInCodeBlock { 130 | if line.indexOf("```") >= 0 { 131 | isInCodeBlock = false 132 | output += "\n" 133 | isCurrentLineNeedBr = false 134 | continue 135 | }else { 136 | output += line.replaceAll("\"", toStr:""") + "\n" 137 | } 138 | } else if bInTable && line.length > 0 { 139 | handleTableLine(line, isHead:false) 140 | } else { 141 | // not in block 142 | if line.length == 0 { 143 | // empty line 144 | closeTags() 145 | closeParagraph() 146 | closeTable() 147 | isAfterEmptyLine = true 148 | isCurrentLineNeedBr = false 149 | continue 150 | }else { 151 | isAfterEmptyLine = false 152 | } 153 | 154 | if line.indexOf("- ") == 0 155 | || line.indexOf("* ") == 0 156 | || line.indexOf("+ ") == 0 { 157 | if self.nCurrentBulletLevel == 0 { 158 | output += "
    \n" 159 | blockEndTags.append("
\n") 160 | self.nCurrentBulletLevel = 1 161 | isCurrentLineNeedBr = false 162 | 163 | } 164 | output += "
  • " 165 | let newline = line.substring("- ".length, end: line.length - 1) 166 | handleLine(newline) 167 | output += "
  • \n" 168 | continue 169 | } else { 170 | if self.nCurrentBulletLevel > 0 { 171 | self.nCurrentBulletLevel = 0 172 | output += "\n" 173 | } 174 | } 175 | 176 | if line.indexOf("```") == 0 { 177 | isInCodeBlock = true 178 | var cssClass = "no-highlight" 179 | if line.length > "```".length { 180 | //prettyprint javascript prettyprinted 181 | let remaining = line.substringFromIndex(line.startIndex.advancedBy( "```".length)) 182 | cssClass = "prettyprint lang-\(remaining)" 183 | } 184 | output += "
    \n"
    185 |                     continue // ignor current line
    186 |                 }
    187 | 
    188 |                 if i + 1 <= lines.count - 1 {
    189 |                     let nextLine = lines[i + 1].trim()
    190 |                     if nextLine.contains3PlusandOnlyChars("="){
    191 |                         output += "

    " + line + "

    \n" 192 | i++ 193 | continue 194 | } else if nextLine.contains3PlusandOnlyChars("-"){ 195 | output += "

    " + line + "

    \n" 196 | i++ 197 | continue 198 | } else if nextLine.indexOf("|") >= 0 199 | && line.indexOf("|") >= 0 200 | && nextLine.replaceAll("|", toStr: "").replaceAll("-", toStr: "").replaceAll(":", toStr: "").replaceAll(" ", toStr: "").length == 0 201 | { 202 | 203 | beginTable(nextLine) 204 | handleTableLine(line, isHead:true) 205 | i++ 206 | continue 207 | } 208 | } 209 | 210 | 211 | handleLine(line) 212 | if isCurrentLineNeedBr 213 | && lines[i].length >= 2 214 | && lines[i].substringFromIndex(lines[i].startIndex.advancedBy( lines[i].length - 2)) == " " { 215 | output += "
    " 216 | } 217 | 218 | //output += "

    " 219 | } 220 | }//end for 221 | closeTags() 222 | closeParagraph() 223 | 224 | } 225 | 226 | func handleTableLine(rawline:String, isHead:Bool) { 227 | 228 | let cols = rawline.characters.split{$0 == "|"}.map { String($0) } 229 | output += "" 230 | var i = 0 231 | 232 | for col in cols { 233 | let colAlign = self.tableColsAlignment[i] 234 | if isHead { 235 | output += colAlign.length > 0 ? "" : "" 236 | parseInLine(col) 237 | output += "" 238 | } else { 239 | output += colAlign.length > 0 ? "" : "" 240 | parseInLine(col) 241 | output += "" 242 | } 243 | i++ 244 | } 245 | output += "" 246 | } 247 | 248 | func beginTable(alignmentLine: String){ 249 | if !bInTable { 250 | bInTable = true 251 | output += "" 252 | self.tableColsAlignment.removeAll(keepCapacity: false) 253 | let arr = alignmentLine.trim().componentsSeparatedByString("|") 254 | for col in arr { 255 | if col.indexOf(":-") >= 0 && col.indexOf("-:") > 0 { 256 | self.tableColsAlignment.append("style=\"text-align: center;\"") 257 | }else if col.indexOf("-:") > 0{ 258 | self.tableColsAlignment.append("style=\"text-align: right;\"") 259 | }else { 260 | self.tableColsAlignment.append("") 261 | } 262 | } 263 | } 264 | } 265 | func closeTable(){ 266 | if bInTable { 267 | bInTable = false 268 | output += "
    " 269 | } 270 | } 271 | func closeTags(){ 272 | for var i = blockEndTags.count - 1; i >= 0; i-- { 273 | output += blockEndTags[i] 274 | //blockEndTags.removeAtIndex(i) 275 | } 276 | blockEndTags.removeAll(keepCapacity: false) 277 | } 278 | 279 | func closeParagraph () { 280 | if isInParagraph { 281 | isInParagraph = false 282 | output += "

    \n" 283 | } 284 | } 285 | 286 | func beginParagraph(){ 287 | if !isInParagraph { 288 | isInParagraph = true 289 | output += "

    " 290 | } 291 | } 292 | 293 | 294 | 295 | func calculateHeadLevel(line:String)->Int{ 296 | var nFindHead = 0 297 | var pos: String.Index = line.startIndex 298 | for var i = 0; i <= 6 && i < line.characters.count; i++ { 299 | pos = line.startIndex.advancedBy( i) 300 | if line[pos] == headerChar { 301 | nFindHead = i + 1 302 | } else { 303 | break; 304 | } 305 | } 306 | return nFindHead 307 | } 308 | 309 | func handleLine(rawline:String) { 310 | 311 | if rawline.contains3PlusandOnlyChars("-") 312 | || rawline.contains3PlusandOnlyChars("*") 313 | || rawline.contains3PlusandOnlyChars("_"){ 314 | closeParagraph() 315 | output += "


    \n" 316 | return 317 | } 318 | var line = rawline 319 | var endTags = [String]() 320 | 321 | var pos: String.Index = line.startIndex 322 | 323 | if line[0] == ">" { 324 | output += "
    " 325 | line = line.substringFromIndex(line.startIndex.advancedBy( ">".length)) 326 | endTags.append("
    ") 327 | } 328 | 329 | let nFindHead = calculateHeadLevel(line) 330 | if (nFindHead > 0) { 331 | isCurrentLineNeedBr = false 332 | 333 | output += "" 334 | endTags.append("") 335 | pos = pos.advancedBy( nFindHead) 336 | } else { 337 | beginParagraph() 338 | } 339 | 340 | //line = this.handleImage(line, sb) 341 | 342 | let remaining = line.substringFromIndex(pos).trim() 343 | parseInLine(remaining) 344 | //output += "\n" 345 | 346 | for var i = endTags.count - 1; i >= 0; i-- { 347 | output += endTags[i] 348 | } 349 | 350 | //output += "\n" 351 | 352 | } 353 | 354 | func parseInLine(line: String) { 355 | let len = line.length 356 | let start = line.startIndex 357 | for var i = 0; i < len ; i++ { 358 | let ch:Character = line[start.advancedBy( i)] 359 | 360 | switch ch { 361 | case "*","_","~": 362 | if (i + 1 > len - 1) { 363 | output.append(ch) 364 | return 365 | } 366 | var strong = "strong" 367 | if ch == "~" { 368 | strong = "del" 369 | } 370 | if line[start.advancedBy( i + 1)] == ch { 371 | //possible ** 372 | let remaining = line.substringFromIndex(start.advancedBy( i + 2)) 373 | i += scanClosedChar(MarkNoteParser.charArray(ch, len: 2),inStr: remaining,tag: strong) + 1 374 | } else { 375 | let remaining = line.substringFromIndex(start.advancedBy( i + 1)) 376 | i += scanClosedChar("\(ch)",inStr: remaining,tag: "em") 377 | } 378 | case "`": 379 | let remaining = line.substringFromIndex(start.advancedBy( i + 1)) 380 | i += scanClosedChar("`",inStr: remaining,tag: "code") 381 | isCurrentLineNeedBr = false 382 | 383 | case "!": 384 | if i >= line.length - 1 || line[start.advancedBy( i + 1)] != "[" { 385 | output.append(ch) 386 | continue 387 | } 388 | i++ 389 | let remaining = line.substringFromIndex(start.advancedBy( i + 1)) 390 | let posArray = MarkNoteParser.detectPositions(["]","(",")"],inStr: remaining) 391 | if posArray.count == 3 { 392 | let img = ImageTag() 393 | img.alt = line.substring(i + 1, end: i + 1 + posArray[0] - 1) 394 | img.url = URLTag(url: line.substring( i + 1 + posArray[1] + 1, end: i + 1 + posArray[2] - 1) 395 | ) 396 | output += img.toHtml() 397 | i += posArray[2] + 1 398 | }else { 399 | // check image reference defintion 400 | let posArray2 = MarkNoteParser.detectPositions(["]","[","]"],inStr: remaining) 401 | if posArray2.count == 3 { 402 | //is reference usage 403 | let title = line.substring(i + 1, end: i + 1 + posArray2[0] - 1) 404 | let url = line.substring( i + 1 + posArray2[1] + 1, end: i + 1 + posArray2[2] - 1) 405 | let refer = ReferenceUsageInfo() 406 | refer.type = .Image 407 | refer.key = url.lowercaseString 408 | refer.title = title 409 | self.arrReferenceUsage.append(refer) 410 | output += refer.placeHolder() 411 | i += posArray2[2] + 1 + 1 412 | } 413 | } 414 | 415 | case "[": 416 | let remaining = line.substringFromIndex(start.advancedBy( i + 1)) 417 | let posArray = MarkNoteParser.detectPositions(["]","(",")"],inStr: remaining) 418 | if posArray.count == 3 { 419 | let link = LinkTag() 420 | link.text = line.substring(i + 1, end: i + 1 + posArray[0] - 1) 421 | link.url = URLTag(url: line.substring( i + 1 + posArray[1] + 1, end: i + 1 + posArray[2] - 1)) 422 | output += link.toHtml() 423 | i += posArray[2] + 1 424 | }else { 425 | // check reference defintion 426 | let pos = remaining.indexOf("]:") 427 | if pos > 0 && pos < remaining.length - "]:".length { 428 | // is reference definition 429 | let info = ReferenceDefinition() 430 | info.key = remaining.substringToIndex(remaining.startIndex.advancedBy( pos )) 431 | let remaining2 = remaining.substringFromIndex(remaining.startIndex.advancedBy( pos + "]:".length )) 432 | info.url = URLTag(url: remaining2) 433 | self.arrReferenceInfo.append(info) 434 | i += pos + "]:".length + remaining2.length 435 | } else { 436 | let posArray2 = MarkNoteParser.detectPositions(["]","[","]"],inStr: remaining) 437 | if posArray2.count == 3 { 438 | //is reference usage 439 | let title = line.substring(i + 1, end: i + 1 + posArray2[0] - 1) 440 | let url = line.substring( i + 1 + posArray2[1] + 1, end: i + 1 + posArray2[2] - 1) 441 | let refer = ReferenceUsageInfo() 442 | refer.type = .Link 443 | refer.key = url.lowercaseString 444 | refer.title = title 445 | self.arrReferenceUsage.append(refer) 446 | output += refer.placeHolder() 447 | i += pos + posArray2[2] + 1 + 1 448 | } 449 | } 450 | } 451 | case "\"": 452 | output += """ 453 | default: 454 | //do nothing 455 | output.append(ch) 456 | } 457 | } 458 | } 459 | 460 | public static func charArray(ch:Character, len:Int)->String{ 461 | var str = "" 462 | for var i = 0 ; i < len ; i++ { 463 | str.append(ch) 464 | } 465 | return str 466 | } 467 | 468 | public static func detectPositions(toFind:[String],inStr:String )->[Int]{ 469 | var posArray = [Int]() 470 | let count = toFind.count 471 | var lastPos = 0 472 | for var i = 0; i < count ; i++ { 473 | let pos = inStr.substringFromIndex(inStr.startIndex.advancedBy( lastPos)).indexOf(toFind[i]) 474 | lastPos += pos 475 | if pos >= 0 { 476 | posArray.append(lastPos) 477 | }else { 478 | return posArray 479 | } 480 | } 481 | return posArray 482 | } 483 | 484 | func scanClosedChar(ch:String, inStr:String,tag:String) -> Int { 485 | let pos = inStr.indexOf(ch) 486 | if pos > 0 { 487 | output += "<\(tag)>" + inStr.substringToIndex(inStr.startIndex.advancedBy( pos )) + "" 488 | } else { 489 | output += ch 490 | } 491 | return pos + ch.length 492 | } 493 | public class func splitStringWithMidSpace(input: String) -> [String]{ 494 | var array = [String]() 495 | let trimmed = input.trim() 496 | let pos = trimmed.indexOf(" ") 497 | if pos > 0 { 498 | array.append(trimmed.substringToIndex(trimmed.startIndex.advancedBy( pos))) 499 | array.append(trimmed.substringFromIndex(trimmed.startIndex.advancedBy( pos + 1))) 500 | } else { 501 | array.append(trimmed) 502 | } 503 | return array 504 | } 505 | 506 | } 507 | enum ReferenceType{ 508 | case Link 509 | case Image 510 | } 511 | 512 | class URLTag: NSObject { 513 | var _url = "" 514 | var _title = "" 515 | init(url:String){ 516 | let trimmed = url.trim() 517 | //let posSpace = trimmed.indexOf(" ") 518 | let arr = MarkNoteParser.splitStringWithMidSpace(trimmed) 519 | if arr.count > 1 { 520 | _url = arr[0].lowercaseString 521 | _title = arr[1].replaceAll("\"", toStr: "") 522 | } else { 523 | _url = arr[0].lowercaseString 524 | } 525 | 526 | } 527 | override var description: String { 528 | return _url 529 | } 530 | } 531 | 532 | class LinkTag { 533 | var text = "" 534 | var url = URLTag(url:"") 535 | func toHtml()-> String{ 536 | if url._title.length > 0 { 537 | return "" + text + "" 538 | } else { 539 | return "" + text + "" 540 | } 541 | } 542 | } 543 | 544 | class ImageTag{ 545 | 546 | var alt = "" 547 | var url = URLTag(url:"") 548 | func toHtml()-> String{ 549 | if url._title.length > 0 { 550 | return "" 551 | } else { 552 | return "\"\(alt)\"" 553 | } 554 | } 555 | } 556 | 557 | class ReferenceDefinition { 558 | var key = "" 559 | var url = URLTag(url:"") 560 | } 561 | class ReferenceUsageInfo{ 562 | var title = "" 563 | var key = "" 564 | var type = ReferenceType.Link 565 | func placeHolder() -> String{ 566 | return "ReferenceUsageInfo\(key)\(title)" 567 | } 568 | } 569 | -------------------------------------------------------------------------------- /MarkNoteParser/MarkNoteParser/ViewController.swift: -------------------------------------------------------------------------------- 1 | /** 2 | * MarknoteParser- a markdown parser 3 | * Copyright (c) 2015, MarkNote. (MIT Licensed) 4 | * https://github.com/marknote/MarknoteParser 5 | */ 6 | 7 | import UIKit 8 | 9 | class ViewController: UIViewController { 10 | 11 | override func viewDidLoad() { 12 | super.viewDidLoad() 13 | // Do any additional setup after loading the view, typically from a nib. 14 | } 15 | 16 | override func didReceiveMemoryWarning() { 17 | super.didReceiveMemoryWarning() 18 | // Dispose of any resources that can be recreated. 19 | } 20 | 21 | 22 | } 23 | 24 | -------------------------------------------------------------------------------- /MarkNoteParser/MarkNoteParser/util/StringExtensions.swift: -------------------------------------------------------------------------------- 1 | 2 | 3 | import Foundation 4 | 5 | extension String { 6 | 7 | //MARK: Helper methods 8 | 9 | /** 10 | Returns the length of the string. 11 | 12 | :returns: Int length of the string. 13 | */ 14 | 15 | var length: Int { 16 | return self.characters.count//count(self) 17 | } 18 | 19 | 20 | 21 | //MARK: - Linguistics 22 | 23 | /** 24 | Returns the langauge of a String 25 | 26 | NOTE: String has to be at least 4 characters, otherwise the method will return nil. 27 | 28 | :returns: String! Returns a string representing the langague of the string (e.g. en, fr, or und for undefined). 29 | */ 30 | func detectLanguage() -> String? { 31 | if self.length > 4 { 32 | let tagger = NSLinguisticTagger(tagSchemes:[NSLinguisticTagSchemeLanguage], options: 0) 33 | tagger.string = self 34 | return tagger.tagAtIndex(0, scheme: NSLinguisticTagSchemeLanguage, tokenRange: nil, sentenceRange: nil) 35 | } 36 | return nil 37 | } 38 | 39 | /** 40 | Returns the script of a String 41 | 42 | :returns: String! returns a string representing the script of the String (e.g. Latn, Hans). 43 | */ 44 | func detectScript() -> String? { 45 | if self.length > 1 { 46 | let tagger = NSLinguisticTagger(tagSchemes:[NSLinguisticTagSchemeScript], options: 0) 47 | tagger.string = self 48 | return tagger.tagAtIndex(0, scheme: NSLinguisticTagSchemeScript, tokenRange: nil, sentenceRange: nil) 49 | } 50 | return nil 51 | } 52 | 53 | /** 54 | Check the text direction of a given String. 55 | 56 | NOTE: String has to be at least 4 characters, otherwise the method will return false. 57 | 58 | :returns: Bool The Bool will return true if the string was writting in a right to left langague (e.g. Arabic, Hebrew) 59 | 60 | */ 61 | var isRightToLeft : Bool { 62 | let language = self.detectLanguage() 63 | return (language == "ar" || language == "he") 64 | } 65 | 66 | 67 | //MARK: - Usablity & Social 68 | 69 | /** 70 | Check that a String is only made of white spaces, and new line characters. 71 | 72 | :returns: Bool 73 | */ 74 | func isOnlyEmptySpacesAndNewLineCharacters() -> Bool { 75 | return self.stringByTrimmingCharactersInSet(NSCharacterSet.whitespaceAndNewlineCharacterSet()).length == 0 76 | } 77 | 78 | 79 | 80 | 81 | /** 82 | :returns: Base64 encoded string 83 | */ 84 | func encodeToBase64Encoding() -> String { 85 | let utf8str = self.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)! 86 | return utf8str.base64EncodedStringWithOptions(NSDataBase64EncodingOptions.Encoding64CharacterLineLength) 87 | } 88 | 89 | /** 90 | :returns: Decoded Base64 string 91 | */ 92 | func decodeFromBase64Encoding() -> String { 93 | /*let base64data = NSData(base64EncodedString: self, options: NSDataBase64DecodingOptions.IgnoreUnknownCharacters) 94 | return NSString(data: base64data!, encoding: NSUTF8StringEncoding)! as String*/ 95 | /*let base64Decoded = NSData(base64EncodedString: self, options: NSDataBase64DecodingOptions(rawValue: 0)) 96 | .map({ NSString(data: $0, encoding: NSUTF8StringEncoding) }) 97 | return base64Decoded as! String*/ 98 | 99 | let decodedData = NSData(base64EncodedString: self, options: NSDataBase64DecodingOptions(rawValue: 0)) 100 | return NSString(data: decodedData!, encoding: NSUTF8StringEncoding)! as String 101 | 102 | } 103 | 104 | func contains (substring:String)->Bool{ 105 | return self.rangeOfString(substring) != nil 106 | } 107 | 108 | 109 | 110 | // MARK: Subscript Methods 111 | 112 | /*subscript (i: Int) -> String { 113 | //return String(Array(self)[i]) 114 | return self[advance(self.startIndex, i)] 115 | }*/ 116 | subscript (i: Int) -> Character { 117 | return self[self.startIndex.advancedBy( i)] 118 | } 119 | 120 | subscript (i: Int) -> String { 121 | return String(self[i] as Character) 122 | } 123 | 124 | subscript (r: Range) -> String { 125 | return substringWithRange( 126 | Range(start: startIndex.advancedBy( r.startIndex), 127 | end: startIndex.advancedBy( r.endIndex) 128 | ) 129 | ) 130 | } 131 | 132 | 133 | 134 | subscript (range: NSRange) -> String { 135 | let end = range.location + range.length 136 | return self[Range(start: range.location, end: end)] 137 | } 138 | 139 | subscript (substring: String) -> Range? { 140 | return rangeOfString(substring, options: NSStringCompareOptions.LiteralSearch, range: Range(start: startIndex, end: endIndex), locale: NSLocale.currentLocale()) 141 | } 142 | 143 | func substring(begin:Int, end:Int)->String{ 144 | let range:NSRange = NSMakeRange(begin, end - begin + 1 ) 145 | return self[range] 146 | } 147 | 148 | 149 | func stringByDecodingURLFormat() ->String { 150 | return self.stringByReplacingOccurrencesOfString("+", withString: " ", options: NSStringCompareOptions.LiteralSearch, range: nil) 151 | .stringByRemovingPercentEncoding! 152 | //.stringByReplacingPercentEscapesUsingEncoding(NSUTF8StringEncoding)! 153 | 154 | } 155 | 156 | func toBool() -> Bool? { 157 | switch self { 158 | case "True", "true", "yes", "1": 159 | return true 160 | case "False", "false", "no", "0": 161 | return false 162 | default: 163 | return nil 164 | } 165 | } 166 | 167 | func trim() ->String{ 168 | return self.stringByTrimmingCharactersInSet(NSCharacterSet.whitespaceCharacterSet()) 169 | 170 | } 171 | 172 | func indexOf(toFind:String)->Int{ 173 | 174 | if let range = self.rangeOfString(toFind){ 175 | 176 | return self.startIndex.distanceTo( range.startIndex) 177 | } else { 178 | return -1 179 | } 180 | } 181 | 182 | func contains3PlusandOnlyChars( char:String)-> Bool{ 183 | return self.length >= 3 184 | && self.indexOf(char) == 0 185 | && self.stringByReplacingOccurrencesOfString(char, withString: "", options: NSStringCompareOptions.LiteralSearch, range: nil).length == 0 186 | } 187 | 188 | func replaceAll(target:String, toStr: String)->String{ 189 | return self.stringByReplacingOccurrencesOfString(target, withString: toStr, options: NSStringCompareOptions.LiteralSearch, range: nil) 190 | } 191 | } -------------------------------------------------------------------------------- /MarkNoteParser/MarkNoteParserTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | MarkNote.$(PRODUCT_NAME:rfc1034identifier) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /MarkNoteParser/MarkNoteParserTests/MarkNoteParserTests.swift: -------------------------------------------------------------------------------- 1 | /** 2 | * MarknoteParser- a markdown parser 3 | * Copyright (c) 2015, MarkNote. (MIT Licensed) 4 | * https://github.com/marknote/MarknoteParser 5 | */ 6 | 7 | import UIKit 8 | import XCTest 9 | import MarkNoteParser 10 | 11 | class MarkNoteParserTests: XCTestCase { 12 | 13 | override func setUp() { 14 | super.setUp() 15 | // Put setup code here. This method is called before the invocation of each test method in the class. 16 | } 17 | 18 | override func tearDown() { 19 | // Put teardown code here. This method is called after the invocation of each test method in the class. 20 | super.tearDown() 21 | } 22 | 23 | func markdown(input :String)->String{ 24 | let result = MarkNoteParser.toHtml(input) 25 | print("input: \(input) result:\(result)") 26 | return result 27 | } 28 | 29 | /*func assertHtmlEauql(expected:String, actual:String){ 30 | return assertHtmlEauql(expected, actual,"") 31 | }*/ 32 | 33 | func assertHtmlEauql(expected:String, _ actual:String, _ message:String = ""){ 34 | return XCTAssertEqual(expected.stringByReplacingOccurrencesOfString("\n", withString: "", options: NSStringCompareOptions.LiteralSearch, range: nil), 35 | actual.stringByReplacingOccurrencesOfString("\n", withString: "", options: NSStringCompareOptions.LiteralSearch, range: nil), 36 | message) 37 | } 38 | 39 | func testHeading() { 40 | assertHtmlEauql("

    Hello

    ", markdown("# Hello"), "H1 Heading Pass") 41 | assertHtmlEauql("

    Hello

    ", markdown("## Hello"), "H2 Heading Pass") 42 | assertHtmlEauql("

    Hello

    ", markdown("### Hello"), "H3 Heading Pass") 43 | assertHtmlEauql("

    Hello

    ", markdown("#### Hello"), "H4 Heading Pass") 44 | assertHtmlEauql("
    Hello
    ", markdown("##### Hello"), "H5 Heading Pass") 45 | assertHtmlEauql("
    Hello
    ", markdown("###### Hello"), "H6 Heading Pass") 46 | } 47 | 48 | 49 | func testFencedCode() { 50 | assertHtmlEauql("
    println("Hello")\n
    \n", markdown("```swift\nprintln(\"Hello\")\n```"), "Fenced Code Pass") 51 | } 52 | 53 | func testDefLinks() { 54 | assertHtmlEauql("

    Title

    ", markdown("[Title][Google]\n [Google]:www.google.com\n"), "Deflink no title Pass") 55 | assertHtmlEauql("

    text

    ", markdown("[text][Google]\n[Google]:www.google.com \"GoogleSearch\"\n"), "Deflink no title Pass") 56 | } 57 | 58 | func testDefImages() { 59 | assertHtmlEauql("

    \"Title\"/

    ", markdown("![Title][image]\n [image]:aaa\n"), "Deflink no title Pass") 60 | assertHtmlEauql("

    \"text\"

    ", markdown("![text][image]\n[image]:aaa \"TTTT\"\n"), "Deflink no title Pass") 61 | assertHtmlEauql("

    \"alt

    ", markdown("![alt text][logo]\n[logo]: https://github.com/adam-p/markdown-here/raw/master/src/common/images/icon48.png \"Logo Title Text\""), "Deflink no title Pass") 62 | } 63 | 64 | 65 | 66 | func testInlineLinks() { 67 | assertHtmlEauql("

    Google

    \n", markdown("[Google](www.google.com)"), "inline link Pass") 68 | assertHtmlEauql("

    Google

    \n", markdown("[Google](www.google.com \"googlehome\")"), "inline link Pass") 69 | 70 | } 71 | 72 | func testInlineImages() { 73 | assertHtmlEauql("

    \"abc\"

    \n", markdown("![abc](url)"), "inline image Pass") 74 | 75 | } 76 | 77 | func testInlineImages2() { 78 | assertHtmlEauql("

    !\"abc\"

    \n", markdown("!![abc](url)"), "inline image Pass") 79 | 80 | } 81 | 82 | func testHRule() { 83 | assertHtmlEauql("
    \n", markdown("-----"), "HRule dashes Pass") 84 | assertHtmlEauql("
    \n", markdown("***"), "HRule asterisks Pass") 85 | assertHtmlEauql("
    \n", markdown("___"), "HRule underscope Pass") 86 | } 87 | 88 | func testLHeading() { 89 | assertHtmlEauql("

    Hello

    \n", markdown("Hello\n====="), "H1 LHeading Pass") 90 | assertHtmlEauql("

    Hello

    \n", markdown("Hello\n-----"), "H2 LHeading Pass") 91 | } 92 | 93 | func testBlockQuote() { 94 | assertHtmlEauql("

    Hello

    ", markdown(">### Hello"), "HRule dashes Pass") 95 | } 96 | 97 | func testInlineCode() { 98 | assertHtmlEauql("

    Hello

    \n", markdown("`Hello`\n"), "InlineCode Pass") 99 | } 100 | 101 | func testBlockCode() { 102 | assertHtmlEauql("
    \nHello\n
    \n", markdown("``` \r\nHello\r\n```\n"), "BlockCode Pass") 103 | } 104 | 105 | func testDoubleEmphasis() { 106 | assertHtmlEauql("

    Hello

    \n", markdown("**Hello**"), "Double Emphasis Asterisk Pass") 107 | assertHtmlEauql("

    World

    \n", markdown("__World__"), "Double Emphasis Underscope Pass") 108 | assertHtmlEauql("

    Hello

    \n", markdown("~~Hello~~"), "Double Emphasis Asterisk Pass") 109 | 110 | } 111 | 112 | func testDoubleEmphasis2() { 113 | assertHtmlEauql("

    123Hello456

    \n", markdown("123**Hello**456"), "Double Emphasis Asterisk Pass") 114 | assertHtmlEauql("

    123World456

    \n", markdown("123__World__456"), "Double Emphasis Underscope Pass") 115 | } 116 | 117 | 118 | func testEmphasis() { 119 | assertHtmlEauql("

    Hello

    \n", markdown("*Hello*"), "Emphasis Asterisk Pass") 120 | assertHtmlEauql("

    World

    \n", markdown("_World_"), "Emphasis Underscope Pass") 121 | assertHtmlEauql("

    123Hello456

    \n", markdown("123*Hello*456"), "Emphasis Asterisk Pass") 122 | assertHtmlEauql("

    123World456

    \n", markdown("123_World_456"), "Emphasis Underscope Pass") 123 | assertHtmlEauql("

    123Hello456123world456

    \n", markdown("123*Hello*456123*world*456"), "Emphasis Asterisk Pass") 124 | assertHtmlEauql("

    123World456123world456

    \n", markdown("123_World_456123*world*456"), "Emphasis Underscope Pass") 125 | } 126 | 127 | func testBulletList() 128 | { 129 | let input = "A bulleted list:\n- a\n- b\n- c\n" 130 | let expected = "

    A bulleted list:

    • a
    • b
    • c

    " 131 | let actual = markdown(input).stringByReplacingOccurrencesOfString("\n", withString: "", options: NSStringCompareOptions.LiteralSearch, range: nil) 132 | assertHtmlEauql(expected, actual) 133 | } 134 | 135 | 136 | func testDetectPositions() { 137 | let expected = [1,2,3] 138 | let actual = MarkNoteParser.detectPositions(["1","2","3"],inStr:"0123") 139 | XCTAssertEqual(expected, actual) 140 | 141 | } 142 | 143 | func testDetectPositions2() { 144 | let expected = [2,4,5] 145 | let actual = MarkNoteParser.detectPositions(["2","4","5"],inStr:"012345") 146 | XCTAssertEqual(expected, actual) 147 | 148 | } 149 | 150 | func testHTMLTag(){ 151 | 152 | let input = "" 153 | let expected = "" 154 | let actual = markdown(input).stringByReplacingOccurrencesOfString("\n", withString: "", options: NSStringCompareOptions.LiteralSearch, range: nil) 155 | assertHtmlEauql(expected, actual) 156 | } 157 | 158 | func testMixedHTMLTag(){ 159 | 160 | let input = "\n## Inline HTML\nYou can also use raw HTML in your Markdown" 161 | let expected = "

    Inline HTML

    You can also use raw HTML in your Markdown

    " 162 | let actual = markdown(input).stringByReplacingOccurrencesOfString("\n", withString: "", options: NSStringCompareOptions.LiteralSearch, range: nil) 163 | assertHtmlEauql(expected, actual) 164 | } 165 | 166 | func testHTMLTag2(){ 167 | 168 | let input = "111
    123222" 169 | let expected = "

    111

    123

    222

    " 170 | let actual = markdown(input).stringByReplacingOccurrencesOfString("\n", withString: "", options: NSStringCompareOptions.LiteralSearch, range: nil) 171 | assertHtmlEauql(expected, actual) 172 | } 173 | 174 | func testEmbeddedHTML(){ 175 | let input = "Don't modify this note. Your changes will be overrided." 176 | let expected = "Don't modify this note. Your changes will be overrided." 177 | let actual = markdown(input).stringByReplacingOccurrencesOfString("\n", withString: "", options: NSStringCompareOptions.LiteralSearch, range: nil) 178 | assertHtmlEauql(expected, actual) 179 | 180 | } 181 | 182 | func testHTMLInCode(){ 183 | 184 | let input = "```\n<html>\n```\n" 185 | let expected = "
    <html>
    " 186 | let actual = markdown(input).stringByReplacingOccurrencesOfString("\n", withString: "", options: NSStringCompareOptions.LiteralSearch, range: nil) 187 | assertHtmlEauql(expected, actual) 188 | } 189 | 190 | func testNewLine(){ 191 | 192 | let input = "abc \n123" 193 | let expected = "

    abc
    123

    " 194 | let actual = markdown(input) 195 | assertHtmlEauql(expected, actual) 196 | } 197 | 198 | func testTable(){ 199 | 200 | let input = "|a|b|c|\n|------|-----|-----|\n|1|2|3|\n\n\n" 201 | let expected = "
    abc
    123
    " 202 | let actual = markdown(input) 203 | assertHtmlEauql(expected, actual) 204 | } 205 | 206 | func testTableWithoutOuterPipe(){ 207 | 208 | let input = "a|b|c\n------|-----|-----\n1|2|3\n\n\n" 209 | let expected = "
    abc
    123
    " 210 | let actual = markdown(input) 211 | assertHtmlEauql(expected, actual) 212 | } 213 | 214 | func testTableWithColumnAignment(){ 215 | 216 | let input = "a|b|c\n------|:-----:|-----:\n1|2|3\n\n\n" 217 | let expected = "
    abc
    123
    " 218 | let actual = markdown(input) 219 | assertHtmlEauql(expected, actual) 220 | } 221 | 222 | 223 | func testSplitEmptyLiens(){ 224 | XCTAssertEqual(["1","","3"],"1\n\n3".componentsSeparatedByString("\n")) 225 | XCTAssertEqual(["A bulleted list:","- a","- b","- c",""],"A bulleted list:\n- a\n- b\n- c\n".componentsSeparatedByString("\n")) 226 | 227 | } 228 | 229 | func testSplitStringWithMidSpace(){ 230 | XCTAssertEqual(["1","2"], MarkNoteParser.splitStringWithMidSpace("1 2") ) 231 | } 232 | } 233 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MarkNote Parser 2 | 3 | 4 | Objective-C version: https://github.com/marknote/MarkNoteParserObjC 5 | 6 | Swift version: https://github.com/marknote/MarknoteParser 7 | 8 | 9 | A dead simple markdown parser implemented in both **Swift** and **Objective-C** with performance in mind, which can help you to transform markdown code into HTML. 10 | Most of markdown parsers highly depend on regular expression while MarkNote Parser avoids doing so. 11 | 12 | ## Purpose 13 | 14 | At the beginning my app [MarkNote](https://itunes.apple.com/us/app/marknote/id991297585?ls=1&mt=8) was using [marked](https://github.com/chjj/marked) to render markdown as HTML. 15 | After trying to find a relevant markdown parser in Swfit/Object-c while no luck, I decided to build my own. 16 | 17 | 18 | ## Usage 19 | 20 | ### Using swift version 21 | - Cope 2 files into your project: 22 | -- **StringExtensions.swift** , extension of String class; 23 | -- **MarkNoteParser.swift**, the parser class; 24 | 25 | - Use the method MarkNoteParser.toHtml to convert markdown text to HTML string, like this: 26 | 27 | ```swift 28 | func markdown(input :String)->String{ 29 | let result = MarkNoteParser.toHtml(input) 30 | println("input: \(input) result:\(result)") 31 | return result 32 | } 33 | ``` 34 | ### Using objetive-c version 35 | - Cope all files under "MarkNoteParserOC" folder into your project, and import the header file like this 36 | 37 | ```objective-c 38 | #import "MarkNoteParser.h" 39 | ``` 40 | - Then you can call MarkNoteParser to parse your markdown document: 41 | 42 | ```objective-c 43 | NSString* result = [MarkNoteParser toHtml:input]; 44 | return result; 45 | ``` 46 | 47 | ## Features 48 | 49 | ### headers 50 | 51 | ```markdown 52 | # H1 53 | ## H2 54 | ### H3 55 | ``` 56 | will be transformed into: 57 | 58 | ```html 59 |

    H1

    H2

    H3

    60 | ``` 61 | 62 | ### Emphasis 63 | 64 | ```markdown 65 | Emphasis, aka italics, with *asterisks* or _underscores_. 66 | Strong emphasis, aka bold, with **asterisks** or __underscores__. 67 | Strikethrough uses two tildes. ~~Scratch this.~~ 68 | ``` 69 | will be transformed into: 70 | 71 | ```html 72 |

    Emphasis, aka italics, with asterisks or underscores.

    73 |

    Strong emphasis, aka bold, with asterisks or underscores.

    74 |

    Strikethrough uses two tildes. Scratch this.

    75 | ``` 76 | 77 | ### Links 78 | 79 | ```markdown 80 | [I'm an inline-style link](https://www.google.com) 81 | [I'm an inline-style link with title](https://www.google.com "Google's Homepage") 82 | ``` 83 | 84 | will be transformed into: 85 | 86 | ```html 87 |

    I'm an inline-style link

    88 |

    I'm an inline-style link with title

    89 | ``` 90 | ### Images 91 | 92 | ```markdown 93 | ![alt text](https://avatars3.githubusercontent.com/u/12975088?v=3&s=40 "Logo Title") 94 | ``` 95 | will be transformed into: 96 | ```html 97 | alt text 98 | ``` 99 | ### Code 100 | 101 |
    102 | ```
    103 | var s = "JavaScript syntax highlighting";
    104 | alert(s);
    105 | ```
    106 | 
    107 | 108 | will be transformed into: 109 | ```html 110 |
    111 | var s = "JavaScript syntax highlighting";
    112 | alert(s);
    113 | 
    114 | ``` 115 | 116 | ### Table 117 | 118 | ```markdown 119 | | Tables | Are | Cool | 120 | | ------------- |:-------------:| -----:| 121 | | col 3 is | right-aligned | $1600 | 122 | | col 2 is | centered | $12 | 123 | | zebra stripes | are neat | $1 | 124 | ``` 125 | 126 | will be transformed into: 127 | 128 | ```html 129 |
    Tables Are Cool
    col 3 is right-aligned $1600
    col 2 is centered $12
    zebra stripes are neat $1

    The outer pipes (|) are optional, and you don't need to make the raw Markdown line up prettily. You can also use inline Markdown.

    130 | ``` 131 | 132 | 133 | ## Feedback 134 | 135 | If you have any suggestion or feedback, feel free to drop me a message or follow me on [twitter @markmarknote](https://twitter.com/markmarknote) --------------------------------------------------------------------------------