我想为 LaTeX 编写一个类似 XML 的键值解析器。也就是说,键值对用空格分隔,值与键用 = 分隔并用双引号封装。以下最小示例就是这样做的:
\documentclass[english]{book}
\usepackage{babel}
\makeatletter
\def\tp@parse@attributes#1\@nil{%
\if!#1!\else\@tp@parse@attributes#1 ="" =""\@nil\fi}
\def\@tp@parse@attributes#1="#2" #3=""\@nil{%
\edef\@argi{#1}\edef\@argiii{#3}%
\ifx\@argi\@empty\else%
\ifx\@argi\space\else%
\expandafter\def\csname tp@attr@#1\endcsname{#2}%
\ifx\@argiii\@empty\else
\ifx\@argiii\space\else
\def\@tempa{="" }%
\ifx\@argiii\@tempa\else%
\expandafter\@tp@parse@attributes #3=""\@nil%
\fi\fi\fi\fi\fi}%
\def\test{\@ifnextchar [\@test{\@test[class="default"]}}%]
\def\@test[#1]#2{%
\tp@parse@attributes#1\@nil
Class: {\ttfamily\tp@attr@class}\space
Content: {\sffamily #2}\par
\let\tp@attr@class\relax}% reset class for next instance
\parindent\z@
\makeatother
\begin{document}
1st test: \test{Content}
2nd test: \test[class="foobar"]{Content}
\end{document}
只要文档语言是英语。
为了德语文本,相同的代码引发了错误,! Argument of \language@active@arg" has an extra }.
我发现 babel 简写是罪魁祸首,因此我尝试了 catcodes 并得出了以下代码(…
意味着该代码与上面的 MWE 中的代码相同):
\documentclass[german]{book}
\usepackage{babel}
\makeatletter
\begingroup
\catcode`\"=\active
\gdef\tp@parse@attributes#1\@nil{…}
\gdef\@tp@parse@attributes#1="#2" #3=""\@nil{…}%
\gdef\test{…}%
\gdef\@test[#1]#2{…}
\endgroup
\parindent\z@
\makeatother
\begin{document}
…
\end{document}
不幸的是,当(唯一)语言是英语(! Undefined control sequence.^^J<argument> ="
)。然而,确实有效什么时候两种语言已加载 ( \documentclass[english,german]{book}
)
如果语言土耳其被添加(或单一语言),则这些选项均不起作用。
我也尝试使用 在本地关闭 babel 简写\shorthandoff{"=}
,但由于我不知道实际加载了哪些语言,所以我无法预测哪些字符是活动的简写,哪些不是。
我的问题是:我必须如何修改我的代码才能使其适用于所有语言, 和无需全局停用 Babel 简写?
答案1
最常见的必须处理babel
键值列表中的活动字符的情况是土耳其语,其中=
和是活动的。对于和,,
可以应用相同的技术- 这只是增加一个额外步骤的问题。"
=
,
基本上有两种可能的方法,它们实际上只是同一个想法的变体:我们只需要查找具有正确类别代码的分隔参数。因此,你要么
首先解析原始列表
=
,并用相同 catcode 的“其他”标记替换“活动”,
,"
然后仅使用“其他”catcode 进行键值解析使用两步法解析原始列表,首先查找每个标记的“活动”版本,然后查找“其他”版本
在确定要做什么时需要考虑一些性能问题,我建议查看expkv
实现。我认为从概念层面来看,最简单的方法可能是先替换所有内容,然后进行简单搜索。作为概念验证,我将通过使用 的expl3
搜索和替换功能来演示这一点 - 这是不是最有效的方法,但它将展示一种可能的方法
\documentclass[german]{book}
\usepackage{babel}
\ExplSyntaxOn
\cs_new_eq:NN \ReplaceAll \tl_replace_all:Nnn
\ExplSyntaxOff
\makeatletter
\begingroup
\catcode`\"=\active
\xdef\tp@parse@attributes#1\@nil{%
\def\noexpand\@tempa{#1}%
\ReplaceAll\noexpand\@tempa{\noexpand"}{\string"}%
\noexpand\expandafter\noexpand\tp@parse@attributes@aux
\noexpand\expandafter{\noexpand\@tempa}%
}
\endgroup
\def\tp@parse@attributes@aux#1{%
\if!#1!\else\@tp@parse@attributes#1 ="" =""\@nil\fi}
\def\@tp@parse@attributes#1="#2" #3=""\@nil{%
\edef\@argi{#1}\edef\@argiii{#3}%
\ifx\@argi\@empty\else%
\ifx\@argi\space\else%
\expandafter\def\csname tp@attr@#1\endcsname{#2}%
\ifx\@argiii\@empty\else
\ifx\@argiii\space\else
\def\@tempa{="" }%
\ifx\@argiii\@tempa\else%
\expandafter\@tp@parse@attributes #3=""\@nil%
\fi\fi\fi\fi\fi}%
\def\test{\@ifnextchar [\@test{\@test[class="default"]}}%]
\def\@test[#1]#2{%
\tp@parse@attributes#1\@nil
Class: {\ttfamily\tp@attr@class}\space
Content: {\sffamily #2}\par
\let\tp@attr@class\relax}% reset class for next instance
\parindent\z@
\makeatother
\begin{document}
1st test: \test{Content}
2nd test: \test[class="foobar"]{Content}
\end{document}