Ole*_*ann 5 string unicode swift swift4
我的目标:给定UTF-16中的任意位置String,找到String.Index代表Character指定UTF-16代码单元一部分的(即扩展字素簇)对应的内容。
例:
这是我的测试字符串:
let str = "?"
Run Code Online (Sandbox Code Playgroud)
(注意:要将字符串视为单个字符,您需要在相当新的OS /浏览器组合上阅读此字符串,该组合可以处理Unicode 9中引入的带有皮肤色调的新职业表情符号。)
它是一个Character(字素群集),由四个Unicode标量或7个UTF-16代码单元组成:
print(str.unicodeScalars.map { "0x\(String($0.value, radix: 16))" })
// ? ["0x1f468", "0x1f3fe", "0x200d", "0x1f692"]
print(str.utf16.map { "0x\(String($0, radix: 16))" })
// ? ["0xd83d", "0xdc68", "0xd83c", "0xdffe", "0x200d", "0xd83d", "0xde92"]
print(str.utf16.count)
// ? 7
Run Code Online (Sandbox Code Playgroud)
给定任意UTF-16偏移量(例如2),我可以创建一个对应的String.Index:
let utf16Offset = 2
let utf16Index = String.Index(encodedOffset: utf16Offset)
Run Code Online (Sandbox Code Playgroud)
我可以使用该索引对字符串进行下标,但是如果索引不位于Character边界上,Character则下标返回的值可能不会覆盖整个字素簇:
let char = str[utf16Index]
print(char)
// ? ?
print(char.unicodeScalars.map { "0x\(String($0.value, radix: 16))" })
// ? ["0x1f3fe", "0x200d", "0x1f692"]
Run Code Online (Sandbox Code Playgroud)
否则下标操作甚至可能会陷阱(我不确定这是否是预期的行为):
let trappingIndex = String.Index(encodedOffset: 1)
str[trappingIndex]
// fatal error: Can't form a Character from a String containing more than one extended grapheme cluster
Run Code Online (Sandbox Code Playgroud)
您可以测试索引是否落在Character边界上:
extension String.Index {
func isOnCharacterBoundary(in str: String) -> Bool {
return String.Index(self, within: str) != nil
}
}
trappingIndex.isOnCharacterBoundary(in: str)
// ? false (as expected)
utf16Index.isOnCharacterBoundary(in: str)
// ? true (WTF!)
Run Code Online (Sandbox Code Playgroud)
问题:
我认为问题在于最后一个表达式返回了true。 文档String.Index.init(_:within:)说明:
如果传递的索引as
sourcePosition表示扩展字素簇的开始(字符串的元素类型),则初始化程序成功。
在这里,utf16Index它不代表扩展的字素簇的开始-字素簇从偏移量0开始,而不是偏移量2。但是初始化程序成功。
结果,我通过重复递减索引值encodedOffset和测试来找到字素簇开始的所有尝试都isOnCharacterBoundary失败了。
我在俯视什么吗?还有另一种方法可以测试索引是否落在a的开头Character吗?这是Swift中的错误吗?
我的环境:macOS 10.13上的Swift 4.0 / Xcode 9.0。
更新:我将String.Index.init?(_:within:)Swift 4.0中的行为报告为错误:SR-5992。
可能的解决方案,使用rangeOfComposedCharacterSequence(at:)\n方法:
extension String {\n func index(utf16Offset: Int) -> String.Index? {\n guard utf16Offset >= 0 && utf16Offset < utf16.count else { return nil }\n let idx = String.Index(encodedOffset: utf16Offset)\n let range = rangeOfComposedCharacterSequence(at: idx)\n return range.lowerBound\n }\n}\nRun Code Online (Sandbox Code Playgroud)\n\n例子:
\n\nlet str = "a\xe2\x80\x8dbcd\xe2\x80\x8d\xe2\x80\x8d\xe2\x80\x8de"\nfor utf16Offset in 0..<str.utf16.count {\n if let idx = str.index(utf16Offset: utf16Offset) {\n print(utf16Offset, str[idx])\n }\n}\nRun Code Online (Sandbox Code Playgroud)\n\n输出:
\n\n\n0 a\n1 \xe2\x80\x8d\n2 \xe2\x80\x8d\n3 \xe2\x80\x8d\n4 \xe2\x80\x8d\n5 \xe2\x80\x8d\n6 \xe2\x80\ x8d\n7 \xe2\x80\x8d\n8 b\n9 \n10 \n11 \n12 \n13 c\n14 \n15 \n16 d\n17 \xe2\x80\x8d\xe2\x80\x8d\xe2\x80\x8d \n18 \xe2\x80\x8d\xe2\x80\x8d\xe2\x80\x8d\n19 \xe2\x80\x8d\xe2\x80\x8d\xe2\x80\x8d\n20 \xe2\x80\x8d\xe2 \x80\x8d\xe2\x80\x8d\n21 \xe2\x80\x8d\xe2\x80\x8d\xe2\x80\x8d\n22 \xe2\x80\x8d\xe2\x80\x8d\xe2\x80\x8d \n23 \xe2\x80\x8d\xe2\x80\x8d\xe2\x80\x8d\n24 \xe2\x80\x8d\xe2\x80\x8d\xe2\x80\x8d\n25 \xe2\x80\x8d\xe2 \x80\x8d\xe2\x80\x8d\n26 \xe2\x80\x8d\xe2\x80\x8d\xe2\x80\x8d\n27 \xe2\x80\x8d\xe2\x80\x8d\xe2\x80\x8d \n28 e \n\n