防止 Babel 简写干扰 Catcode 更改

防止 Babel 简写干扰 Catcode 更改

我想为 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}

相关内容