9

The below function given a NSString, removes the HTML tags from that string and returns the result also as a NSString.

private func removeHTMLTags(source: NSString) -> NSString {
    var range = NSMakeRange(0, 0)
    let HTMLTags = "<[^>]*>"
    var sourceString = source

    while sourceString.rangeOfString(HTMLTags, options: NSStringCompareOptions.RegularExpressionSearch).location != NSNotFound {
        range = sourceString.rangeOfString(HTMLTags, options: NSStringCompareOptions.RegularExpressionSearch)
        sourceString = sourceString.stringByReplacingCharactersInRange(range, withString: "")
    }
    return sourceString;
}

I'm trying to rewrite this in pure Swift. I'm facing a issue with the Range type in Swift.

In the original code function, range variable is declared of type NSRange. In my version I cannot do that because the line sourceString.rangeOfString(HTMLTags, options: NSStringCompareOptions.RegularExpressionSearch) inside the while loop returns the type Range<String.Index> and it would give me the error Cannot convert the expression's type '()' to type 'NSRange'.

So I declared the variable like this var range = Range(start: 0, end: 0) but now I get a couple of new errors.

Cannot convert the expression's type '()' to type 'Range' error at the line

range = sourceString.rangeOfString(HTMLTags, options: NSStringCompareOptions.RegularExpressionSearch)

And 'Int' is not identical to 'String.Index' at the line

sourceString = sourceString.stringByReplacingCharactersInRange(range, withString: "")

I searched for a solution to this and came across this post. Then I changed the range variable declaration to this.

var range = Range<String.Index>(start: 0, end: 0)

But I get this new error now! Extra argument 'end' in call

I can't figure out a way to resolve this. Can anyone help please?

Thank you.

Isuru
  • 30,617
  • 60
  • 187
  • 303

5 Answers5

8

The Swift String method rangeOfString() returns an optional Range? which does not have a location property but can be checked with conditional binding (if let).

And if you replace the NSString method stringByReplacingCharactersInRange() by the Swift String method replaceRange() (or in this case simply by removeRange()) then you can work purely with Range<Swift.Index> without converting it to NSRange or Range<Int>.

func removeHTMLTags(source : String) -> String {

    var sourceString = source
    let HTMLTags = "<[^>]*>"

    while let range = sourceString.rangeOfString(HTMLTags, options: .RegularExpressionSearch) {
        sourceString.removeRange(range)
    }

    return sourceString;
}
Martin R
  • 529,903
  • 94
  • 1,240
  • 1,382
  • Awesome! Huge thanks as always. I need to familiarize myself with this conditional binding more. – Isuru Nov 26 '14 at 20:12
  • 1
    Besides this workaround, is there a way to create a `String.Index` from an `Int`? – Rivera Apr 28 '15 at 19:25
  • @Rivera: Yes, with `advance()`, compare http://stackoverflow.com/questions/24092884/get-nth-character-of-a-string-in-swift-programming-language or http://stackoverflow.com/questions/24044851/how-do-you-use-string-substringwithrange-or-how-do-ranges-work-in-swift. – But it depends on what you really want to do, the problem is that NSString and Swift strings count characters differently. – Martin R Apr 28 '15 at 19:40
4

For people like me who really want to get a Range<String.Index>:

func convert(range: Range<Int>, string: String) -> Range<String.Index>
{
    return Range<String.Index>(start: advance(string.startIndex, range.startIndex),
                               end: advance(string.startIndex, range.endIndex))
}

You do need to reference the string where you'll be using the range.

Rivera
  • 10,792
  • 3
  • 58
  • 102
  • 3
    In Swift 2, this is `return Range(start: string.startIndex.advancedBy(nsRange.location), end: string.startIndex.advancedBy(nsRange.location+nsRange.length))` – jrc Oct 08 '15 at 22:20
  • Not it is not, we're dealing with Range not NSRange – thibaut noah Jan 26 '16 at 16:43
2

In Swift 2, given string: String and nsRange: NSRange, this is

let range = Range(start: string.startIndex.advancedBy(nsRange.location),
    end: string.startIndex.advancedBy(nsRange.location+nsRange.length))
Dalorzo
  • 19,834
  • 7
  • 55
  • 102
jrc
  • 20,354
  • 10
  • 69
  • 64
0

Swiss-army steam shovel for String subscripting, NSRange, Range<Int|String.Index> conversions

import Foundation

func +<T: IntegerType>(lhs: String.Index, rhs: T) -> String.Index {
    var r = lhs
    var x = rhs
    while x > 0 { // advance() won't work because IntegerType and String.Index are incompatible 
        r = r.successor()
        x--
    }
    while x < 0 {
        r = r.predecessor()
        x++
    }
    return r
}

func -<T: IntegerType>(lhs: String.Index, rhs: T) -> String.Index {
    var r = lhs
    var x = rhs
    while x > 0 {
        r = r.predecessor()
        x--
    }
    while x < 0 {
        r = r.successor()
        x++
    }
    return r
}

extension NSRange {
    init(range: Range<Int>) {
        location = range.startIndex
        length = range.endIndex - range.startIndex
    }

    var range: Range<Int> { return Range<Int>(start: location, end: location + length) }
}

extension String {
    var nsrange: NSRange { return NSMakeRange(0, count(self)) }

    var range: Range<Int> { return Range<Int>(start: 0, end: count(self)) }

    subscript (index: Int) -> String {
        return self[index...index]
    }

    subscript (index: Int) -> Character {
        return self[index] as Character
    }

    subscript (range: Range<String.Index>) -> String {
        return substringWithRange(range)
    }

    subscript (range: NSRange) -> String {
        return self[toStringRange(range)]
    }

    // allows "abcd"[0...1] // "ab"
    subscript (range: Range<Int>) -> String {
        return self[toStringRange(range)]
    }

    func toStringRange(range: NSRange) -> Range<String.Index> {
        return toStringRange(range.range)
    }

    func toStringRange(range: Range<Int>) -> Range<String.Index> {
        let start = startIndex + max(range.startIndex, 0)
        let end = startIndex + min(range.endIndex, count(self))
        return Range<String.Index>(start: start, end: end)
    }
}

Gist here

0

Convert NSRange to Range<String.Index> swift function

func rangeWithString(string : String, range : NSRange) -> Range<String.Index> {
    return string.startIndex.advancedBy(range.location) ..< string.startIndex.advancedBy(range.location+range.length)
}
David Douglas
  • 10,377
  • 2
  • 55
  • 53