如何定位文本字符串中的多字节符号字符?
How to locate mutli-byte symbol characters in text strings?
我有文本数据,存储在 SQLite 数据库 table 中,其中包括破折号,至少我认为它们是这样的。我想找出结果中包含一个的词。
我能够检索带有 em-dashes 的文本并将其传递给浏览器进行显示,并且它按预期显示。虽然我在 Konsole 中看不到它,但我认为命令行界面无法显示它。
它的长度似乎也超过了一个字节。
我的问题是,如何测试文本中的这个字符?
下面是我一直在尝试的代码,它没有找到符号。不过它确实找到了其他文本。
列$text_content
是一串文字,我是按字分开的。更准确地说,通过 space 字符,因为 SQLite returns 将字符串作为列表。因此,标点符号和其他符号通常作为英文单词的前缀或后缀。我想记录带标点符号和不带标点符号的单词,并捕捉某些符号的存在,例如 em-dash,就好像它们是单词一样。目的是构建一个单独的 table,从中可以更快地执行文本搜索字词和字词组合。
此外,如果有更好的方法在 SQLite 中跨文本行执行文本搜索,请告诉我它们是什么。每次搜索都搜索每一行似乎效率很低。所以,我正在尝试构建一个相当于圣经索引的索引,它指向包含该词的每一行,然后使用“指针”的交集来确定满足所有搜索要求的行。
我需要找到所有匹配项,而不仅仅是第一个,但这只是为了测试。
感谢您提供的任何指导。
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"
}
使用来自@Shawn 的代码后。
lmap 命令甚至不在我的 Tcl 书中,至少不在索引中。无论如何,我在其中一个字符串上使用了代码示例,我认为它是破折号;我想它不是真正的破折号,因为 ASCII 代码是 151,返回的是破折号的三个代码--226 128 148。当我从 SQLite 中提取字符串并将其显示在浏览器中时,使用 Tcl 作为本地服务器,它显示某种破折号;但这些 ASCII 代码是带有抑扬符、欧元符号和右双引号的拉丁文 a。当从 Tcl 写入 stdout 时,Konsole 显示带有抑扬符的 a。
我对多字节字符几乎一无所知,只是它们在很多方面给我带来了问题。将数据写入数据库时一定出了什么问题,因为如果我返回源代码并将其复制到 Kate 中,然后 运行 将其放在该字符串上而不是从 SQLite 中检索它,破折号的代码是 8212 .
我想,如果这是所有这些破折号在这些字符串中的编码方式,我可以通过代码集搜索它们,就像在一个字符串中搜索一个字符串,然后用真正的 em 破折号替换它们。
我试过使用这个代码;它似乎有效。但是,一些带有破折号的单词也带有分号;当使用 lset
在列表中修改和更改单词时,它会作为长度为 1 的列表本身添加。我怎样才能让它成为以分号结尾的单词?我将其添加为一个单独的问题 .
例如,如果原来是 word—;
,我将奇数破折号修正为 151 并修改列表,它显示为 { word word {word—;} word }
。
我想知道的是,至少对于这种特殊情况,这是一种在 Tcl 中处理多字节的可靠方法吗?
我发现使用 151 很愚蠢,因为它不是 ASCII 编码的。当我使用 8212 并将文本从 Tcl 传递到浏览器时,浏览器抛出 SyntaxError: JSON.parse: bad control character in string literal at line 1 column 49 of the JSON data
错误。因此,不管是什么原因,这一定是为什么 em 破折号在开始时被编码为三个字节。如果我将三字节版本传递给浏览器,它会接受并按预期显示。因此,我不明白这里重要和基本的东西。将其转换为二进制文件以传递给浏览器时似乎会出现此问题。
谢谢。
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
}
有三种比较常见的破折号:-
(minus/hyphen)、–
(en-dash)和—
(em-dash)。好的,它们在那种字体下看起来是一样的;它们在 运行 文本中:“-”、“–”和“—”。它们的Unicode码分别是45、8211、8212。只有第一个是 ASCII;另外两个不是。结果是它们需要多个字节来表示(现在非常常见)UTF-8 编码,实际上每个破折号需要三个字节(这样通常是无趣的,除非事情变得相当错误)。只有第一个在编程中真正用得那么多,而其他两个通常更难打字(尽管不在这台机器上)。
在 Tcl 中,要匹配这些字符而不输入它们,您可以使用 \u2013
和 \u2014
(这些数字是十六进制的)。您可以在标准 Tcl 代码或正则表达式中使用它;它在两个地方都一样。
# Print an em-dash
puts "abc \u2014 def"
# Test if a string has an en-dash or em-dash in it
if {[regexp {[\u2013\u2014]} $inputString]} {
puts "found one"
}
我不记得 SQLite 是如何处理字符转义的,但它肯定更喜欢将文本存储为 UTF-8。它应该与 Tcl 一起工作得很好。
您的代码中的问题(lmap
是 Tcl 8.6 中引入的命令)是您在字符串和列表之间不小心进行了转换;列表是字符串,是的,但是有 非常 特定的引用规则,并且这些规则在生成列表的字符串形式时特别严格(例如,在您使用 [=19= 修改列表之后] 或 lappend
)。特别是,all Tcl 元字符以一种或另一种方式被引用(通常用大括号,有时用反斜杠)。从字符串解析列表的代码要宽松得多。
解决这个问题的方法是 split
输入句子,将转换应用于列表中的每个 word 项,然后 join
结果。
join [lmap word [split $input] { ... }]
这就是你要做的事情的框架;您只需添加 ...
,也许 string map
:
puts [join [lmap word [split $input] {
string map {\u2013 - \u2014 -} $word
}]]
我有文本数据,存储在 SQLite 数据库 table 中,其中包括破折号,至少我认为它们是这样的。我想找出结果中包含一个的词。
我能够检索带有 em-dashes 的文本并将其传递给浏览器进行显示,并且它按预期显示。虽然我在 Konsole 中看不到它,但我认为命令行界面无法显示它。
它的长度似乎也超过了一个字节。
我的问题是,如何测试文本中的这个字符?
下面是我一直在尝试的代码,它没有找到符号。不过它确实找到了其他文本。
列$text_content
是一串文字,我是按字分开的。更准确地说,通过 space 字符,因为 SQLite returns 将字符串作为列表。因此,标点符号和其他符号通常作为英文单词的前缀或后缀。我想记录带标点符号和不带标点符号的单词,并捕捉某些符号的存在,例如 em-dash,就好像它们是单词一样。目的是构建一个单独的 table,从中可以更快地执行文本搜索字词和字词组合。
此外,如果有更好的方法在 SQLite 中跨文本行执行文本搜索,请告诉我它们是什么。每次搜索都搜索每一行似乎效率很低。所以,我正在尝试构建一个相当于圣经索引的索引,它指向包含该词的每一行,然后使用“指针”的交集来确定满足所有搜索要求的行。
我需要找到所有匹配项,而不仅仅是第一个,但这只是为了测试。
感谢您提供的任何指导。
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"
}
使用来自@Shawn 的代码后。
lmap 命令甚至不在我的 Tcl 书中,至少不在索引中。无论如何,我在其中一个字符串上使用了代码示例,我认为它是破折号;我想它不是真正的破折号,因为 ASCII 代码是 151,返回的是破折号的三个代码--226 128 148。当我从 SQLite 中提取字符串并将其显示在浏览器中时,使用 Tcl 作为本地服务器,它显示某种破折号;但这些 ASCII 代码是带有抑扬符、欧元符号和右双引号的拉丁文 a。当从 Tcl 写入 stdout 时,Konsole 显示带有抑扬符的 a。
我对多字节字符几乎一无所知,只是它们在很多方面给我带来了问题。将数据写入数据库时一定出了什么问题,因为如果我返回源代码并将其复制到 Kate 中,然后 运行 将其放在该字符串上而不是从 SQLite 中检索它,破折号的代码是 8212 .
我想,如果这是所有这些破折号在这些字符串中的编码方式,我可以通过代码集搜索它们,就像在一个字符串中搜索一个字符串,然后用真正的 em 破折号替换它们。
我试过使用这个代码;它似乎有效。但是,一些带有破折号的单词也带有分号;当使用 lset
在列表中修改和更改单词时,它会作为长度为 1 的列表本身添加。我怎样才能让它成为以分号结尾的单词?我将其添加为一个单独的问题
例如,如果原来是 word—;
,我将奇数破折号修正为 151 并修改列表,它显示为 { word word {word—;} word }
。
我想知道的是,至少对于这种特殊情况,这是一种在 Tcl 中处理多字节的可靠方法吗?
我发现使用 151 很愚蠢,因为它不是 ASCII 编码的。当我使用 8212 并将文本从 Tcl 传递到浏览器时,浏览器抛出 SyntaxError: JSON.parse: bad control character in string literal at line 1 column 49 of the JSON data
错误。因此,不管是什么原因,这一定是为什么 em 破折号在开始时被编码为三个字节。如果我将三字节版本传递给浏览器,它会接受并按预期显示。因此,我不明白这里重要和基本的东西。将其转换为二进制文件以传递给浏览器时似乎会出现此问题。
谢谢。
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
}
有三种比较常见的破折号:-
(minus/hyphen)、–
(en-dash)和—
(em-dash)。好的,它们在那种字体下看起来是一样的;它们在 运行 文本中:“-”、“–”和“—”。它们的Unicode码分别是45、8211、8212。只有第一个是 ASCII;另外两个不是。结果是它们需要多个字节来表示(现在非常常见)UTF-8 编码,实际上每个破折号需要三个字节(这样通常是无趣的,除非事情变得相当错误)。只有第一个在编程中真正用得那么多,而其他两个通常更难打字(尽管不在这台机器上)。
在 Tcl 中,要匹配这些字符而不输入它们,您可以使用 \u2013
和 \u2014
(这些数字是十六进制的)。您可以在标准 Tcl 代码或正则表达式中使用它;它在两个地方都一样。
# Print an em-dash
puts "abc \u2014 def"
# Test if a string has an en-dash or em-dash in it
if {[regexp {[\u2013\u2014]} $inputString]} {
puts "found one"
}
我不记得 SQLite 是如何处理字符转义的,但它肯定更喜欢将文本存储为 UTF-8。它应该与 Tcl 一起工作得很好。
您的代码中的问题(lmap
是 Tcl 8.6 中引入的命令)是您在字符串和列表之间不小心进行了转换;列表是字符串,是的,但是有 非常 特定的引用规则,并且这些规则在生成列表的字符串形式时特别严格(例如,在您使用 [=19= 修改列表之后] 或 lappend
)。特别是,all Tcl 元字符以一种或另一种方式被引用(通常用大括号,有时用反斜杠)。从字符串解析列表的代码要宽松得多。
解决这个问题的方法是 split
输入句子,将转换应用于列表中的每个 word 项,然后 join
结果。
join [lmap word [split $input] { ... }]
这就是你要做的事情的框架;您只需添加 ...
,也许 string map
:
puts [join [lmap word [split $input] {
string map {\u2013 - \u2014 -} $word
}]]