如何在SQLite存储的文本中定位em-dash等多字节符号字符?
初始问题
我有存储在SQLite数据库表中的文本数据,其中包含em-dash(长破折号),我希望识别出结果中包含该符号的单词。
我可以正常取出带em-dash的文本传递到浏览器展示,显示正常,但Konsole终端无法显示该字符,我推测是命令行界面不支持渲染,且该字符长度超过1字节。
我的核心问题是如何在文本中检测该字符。
我尝试了如下代码,但无法定位到该符号,不过可以正常匹配其他文本内容:
dbt eval $sql { set l $text_content foreach word $l { if { [string first — $word] > -1 } { puts "Got one!" } chan puts stdout "[incr i] $word" } set i 0 chan puts stdout "\n\n" }
字段$text_content存储文本字符串,我按空格对其进行分词,由于SQLite会将字符串以列表形式返回,因此标点符号等通常会附着在英文单词前后。我需要记录带标点和不带标点的单词,同时捕获em-dash等特定符号的出现,就像处理独立单词一样,目的是构建单独的索引表来提速多词组合文本检索效率。另外也想了解SQLite中跨文本行检索的更优方案,逐行检索效率过低,我希望构建类似圣经经文索引的结构,指向所有包含目标单词的行,再通过“指针”交集筛选满足所有检索条件的行。
我需要匹配所有符合条件的结果,上述代码仅用于测试。
后续调试进展
使用社区提供的代码测试后发现:
我手头的Tcl教程索引中没有lmap命令,我将示例代码用于测试包含疑似em-dash的字符串,发现该字符并非标准em-dash:标准em-dash的ASCII码为151,而该字符返回的编码为226、128、148三个值。我用Tcl做本地服务将SQLite取出的字符串传到浏览器可正常显示为破折号,但这三个编码对应ASCII字符为带抑扬符的拉丁文a、欧元符号、右双引号,Tcl输出到Konsole终端时仅显示带抑扬符的拉丁文a。
我对多字节字符了解很少,这类字符已经在多个场景给我造成了问题。推测数据写入数据库时出现了编码问题,因为直接从源文件复制内容到Kate编辑器中测试,破折号的编码为8212。
如果数据库中所有同类破折号都是这套编码的话,我可以通过编码集合搜索匹配,将其替换为标准em-dash。
我尝试了如下代码,看起来可以正常运行,但部分带em-dash的单词同时包含分号,使用lset修改列表中的单词时,该单词会被作为长度为1的子列表插入,如何让其保持为末尾带分号的普通字符串?
示例:原字符串为word—;,我将异常破折号替换为编码151的em-dash后修改列表,结果变为{ word word {word—;} word }。
我想知道当前的多字节处理方案在该场景下是否可靠?
我意识到使用151编码不合理,因为数据不是ASCII编码,使用8212编码的em-dash从Tcl传递到浏览器时,浏览器报错SyntaxError: JSON.parse: bad control character in string literal at line 1 column 49 of the JSON data,这应该就是最初em-dash被存为三字节编码的原因。直接传递三字节版本到浏览器可正常接收显示,我对这一基础编码逻辑存在认知误区,问题似乎出现在将文本转成二进制传输到浏览器的环节。
proc Emdash {} { global cps string pseudo_emdash set l [expr { [llength $cps] - 2}] set k -1; # Character position in cps. set b 0; # Count blank characters for word position. set p 0; # Track position of last blank for character position. while { [incr k] < $l } { if { [lindex $cps $k] == 32 } { incr b; set p $k } for { set x [lindex $cps [set i $k]]; set y [lindex $pseudo_emdash [set j 0]] } \ { $x == $y && $j < 3 } { incr i; incr j } { } if { $j == 3 } { chan puts stdout "A match begins at index $k after blank no. $b." chan puts stdout [lrange $cps $k [expr {$i-1}]] chan puts stdout "[lindex $string $b] [expr {$k-$p-1}]" set new [string replace [lindex $string $b] [set z [expr {$k-$p-1}]] $z+2 [format %c 151]] chan puts stdout $new lset string $b $new chan puts stdout $string } } } set pseudo_emdash {226 128 148} set string "" set cps "" dbt eval $sql { global string cps chan puts stdout $text_content set string $text_content set cps [lmap c [split $text_content ""] { scan $c %c }] Emdash }
内容的提问来源于stack exchange,提问作者Gary

