Skip to content

Latest commit

 

History

History
167 lines (121 loc) · 4.49 KB

README.md

File metadata and controls

167 lines (121 loc) · 4.49 KB

LLRegex

Build Status CocoaPods Compatible Platform

Regular expression library in Swift, wrapping NSRegularExpression. Don't hesitate to try out on playground.

Features

  • Value Semantics
  • Enumerates matches with Sequence
  • Named capture group (unavailable on iOS 8)
  • Range supported (NSRange eliminated)
  • Regex Options, Match Options
  • Find & Replace with flexibility
  • String matching, replacing, splitting

Communication

  • If you found a bug, open an issue, typically with related pattern.
  • If you have a feature request, open an issue.

Requirements

  • iOS 8.0+ / macOS 10.10+ / tvOS 9.0+ / watchOS 2.0+
  • Xcode 8.1+
  • Swift 3.2

Installation

CocoaPods

pod 'LLRegex', '~> 1.3'

Swift Package Manager

dependencies: [
    .Package(url: "https://github.com/LittleRockInGitHub/LLRegex.git", majorVersion: 1)
]

Usage

Making a Regex

let numbers = Regex("(\\d)(\\d+)(\\d)")

let insensitive = Regex("LLRegex", options: [.caseInsensitive])

let runtimeError = Regex("")    // Runtime error would be raised

let invalid = try? Regex(pattern: "")   // nil returned

Searching

Method matches(in:options:range:) returns a sequence producing matches lazily, which is the only one for searching. All other variants were dropped thanks to the power of Sequence.

let s = "123-45-6789-0-123-45-6789-01234"
let subrange = s.characters.dropFirst(3).startIndex..<s.endIndex

for match in numbers.matches(in: s) {
    // enumerating
    match.matched
}

if let first = numbers.matches(in: s).first {
    // first match
    first.matched
}

let allMatches: [Match] = numbers.matches(in: s).all // all matches

let subrangeMatches = numbers.matches(in: s, options: [.withTransparentBounds], range: subrange)

for case let match in subrangeMatches.dropFirst(1) where match.matched != "6789" {
    match.matched
}

Match & Capture Groups

if let first = numbers.matches(in: s).first {
    
    first.matched
    first.range
    first.groups.count
    first.groups[1].matched
    first.groups[1].range
    
    let replacement = first.replacement(withTemplate: "$3$2$1")     // Replacement with template
}

Named Capture Group (unavailable on iOS 8)

Named capture group feature is enabled when .namedCaptureGroups is set.

let named = Regex("(?<year>\\d+)-(?<month>\\d+)-(?<day>\\d+)", options: .namedCaptureGroups)
let s = "Today is 2017-06-23."

for m in named.matches(in: s) {
    m.groups["year"]?.matched
}

named.replacingAllMatches(in: s, replacement: .replaceWithTemplate("${month}/${day}/${year}")) // Today is 06/23/2017.
  • Note: If the comment in pattern contains the notation of capture group, the detection for named capture group will fail.

Replacing

numbers.replacingFirstMatch(in: s, replacement: .remove)

numbers.replacingAllMatches(in: s, range: subrange, replacement: .replaceWithTemplate("$3$2$1"))

Flexible Find & Replace is offered by replacingMatches(in:options:range:replacing:).

numbers.replacingMatches(in: s) { (idx, match) -> Match.Replacing in
    
    switch idx {
    case 0:
        return .keep    // Keeps unchanged
    case 1:
        return .remove  // Removes the matched string
    case 2:
        return .replaceWithTemplate("($1-$3)")    // Replaces with template
    case 3:
        return .replaceWithString(String(match.matched.characters.reversed()))   // Replaces with string
    default:
        return .stop    // Stops replacing
    }
}

String Matching

"123".isMatching("\\d+")
"llregex".isMatching(insensitive)   // Regex is accepted
"123-456".isMatching("\\d+")    // isMatching(_:) checks whether matches entirely

String Replacing

  • Note: Two variants - one with pattern and template label, the other is not.
"123".replacingAll("1", with: "$0")
"123-321".replacingAll(pattern: "(\\d)(\\d)", withTemplate: "$2$1")

s.replacingFirst(pattern: numbers, in: subrange, withTemplate: "!")

String Splitting

s.split(seperator: "\\d")
s.split(seperator: numbers, maxSplits: 2, omittingEmptyString: false)