![可扩展宏,无需额外软件包即可提取 UTF-8/西里尔字符串的第一个字符](https://linux22.com/image/445324/%E5%8F%AF%E6%89%A9%E5%B1%95%E5%AE%8F%EF%BC%8C%E6%97%A0%E9%9C%80%E9%A2%9D%E5%A4%96%E8%BD%AF%E4%BB%B6%E5%8C%85%E5%8D%B3%E5%8F%AF%E6%8F%90%E5%8F%96%20UTF-8%2F%E8%A5%BF%E9%87%8C%E5%B0%94%E5%AD%97%E7%AC%A6%E4%B8%B2%E7%9A%84%E7%AC%AC%E4%B8%80%E4%B8%AA%E5%AD%97%E7%AC%A6.png)
我希望有一个可扩展的宏,无需使用额外的软件包即可提取 UTF-8/西里尔文本字符串的第一个(有时是第二个)字符。TeX 或 LaTeX 的简单解决方案无法处理 UTF-8/西里尔字符串。
下面我给出了一个工作宏的示例,部分取自获取宏参数的第一个和第二个字符:
\documentclass{article}
\usepackage[T2A]{fontenc}
\usepackage[utf8]{inputenc}
\usepackage[russian]{babel}
\makeatletter
\newcommand{\firstof}[1]{\@car#1\@nil}
\makeatother
\begin{document}
\firstof{Vladimir}
\end{document}
Error: Invalid UTF-8 byte sequence (Ð\par)
不幸的是,此示例使用诸如 之类的西里尔字符串时出现错误而失败\firstof{Владимир}
。
我大致知道 TeX 默认不适用于处理多字节字符的字符串,但这个问题在某些软件包中得到了解决。但是,对于这么简单的问题(乍一看似乎如此),我不想使用其他软件包,我将感谢社区提供的帮助和提示。
理想情况下,我想要一个可扩展的宏\newcommand{\firstof}[2][1]{.....}
,例如,对于UTF-8 / Cyrillic字符串,它默认返回第一个字符的值,例如,在\firstof{Владимир}
返回的情况下В
,以及对于\firstof[2]{Владимир}
返回的情况Вл
,这些字符可用于与/ifx
其他字符进行比较并使用写入文件\write
。
答案1
UTF-8 编码的每个字节在 pdflatex 中都是一个单独的标记,但是您可以识别前导标记,它会告诉您需要多少个字节。此版本涵盖了一个字节和两个字节的情况。
\documentclass{article}
\usepackage[T2A]{fontenc}
\usepackage[utf8]{inputenc}
\usepackage[russian]{babel}
\makeatletter
\newcommand{\firstof}[1]{\expandafter\checkfirst#1\@nil}
\def\checkfirst#1{%
\ifx\UTFviii@two@octets#1%
\expandafter\gettwooctets
\else
\expandafter\@car\expandafter#1%
\fi
}
\def\gettwooctets#1#2#3\@nil{\UTFviii@two@octets#1#2}
\makeatother
\begin{document}
\firstof{Vladimir}
\firstof{Владимир}
\end{document}
如果您想要处理输入的其余部分,而不是丢弃第一个字母之后的所有内容,您可以进行一些小改动,以便传入一个命令来应用于剩余的文本。如果您传入,\gobble
它会像以前一样提取。如果您传入,\firstofx\gobble
那么它会提取剩余文本的第一个字母,这样您就会得到两个字母:
\documentclass{article}
\usepackage[T2A]{fontenc}
\usepackage[utf8]{inputenc}
\usepackage[russian]{babel}
\makeatletter
\newcommand{\firstofx}[2]{\expandafter\checkfirst#2\@nil{#1}}
\def\checkfirst#1{%
\ifx\UTFviii@two@octets#1%
\expandafter\gettwooctetsx
\else
\expandafter\getasciix\expandafter#1%
\fi
}
\def\getasciix#1#2\@nil#3{#1#3{#2}}
\def\gettwooctetsx#1#2#3\@nil#4{\UTFviii@two@octets#1#2#4{#3}}
\newcommand\gobble[1]{}
\makeatother
\begin{document}
\firstofx\gobble{Vladimir}
\firstofx{\firstofx\gobble}{Vladimir}
\firstofx\gobble{Владимир}
\firstofx{\firstofx\gobble}{Владимир}
\end{document}
答案2
这应该可以工作。我定义了\headof
和\tailof
(使用从中窃取的代码)这里和这里),它们的作用与名称所承诺的一样。如果你使用\headof*
(或),它将扩展其参数,因此你可以通过嵌套and\tailof*
毫不费力地从序列中获取第 n 个字符(如果你喜欢的话,这非常类似于lisp 中的and )。例如,可以使用以下方法提取的第四个字符\headof*
\tailof*
car
cdr
Владимир
\headof*{\tailof*{\tailof*{\tailof{Владимир}}}}
简单得令人烦恼 :)
然后,您可以使用原始\pdfstrcmp
或更高级别来比较字符串\str_if_eq:eeTF
。 \ifx
这将不起作用,因为它比较两个标记,并且л
(例如)本身就是两个标记(当然,假设您使用 pdfTeX)。
如果参数为空,结果也为空。如果头部是一组标记(在{
...内}
),则该组将被视为单个事物并返回,不带外部括号。
\documentclass{article}
\usepackage[T2A]{fontenc}
\usepackage[utf8]{inputenc}
\usepackage[russian]{babel}
\ExplSyntaxOn
\NewExpandableDocumentCommand \headof { s +m }
{ \IfBooleanT {#1} { \exp_args:Ne } \crosfield_text_head:n {#2} }
\NewExpandableDocumentCommand \tailof { s +m }
{ \IfBooleanT {#1} { \exp_args:Ne } \crosfield_text_tail:n {#2} }
\cs_new:Npn \crosfield_text_head:n #1
{
\tl_if_head_is_N_type:nTF {#1}
{ \__crosfield_get_head:Nw #1 \q_stop }
{
\tl_if_head_is_group:nTF {#1}
{ \exp_not:o { \use_i_delimit_by_q_stop:nw #1 \q_stop } }
{ \tl_if_empty:nTF {#1} { } { ~ } }
}
}
\cs_new:Npn \crosfield_text_tail:n #1
{
\tl_if_head_is_N_type:nTF {#1}
{ \__crosfield_get_tail:Nw #1 \q_stop }
{
\tl_if_head_is_group:nTF {#1}
{ \exp_not:o { \use_none:n #1 } }
{ \tl_if_empty:nTF {#1} { } { \exp_not:o { \exp:w \exp_end_continue_f:w #1 } } }
}
}
\bool_lazy_or:nnTF
{ \sys_if_engine_luatex_p: }
{ \sys_if_engine_xetex_p: }
{
\cs_new:Npn \__crosfield_get_head:Nw #1 #2 \q_stop { \exp_not:N #1 }
\cs_new:Npn \__crosfield_get_tail:Nw #1 #2 \q_stop { \exp_not:n {#2} }
\use_none:n
}
{ \makeatletter \use:n }
{
\makeatother
\cs_new:Npn \__crosfield_get_head:Nw
{ \__crosfield_head_tail:NNw \use_i:nn }
\cs_new:Npn \__crosfield_get_tail:Nw
{ \__crosfield_head_tail:NNw \use_ii:nn }
\cs_new:Npn \__crosfield_head_tail:NNw #1 #2 #3 \q_stop
{
\use:e
{
\exp_not:N \__crosfield_head_tail:w
\exp_not:o { \token_to_meaning:N #2 }
\tl_to_str:n { UTFviii@ one @octets } ~
} \q_stop { #2 #3 } #1
}
\use:e
{
\cs_new:Npn \exp_not:N \__crosfield_head_tail:w
#1 \tl_to_str:n { UTFviii@ } #2 \tl_to_str:n { @octets } ~ #3
\exp_not:N \q_stop #4 #5
}
{
\str_case:nnTF {#2}
{
{ one } { \__crosfield_head_or_tail:NNw #5 }
{ two } { \__crosfield_head_or_tail:NNNw #5 }
{ three } { \__crosfield_head_or_tail:NNNNw #5 }
{ four } { \__crosfield_head_or_tail:NNNNNw #5 }
}
{ #4 \q_stop }
{ \ERROR? }
}
\cs_new:Npn \__crosfield_head_or_tail:NNw #1 #2 #3 \q_stop
{ \exp_not:o { #1 {#2} {#3} } }
\cs_new:Npn \__crosfield_head_or_tail:NNNw #1 #2#3 #4 \q_stop
{ \exp_not:o { #1 {#2#3} {#4} } }
\cs_new:Npn \__crosfield_head_or_tail:NNNNw #1 #2#3#4 #5 \q_stop
{ \exp_not:o { #1 {#2#3#4} {#5} } }
\cs_new:Npn \__crosfield_head_or_tail:NNNNNw #1 #2#3#4#5 #6 \q_stop
{ \exp_not:o { #1 {#2#3#4#5} {#6} } }
}
\cs_new_eq:NN \StrCompare \str_if_eq:eeTF
\ExplSyntaxOff
\begin{document}
\StrCompare
{\headof*{\tailof{Владимир}}}{\detokenize{л}}
{\true}{\false}
\end{document}
答案3
这是一个基于 LuaLaTeX 的解决方案。该解决方案不使用标准string.sub
Lua 函数,因为它不支持 unicode。相反,它使用函数来提取字符串参数的unicode.utf8.sub
第一个n
(默认情况下为)字符。该宏是可扩展的。n=1
\firstof
% !TEX TS-program = lualatex
\documentclass{article}
\usepackage[russian]{babel}
\usepackage{fontspec}
\setmainfont{Noto Serif} % select a suitable opentype text font
\newcommand\firstof[2][1]{\directlua{%
tex.sprint ( unicode.utf8.sub ( "#2" , 1 , #1 ) ) }}
\newcommand\zzz{Владимир}
\begin{document}
\firstof{\zzz}, \firstof[2]{\zzz}, \firstof[4]{\zzz}
\end{document}