9

I want to convert an uppercase string (UPPERCASE) into a title case string (Title Case) in swift. I am not strong in regular expressions, but have found this answer with a regular expression that I have attempted to use.

The search expression is:

"([A-Z])([A-Z]+)\b"

and the template expression is:

"$1\L$2"

In order to use it in swift I have escaped the backslashes as seen below:

var uppercase = "UPPER CASE STRING"
var titlecase = uppercase.stringByReplacingOccurrencesOfString("([A-Z])([A-Z]+)\\b", withString: "$1\\L$2", options: NSStringCompareOptions.RegularExpressionSearch, range: Range<String.Index>(start: uppercase.startIndex, end: uppercase.endIndex))

The code above gives the following result:

"ULPPER CLASE SLTRING"

From that you can see that the search expression successfully finds the two parts $1 and $2, but it looks like escaping the backslash interferes with the replacement.

How can I get the expected result of:

"Upper Case String"
Community
  • 1
  • 1
Marmoy
  • 8,009
  • 7
  • 46
  • 74
  • `"$1$L$2"` results in `"U$LPPER C$LASE S$LTRING"`... – Marmoy Sep 22 '15 at 07:12
  • 3
    You may want to look into the `NSString` method [`capitalizedString`](https://developer.apple.com/library/mac/documentation/Cocoa/Reference/Foundation/Classes/NSString_Class/#//apple_ref/occ/instp/NSString/capitalizedString), which should be available from Swift. – Matt Gibson Sep 22 '15 at 07:13
  • 2
    Judging from "Table 3 – Template Matching Format" in the NSRegularExpression class reference, `\L` is not supported in the replacement template expression. – Martin R Sep 22 '15 at 07:15
  • @MattGibson: Well that was elegant, you can use it directly with Swift Strings too. Thank you. – Marmoy Sep 22 '15 at 07:19
  • @nhahtdh Thanks for the prod; done. – Matt Gibson Sep 22 '15 at 07:37

2 Answers2

11

Many of the useful existing NSString methods are available from Swift. This includes capitalizedString, which may just do exactly what you want, depending on your requirements.

Matt Gibson
  • 37,886
  • 9
  • 99
  • 128
  • 1
    It works for the specific example from the OP, however if you want something a little different, for instance a capital letter after an apostrophe (L'Avion instead of L'avion), then you would need to go for a regex. And I am absolutely missing the \L and/or \U in Swift :/ – Gobe Feb 23 '16 at 19:20
4

As I know, title cased string is the string that has the first letter of each word capitalised (except for prepositions, articles and conjunctions). So, the code should be like that:

public extension String {
    subscript(range: NSRange) -> Substring {
        get {
            if range.location == NSNotFound {
                return ""
            } else {
                let swiftRange = Range(range, in: self)!
                return self[swiftRange]
            }
        }
    }

    /// Title-cased string is a string that has the first letter of each word capitalised (except for prepositions, articles and conjunctions)
    var localizedTitleCasedString: String {
        var newStr: String = ""

        // create linguistic tagger
        let tagger = NSLinguisticTagger(tagSchemes: [.lexicalClass], options: 0)
        let range = NSRange(location: 0, length: self.utf16.count)
        tagger.string = self

        // enumerate linguistic tags in string
        tagger.enumerateTags(in: range, unit: .word, scheme: .lexicalClass, options: []) { tag, tokenRange, _ in
            let word = self[tokenRange]

            guard let tag = tag else {
                newStr.append(contentsOf: word)
                return
            }

            // conjunctions, prepositions and articles should remain lowercased
            if tag == .conjunction || tag == .preposition || tag == .determiner {
                newStr.append(contentsOf: word.localizedLowercase)
            } else {
                // any other words should be capitalized
                newStr.append(contentsOf: word.localizedCapitalized)
            }
        }
        return newStr
    }
}
Vitalii Vashchenko
  • 1,777
  • 1
  • 13
  • 23