可扩展宏,无需额外软件包即可提取 UTF-8/西里尔文或 UTF-8/ASCII 字符串中多个单词的第一个字符

可扩展宏,无需额外软件包即可提取 UTF-8/西里尔文或 UTF-8/ASCII 字符串中多个单词的第一个字符

稍微改变一下描述的宏会出现问题可扩展宏,无需额外软件包即可提取 UTF-8/西里尔字符串的第一个字符对于可能包含两个或更多单词的参数。例如,对于参数 Vladimir Fedorovich,它将返回 VF,对于 Владимир Фёдорович - В.Ф。以下是备受尊敬的大卫·卡莱尔对于单词参数(当然它可以包含多个单词,但只会提取第一个单词的第一个字母):

\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}

我需要\firstof{Vladimir Fedorovich}宏调用返回V.F.并调用\firstof{Владимир Фёдорович}- В.Ф.。但理论上,参数中可以有超过 2 个单词。

我将非常感激您的帮助。

答案1

下面的操作就是你想要的。

我使用 L3 而不是 David 使用的低级来编码。基本思路仍然相同,如果它以扩展为的内容开头,\UTFviii@two@octets我们就收集这两个八位字节,否则我们只使用第一个字符。但是,这也用于\text_purify:n输入,以确保循环的输入仅为文本。

然后我们简单地在每个空格处进行分割(我们必须检查是否完成\quark_if_recursion_tail_stop:n)并获取第一个字符(或组成 UTF8 八位字节对的两个字符)。

该宏\firstofwords将其第一个参数作为每个单词首字母后应放置的内容,第二个参数是用空格分隔的单词列表。

\documentclass{article}

\usepackage[T2A]{fontenc}
\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}

