String.Index is not valid after mutating its associated string
zfoltin opened this issue · comments
Great little util, thanks for it! ❤️
I've just run into this issue with longer Cyrillic text (and a simple <b>...</b>
in there):
https://forums.swift.org/t/string-index-is-not-valid-after-mutating-its-associated-string/30851
It's an issue as text
gets mutated between didStartElement
and didEndElement
calls.
So instead of storing String.Index
, I'm recording the Int
position in Element
(text.count) and creating the NSRange based on that in didEndElement
.
private struct Element {
let name: String
- let startIndex: String.Index
+ let startPosition: Int
let attributes: [NSAttributedString.Key: Any]
}
...
func parser(_ parser: XMLParser,
didStartElement elementName: String,
namespaceURI: String?,
qualifiedName qName: String?,
attributes attributeDict: [String: String] = [:]) {
var attributes = style.getAttributes(for: elementName) ?? [:]
if elementName == "a", let url = attributeDict["href"].map(URL.init(string:)) {
attributes[.link] = url
}
- let element = Element(name: elementName, startIndex: text.endIndex, attributes: attributes)
+ let element = Element(name: elementName, startPosition: text.count, attributes: attributes)
elements.append(element)
}
func parser(_ parser: XMLParser, didEndElement elementName: String, namespaceURI: String?, qualifiedName qName: String?) {
guard let element = elements.popLast() else {
return assertionFailure("No opening tag for \(elementName)")
}
guard element.name == elementName else {
return assertionFailure("Closing tag mismatch. Expected: \(element.name), got: \(elementName)")
}
- let range = NSRange(element.startIndex..<text.endIndex, in: text)
+ let range = NSRange(location: element.startPosition, length: text.count - element.startPosition)
attributes.append((range, element.attributes))
}
Hope this helps
Thanks, can you share the example text so that I could add a unit test for this?
I haven't seen this issue yet, my assumption was that String
doesn't invalidate its indices after appending. If it does, that's going to be a problem. Yeah, switching to offset-based indexing will fix it.
This piece of string caused a crash: "Если вы забыли свой пароль на новом устройстве, вам также будет отправлен <b>6-значный код проверки</b>"
While a similar string worked: "Вы получите временный <b>4-значный пароль</b> через SMS, который вам нужно будет изменить"
...so not quite sure if it's language related or just my environment somehow.
I opened a PR #2.
Looking good 👍 Strings, especially the indexing, in Swift is 🤯 (or more like 💩). Add some compiler optimisation to the mix and we end up with weird stuff like this. I didn't expect indexes to get invalidated when a string "grows" either.