使用特殊输入/可选参数进行列表解析

使用特殊输入/可选参数进行列表解析

我想解析一个列表,其中元素可以是[<pre>]<text>[<post>],其中<pre><post>是可选的,或者(<special text>)。我找到了一个解决方案,如果<text>是单个标记,则一切正常,但如果它是一个单词,则不行。

因此像

\mylist{1,[a]2,3[b],[c]4[d],(5)}

我得到并渴望像这样的输出

正确的结果

\mylist{111,[a]222,333[b],[c]444[d],(555)}

我明白了

错误的结果

虽然希望得到这样的输出

期望结果

我也尝试过使用{<text>}而不是<text>,即将元素放在花括号中,但这并非在所有情况下都有效。此外,我更喜欢不使用括号来界定元素。

分隔符打印效果也不佳。

以下是我目前得到的信息

\documentclass[parskip=full]{scrartcl}

\makeatletter

\usepackage{etoolbox,xparse}
\def\cflist@printsep{%
   \ifcflist@sep\quad$\bullet$\quad\fi
}
\def\cflist@none#1{%
   \@ifnextchar[{\cflist@printsep[#1]\cflist@last}{%
      \cflist@printsep
      [#1]%
   }%
}
\def\cflist@first[#1]#2{%
   \@ifnextchar[{\cflist@printsep\cflist@both[#1]{#2}}{\cflist@printsep${}_{#1}$[#2]}%
}
\def\cflist@last#1[#2]{%
   #1${}_{#2}$%
}
\def\cflist@both[#1]#2[#3]{%
   ${}_{#1}$[#2]${}_{#3}$%
}
\def\cflist@extra(#1){\cflist@printsep[#1]${}_E$}
\newcounter{cflist@cnt}
\newif\ifcflist@sep
\def\cflist@do#1{%
   \@ifnextchar({%
      \cflist@extra%
   }{%
      \@ifnextchar[{\cflist@first}{%
         \cflist@none%
      }%
   }#1%
   \stepcounter{cflist@cnt}%
   \ifnum\value{cflist@cnt}=0\relax\else
      \cflist@septrue
   \fi
}

\DeclareListParser{\cflist@parser}{,}

\newcommand{\mylist}[1]{%
   \let\do\cflist@do
   \setcounter{cflist@cnt}{0}%
   \cflist@sepfalse
   \cflist@parser{#1}%
}

\makeatother


\begin{document}

Single element tokes work fine:\\
\mylist{1,[a]2,3[b],[c]4[d],(5)}

Multiple element tokes don't work:\\
\mylist{111,[a]222,333[b],[c]444[d],(555)}\\
\mylist{aaa,[a]bbb,ccc[b],[c]ddd[d],(eee)}

Multiple element tokens in curly braces don't work either:\\
\mylist{{111},[a]{222},{333}[b],[c]{444}[d],(555)}\\
\mylist{{aaa},[a]{bbb},{ccc}[b],[c]{ddd}[d],(eee)}

Desired result for multiple element tokes:\\
111\quad$\bullet$\quad${}_a$222\quad$\bullet$\quad
333${}_b$\quad$\bullet$\quad${}_c$444${}_d$
\quad$\bullet$\quad555${}_E$
\end{document}

输出格式是其他格式宏的占位符,但我猜如果我在这里使用虚拟宏,它不会影响问题并使代码更短一些。

更新

我忘了是\cflist@sepfalse什么原因导致第一个列表之后的所有列表开头都出现了多余的 sep

已更新 2

真正的应用是将列表发送给\citebiblatex 的宏,因此列表

aaa,[a]bbb,ccc[b],[c]ddd[d],(eee)

应处理为

\cite{aaa}, \cite[][a]{bbb}, \cite[b]{ccc}, \cite[c][d]{ddd}, eee

(eee)部分应该绕过\cite机制并且按原样打印,不带括号。

答案1

这至少部分地解答了解析问题。但当然,我在这里使用了数学模式的特定功能。但原理应该很清楚。

\documentclass{article}
\makeatletter
\def\mylist#1{%
  \@for\next:=#1\do{%
    \expandafter\@mylistaux\next[]\@nil
    \@mylist@separator}%
  }
\def\@mylistaux{\@ifnextchar(\@mylistauxi\@mylistauxii}
\def\@mylistauxi(#1)[]\@nil{\@mylist@parentheses{#1}}
\def\@mylistauxii{\@ifnextchar[\@mylistauxiii{\@mylistauxiii[]}}
\def\@mylistauxiii[#1]#2[#3]#4\@nil{\@mylist@brackets{#1}{#2}{#3}}

%% final macros
\def\@mylist@parentheses#1{%
  % do something for the case (xxx)
  $(#1)_{E}$}
\def\@mylist@parentheses#1#2#3{%
  % do something for the case [a]xxx[b]
  % #1 is a, #2 is xxx, #3 is b
  $_{#1}#2_{#3}$
\def\@mylist@separator{\quad$\bullet$\quad}
\makeatother

\begin{document}
\mylist{1,[a]2,3[b],[c]444[d],(5)}
\end{document}

在此处输入图片描述

LaTeX3 实现更清楚地说明了如何使用收集的参数:

\usepackage{xparse}

\ExplSyntaxOn
\seq_new:N \l_tobi_list_seq
\tl_new:N \l_tobi_last_item_tl
\NewDocumentCommand{\mylist}{m}
  {
   \seq_set_split:Nnn \l_tobi_list_seq { , }{ #1 }
   \seq_pop_right:NN \l_tobi_list_seq \l_tobi_last_item_tl
   \seq_map_inline:Nn \l_tobi_list_seq { \tobi_process:w ##1 [ ] \q_stop \tobi_separator: }
   \exp_after:wN \tobi_process:w \l_tobi_last_item_tl [ ] \q_stop
  }
\cs_new:Npn \tobi_process:w 
  {
   \peek_charcode:NTF ( { \tobi_process_aux_i:w } { \tobi_process_aux_ii:w }
  }
\cs_new:Npn \tobi_process_aux_i:w ( #1 ) [ ] \q_stop
  {
   \tobi_final_parentheses:n { #1 }
  }
\cs_new:Npn \tobi_process_aux_ii:w
  {
   \peek_charcode:NTF [ { \tobi_process_aux_iii:w } { \tobi_process_aux_iii:w [ ] }
  }
\cs_new:Npn \tobi_process_aux_iii:w [ #1 ] #2 [ #3 ] #4 \q_stop
  {
   \tobi_final_brackets:nnn { #1 } { #2 } { #3 }
  }
%% Final macros
\cs_new:Npn \tobi_final_parentheses:n #1
  {
   $(#1)\sb{E}$
  }
\cs_new:Npn \tobi_final_brackets:nnn #1 #2 #3
  {
   $\sb{#1}#2\sb{#3}$
  }
\cs_new:Npn \tobi_separator: { \quad\textbullet\quad }
\ExplSyntaxOff

现在必须适当地定义\tobi_final_parentheses:n\tobi_final_brackets:nnn\tobi_separator:

没有必要使用这些名称:只需使用你喜欢的名称;代码可能是

\usepackage{xparse}

\ExplSyntaxOn
\seq_new:N \l_tobi_list_seq
\tl_new:N \l_tobi_last_item_tl
\NewDocumentCommand{\mylist}{m}
  {
   \seq_set_split:Nnn \l_tobi_list_seq { , }{ #1 }
   \seq_pop_right:NN \l_tobi_list_seq \l_tobi_last_item_tl
   \seq_map_inline:Nn \l_tobi_list_seq { \tobi_process:w ##1 [ ] \q_stop \mylistseparator }
   \exp_after:wN \tobi_process:w \l_tobi_last_item_tl [ ] \q_stop
  }
\cs_new:Npn \tobi_process:w 
  {
   \peek_charcode:NTF ( { \tobi_process_aux_i:w } { \tobi_process_aux_ii:w }
  }
\cs_new:Npn \tobi_process_aux_i:w ( #1 ) [ ] \q_stop
  {
   \mylistelementinparentheses { #1 }
  }
\cs_new:Npn \tobi_process_aux_ii:w
  {
   \peek_charcode:NTF [ { \tobi_process_aux_iii:w } { \tobi_process_aux_iii:w [ ] }
  }
\cs_new:Npn \tobi_process_aux_iii:w [ #1 ] #2 [ #3 ] #4 \q_stop
  {
   \mylistelementinbrackets { #1 } { #2 } { #3 }
  }
\ExplSyntaxOff

%% Final macros
\newcommand\mylistelementinparentheses[1]{$(#1)\sb{E}$}
\newcommand\mylistelementinbrackets[3]{$\sb{#1}#2\sb{#3}$}
\newcommand\mylistseparator {\quad\textbullet\quad}

因此,您可以按照习惯的方式定义制作宏的艰苦工作(当然,名称可以更改)。

编辑

如何定义\tobi_final_parentheses:n\tobi_final_brackets:nnn\tobi_separator:以获得所需的输出。如您所见,LaTeX3 语法允许非常轻松地管理空参数。

%% Final macros
% aaa -> \cite{aaa}
% [a]bbb -> \cite[][a]{bbb}
% ccc[b] -> \cite[b]{ccc}
% [c]ddd[d] -> \cite[c][d]{ddd}
% (eee) -> eee

\cs_new:Npn \tobi_final_parentheses:n #1 { #1 }
\cs_new:Npn \tobi_final_brackets:nnn #1 #2 #3
  {
   \tl_if_empty:nTF { #3 }
     {
      \tl_if_empty:nTF { #1 }
        { \cite{#2} }
        { \cite[][#1]{#2} }
     }
     {
      \tl_if_empty:nTF { #1 }
        { \cite[#3]{#2} }
        { \cite[#1][#3]{#2} }
     }
  }
\cs_new:Npn \tobi_separator: {, ~ }
\ExplSyntaxOff

答案2

如果luatex解决方案可行,那么您可以尝试lpeg解析器来实现您想要的。

首先lua是文件(将其另存为listparsing.lua)。请注意,我使用-2作为第一个参数来tex.sprint逐字打印内容。您必须将此值更改为以-1使用标准 catcode 机制。解析器仅接受单词(即字母连续),但可以轻松扩展到其他类型的单词(例如控制序列)。另一个可能的增强功能是检查括号是否匹配(有点困难,因为它意味着使用真正的语法)。

lpeg = require('lpeg')

local P, R, S, C, Cs, V = lpeg.P, lpeg.R, lpeg.S, lpeg.C, lpeg.Cs, lpeg.V
local match = lpeg.match

local space = S(' \n\t')
local lbracket, rbracket = P('['), P(']')
local lparen, rparen = P('('), P(')')
local comma = P(',') * space^0 / ', '

local letter = R('az') + R('AZ')
local word = letter^1

local digit = R('09')
local number = digit^1

local pretextpost = lbracket * C(word) * rbracket * C(word) * lbracket * C(word) * rbracket / function (a,b,c) return string.format('\\cite[%s][%s]{%s}',a,c,b) end
local pretext = lbracket * C(word) * rbracket * C(word) / function (a,b) return string.format('\\cite[][%s]{%s}',a,b) end
local posttext = C(word) * lbracket * C(word) * rbracket / function (a,b) return string.format('\\cite[%s]{%s}',b,a) end
local text = C(word) / function (a) return string.format('\\cite{%s}',a) end
local special = lparen * C(word) * rparen / function (a) return a  end
local pattern = pretextpost + pretext + posttext + text + special

local parser = Cs(pattern * (comma * pattern)^0)

function parse_and_texprint(s)
   return tex.sprint(-2,match(parser,s))
end

然后是tex文件。

\documentclass{standalone}
\directlua{dofile('listparsing.lua')}
\def\mylist#1{%
  \directlua{%
    parse_and_texprint('#1')}}
\begin{document}
\texttt{\mylist{aaa,[a]bbb,ccc[b],[c]ddd[d],(eee)}}
\end{document}

在此处输入图片描述

答案3

这不是最终的解决方案,因为我认为您的要求可能会变得非常复杂,但这表明需要构建一个有限状态机。出于演示目的,我将@tfor逐个字母扫描列表。我相信 LaTeX3(如 egreg 的解决方案所示)提供了更好的可能性。

\documentclass{article}
\begin{document}
\makeatletter
\newif\if@gather
\newif\if@store
\DeclareRobustCommand\temp{}
\let\@ex\expandafter
\edef\alist{[a]2[b],3[b],[c]4[d],(eee)}
\def\parse#1{%
\@ex\@tfor\@ex\next\@ex:\@ex=#1\do{%
    \@gathertrue\@storetrue
    \if\next,\@gathertrue\@storetrue\def\next{$\bullet$}\fi
    \if\next[\@gathertrue\@storefalse\fi
    \if\next]\@storefalse\@gathertrue\fi
    \if\next(\@gathertrue\@storefalse\fi 
    \if\next)\@storefalse\@gathertrue\fi
    \if@store\edef\temp{\temp\next}\fi
}}
\parse\alist
\temp
\end{document}

这种方法的优点是,除了更易读之外,您还可以捕获每个字母并将其存储在宏或标记列表中,或者对其进行其他操作。在示例中,我删除了所有[]()并将逗号替换为$\bullet。要实现您想要的效果并使其能够可靠地检查错误和边缘情况,还需要做更多的工作。可以使用 构建一个更好、更强大的扫描器\futurelet。在这方面,您可以从soul具有出色解析器的包中借用代码,也可以使用\SOUL@everytoken宏。

答案4

这是一个使用技巧的简单解决方案biblatex

\documentclass{scrartcl}

\usepackage[style=numeric,backend=biber]{biblatex}
\addbibresource{biblatex-examples.bib}

\usepackage{xcolor}

\newcommand{\cfwrapper}[1]{
   \colorbox{green!50!black}{%
      \color{white}\sffamily
      \textbf{i}\enskip
      #1
   }%
}
\DeclareMultiCiteCommand{\cf}[\cfwrapper]{\cite}{, }
\DeclareFieldFormat{postnote}{(#1)}
\renewcommand{\postnotedelim}{ }

\begin{document}
Text \cf{kastenholz,hyman}[S. 2]{markey}
\end{document}

它在大多数情况下都能正常工作,但它缺少一种绕过命令的方法。就像上述问题和答案中\cite括号中的列表元素一样。(pass it)

相关内容