\ExplSyntaxOn
\cs_new:Npn \crosfield_first_of_words:nn #1#2
  { \exp_not:e { \__crosfield_first_of_words:e { \text_purify:n {#2} } {#1} } }
\group_begin:
\cs_set:Npn \__crosfield_tmp:n #1
  {
    \cs_new:Npn \__crosfield_first_of_words:n ##1 ##2
      {
        \__crosfield_first_of_words_spaces:nw {##2} ##1 #1 % #1 is a space
          \q_recursion_tail #1 % #1 is a space
          \q_recursion_stop
      }
  }
\__crosfield_tmp:n { ~ }
\group_end:
\cs_generate_variant:Nn \__crosfield_first_of_words:n { e }
\makeatletter
\cs_new:Npn \__crosfield_first_of_words_spaces:nw #1 #2 ~
  {
    \quark_if_recursion_tail_stop:n {#2}
    \tl_if_head_eq_meaning:oNTF {#2} \UTFviii@two@octets
      { \__crosfield_first_of_words_aux:nnw #2 \q_stop }
      { \tl_head:n {#2} }
    \exp_not:n {#1}
    \__crosfield_first_of_words_spaces:nw {#1}
  }
\makeatother
\cs_generate_variant:Nn \tl_if_head_eq_meaning:nNTF { o }
% there can't be a \q_stop in the argument as that would've caused an infinite
% loop in \text_purify:n, so this is fine and faster than allowing arbitrary
% contents.
\cs_new:Npn \__crosfield_first_of_words_aux:nnw #1#2#3 \q_stop
  { \exp_not:n { #1#2 } }

% define a LaTeX2e name for the above function
\cs_new_eq:NN \firstofwords \crosfield_first_of_words:nn
\ExplSyntaxOff

\begin{document}

\firstof{Vladimir}

\firstof{Владимир}

\firstofwords{.\ }{Vladimir Владимир} example

\firstofwords{.}{foo bar baz}

\end{document}

在此处输入图片描述


编辑:如果您只需要第二个参数并且希望.每次都在第一个字母之间放置一个,则可以使用以下内容代替\cs_new_eq:NN \firstofwords \crosfield_first_of_words:nn

\newcommand* \firstofwords { \crosfield_first_of_words:nn {.} }

编辑2:

此变体仅将第一个参数放置在\firstofwords元素之间,但不放置在最后一个参数之后。

\documentclass[varwidth,border=3.14]{standalone}

\usepackage[T2A]{fontenc}
\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}

\ExplSyntaxOn
\cs_new:Npn \crosfield_first_of_words:nn #1#2
  { \exp_not:e { \__crosfield_first_of_words:e { \text_purify:n {#2} } {#1} } }
\group_begin:
\cs_set:Npn \__crosfield_tmp:n #1
  {
    \cs_new:Npn \__crosfield_first_of_words:n ##1 ##2
      {
        \__crosfield_first_of_words_spaces:Nnw \use_none:n {##2} ##1 #1 % #1 is a space
          \q_recursion_tail #1 % #1 is a space
          \q_recursion_stop
      }
  }
\__crosfield_tmp:n { ~ }
\group_end:
\cs_generate_variant:Nn \__crosfield_first_of_words:n { e }
\makeatletter
\cs_new:Npn \__crosfield_first_of_words_spaces:Nnw #1 #2 #3 ~
  {
    \quark_if_recursion_tail_stop:n {#3}
    #1 {#2}
    \tl_if_head_eq_meaning:oNTF {#3} \UTFviii@two@octets
      { \__crosfield_first_of_words_aux:nnw #3 \q_stop }
      { \tl_head:n {#3} }
    \__crosfield_first_of_words_spaces:Nnw \exp_not:n {#2}
  }
\makeatother
\cs_generate_variant:Nn \tl_if_head_eq_meaning:nNTF { o }
% there can't be a \q_stop in the argument as that would've caused an infinite
% loop in \text_purify:n, so this is fine and faster than allowing arbitrary
% contents.
\cs_new:Npn \__crosfield_first_of_words_aux:nnw #1#2#3 \q_stop
  { \exp_not:n { #1#2 } }

% define a LaTeX2e name for the above function
\cs_new_eq:NN \firstofwords \crosfield_first_of_words:nn
\ExplSyntaxOff

\begin{document}

\firstof{Vladimir}

\firstof{Владимир}

:\firstofwords{.\,}{Vladimir Владимир}.:

\firstofwords{.}{foo bar baz}

\end{document}

如果你希望始终.\,在元素之间使用,并且.在最后一个元素之后使用,则可以使用

\newcommand\firstofwords[1]{ \crosfield_first_of_words:nn {.\,} {#1} . }

而不是\cs_new_eq:NN-line。

答案2

有了expl3\text_map_inline:nn,您就可以做您想做的事情。这个想法就是在第一个项目之后打破映射,添加所需的句点。

\documentclass{article}

\usepackage[T2A]{fontenc}
\usepackage[utf8]{inputenc}
\usepackage[russian]{babel}

\ExplSyntaxOn

\NewDocumentCommand{\firstof}{m}
 {
  \crosfield_firstof:n { #1 }
 }

\seq_new:N \l__crosfield_firstof_items_seq

\cs_new_protected:Nn \crosfield_firstof:n
 {
  % split the input at spaces
  \seq_set_split:Nnn \l__crosfield_firstof_items_seq { ~ } { #1 }
  % extract the first item and add a period
  \seq_map_function:NN \l__crosfield_firstof_items_seq \__crosfield_firstof:n
 }

\cs_new_protected:Nn \__crosfield_firstof:n
 {% map the elements, breaking after the first
  \text_map_inline:nn { #1 } { ##1 \text_map_break:n { . } }
 }

\ExplSyntaxOff

\begin{document}

\firstof{Vladimir}

\firstof{Владимир}

\firstof{Vladimir Fedorovich} 

\firstof{Владимир Фёдорович}

\end{document}

在此处输入图片描述

借用 Skillmon 的答案中的一些代码,我们可以获得可扩展性。

\documentclass{article}

\usepackage[T2A]{fontenc}
\usepackage[utf8]{inputenc}
\usepackage[russian]{babel}

\ExplSyntaxOn

\NewExpandableDocumentCommand{\firstof}{m}
 {
  \crosfield_firstof:n { #1 }
 }

\cs_new:Npn \crosfield_firstof:n #1
 {
  \exp_not:e { \__crosfield_firstof:n { #1 } }
 }
\group_begin:
\cs_set:Npn \__crosfield_tmp:n #1
 {
  \cs_new:Nn \__crosfield_firstof:n
   {
    \__crosfield_firstof_item:w ##1 #1 % #1 is a space
    \q_recursion_tail #1 % #1 is a space
    \q_recursion_stop
   }
 }
\__crosfield_tmp:n { ~ }
\group_end:
\cs_generate_variant:Nn \__crosfield_firstof:n { e }

\cs_new:Npn \__crosfield_firstof_item:w #1 ~
 {
  \quark_if_recursion_tail_stop:n { #1 }
  \text_map_function:nN { #1 } \__crosfield_firstof_do:n 
  \__crosfield_firstof_item:w
 }

\cs_new:Nn \__crosfield_firstof_do:n { #1 \text_map_break:n { . } }

\ExplSyntaxOff

\begin{document}

\firstof{Vladimir}

\firstof{Владимир}

\firstof{Vladimir Fedorovich}

\firstof{Владимир Фёдорович}

\edef\test{\firstof{Владимир Фёдорович}}

\show\test

\end{document}

\show命令将打印

> \test=macro:
->В.Ф..

在控制台上,见证了全面的可扩展性。

相关内容