T
T
tiger_132021-01-06 16:49:22
Swift
tiger_13, 2021-01-06 16:49:22

How to output correct NSRange with NSAttributedString if NSAttributedString is initialized with Data(HTML)?

Hello!
There is a UITextView in which the text is assigned via .attributedText.
NSAttributedString is initialized with Data (we take it from reading the file in which html is). When initializing NSAttributedString, we specify documentType - html.

var HTMLdata:Data?{
        didSet{
            guard let data = self.HTMLdata else {return}
            do{
                let attributed = try NSAttributedString(data: data, options: [NSAttributedString.DocumentReadingOptionKey.documentType:NSAttributedString.DocumentType.html], documentAttributes: nil)
                textView.attributedText = attributed
            }catch{
                return
            }
        }
    }


At this stage, everything works well, NSAttributedString recognizes html tags, applies the styles written in html to them.
But, the next step is to find the link, and scroll the UITextView to the link. Link (a tag is empty)
An example of a piece of HTML:
<p class=main-text><a name="link5"></a>
<span class=main-text-span>Название</span> абзаца</p>

We need to find a with name "link5" and scroll to it.
Since the NSAttributedString has already read the HTML, it is already missing from attributed.string, and accordingly the result of the execution:
let range = attributed.string.range(of: #"<a name="link5"></a>"#) // -> nil

will be nil
If we initialize the String with Data(HTML), then the range will not be nil, but with textView.scrollRangeToVisible() the left piece will be displayed in general, since there will be "not rendered" tags in the String, respectively, the length and location in NSRange will be much more than attributed.string.
How can I find the correct NSRange?

Answer the question

In order to leave comments, you need to log in

1 answer(s)
T
tiger_13, 2021-01-06
@tiger_13

As an option, use regex, with which you can find what is written in from the displayed text (we are looking for a String initialized with data (html), that is, we see all the tags), and look for this text in attributed.string:<p>

//1
    private func getRangeFor(attributedString:String,aValue:String) -> NSRange?{
        guard let aWord = self.findAWords(aValue: aValue),let foundRange = attributedString.range(of: aWord) else {return nil}//ищем aWord - то, что записано в <p>, затем ищем уже range в attributedString(отрендеренный HTML)
        return NSRange(foundRange, in: attributedString)
    }
    
    //2
    private func findAWords(aValue:String) -> String?{
        guard let data = self.HTMLdata,let str = String(data: data, encoding: .utf8) else {return nil}
        
        
        let pattern = #"<p class=main-text><a name="\#(aValue)"></a>\n<span class=main-text-span>(?<paragraph>.+)</span>"#
        let regexRange = NSRange(location: 0, length: str.utf16.count)
        let regex = try! NSRegularExpression(pattern: pattern)
        let regexResult = regex.firstMatch(in: str, options: [], range: regexRange)
        guard let matchedWordRange = regexResult?.range(withName: "paragraph"), let wordRange = Range(matchedWordRange, in: str) else {return nil}
        return String(str[wordRange]) //вернет слово (Название), нужно дописать regex, чтобы была вторая группа - слово ( абзаца)
    }

Didn't find what you were looking for?

Ask your question

Ask a Question

731 491 924 answers to any question