我正在使用refcount
包裹获取与标签关联的参考编号。我想确定两个参考是否“相邻”,也就是说:我想要一个可扩展命令形式:
\comparerefs{refOne}{refTwo}
0
如果refOne
等于refTwo
,则会产生1
refOne
就在之前 refTwo
,2
如果refOne
是之后立马 refTwo
,以及3
其他任何情况。
仅仅调用\getrefnumber
(从refcount
包中),将结果保存到计数器中并加/减 1 不起作用,原因有二:
- 如果
refOne
指的是某个部分(并且我们正在使用该类book
),则\getrefnumber
返回1.1
并设置计数器中断。 - 即使只获取章节编号,也必须小心,因为第 1 章第 1 节是不是紧接在第 2 章第 2 节之前。
这显然应该与“更深层次”的引用(即对小节等的引用)一起使用。
需要澄清的是:假设refOne
形式为a.b.c
,且refTwo
形式为d.e.f
,则如果= 、=和= ,\comparerefs{refOne}{refTwo}
则应返回;如果= 、=和= ,则应返回;如果= 、=和= ,则应返回;如果!= 、或!= 、或!= 、,或者和的长度不同,则应返回。0
a
d
b
e
c
f
\comparerefs{refOne}{refTwo}
1
a
d
b
e
c
f - 1
\comparerefs{refOne}{refTwo}
2
a
d
b
e
c
f + 1
\comparerefs{refOne}{refTwo}
3
a
d
b
e
c
f - 1
f
f + 1
refOne
refTwo
有想法吗?
答案1
第一个答案
\documentclass[a4paper]{book}
\usepackage{xparse,refcount,etoolbox}
\newcounter{comparerefs}
\newcommand{\comparerefs}[2]{%
\begingroup\edef\x{\endgroup
\noexpand\docomparerefs{\getrefnumber{#1}}{\getrefnumber{#2}}}\x}
\NewDocumentCommand{\docomparerefs}{ >{\SplitArgument{2}{.}}m >{\SplitArgument{2}{.}}m }{
\xcomparerefs#1#2}
\newcommand\xcomparerefs[6]{%
\ifboolexpr{ test {\ifstrequal{#1}{0}} or test {\ifstrequal{#4}{0}} }% reference is undefined
{\setcounter{comparerefs}{-1}}
{\ycomparerefs{#1}{#2}{#3}{#4}{#5}{#6}}%
}
\newcommand{\ycomparerefs}[6]{%
\ifboolexpr{ test {\ifstrequal{#3}{\NoValue}} and test {\ifstrequal{#6}{\NoValue}} }
{\zcomparerefs{#1}{#2}{#4}{#5}}
{\subseccomparerefs{#1}{#2}{#3}{#4}{#5}{#6}}%
}
\newcommand{\zcomparerefs}[4]{%
\ifboolexpr{ test {\ifstrequal{#2}{\NoValue}} and test {\ifstrequal{#4}{\NoValue}} }
{\chapcomparerefs{#1}{#3}}
{\seccomparerefs{#1}{#2}{#3}{#4}}%
}
\newcommand{\chapcomparerefs}[2]{%
\ifnum#1=#2\relax
\setcounter{comparerefs}{0}%
\else
\ifnum#1=\numexpr#2-1\relax\relax
\setcounter{comparerefs}{1}%
\else
\ifnum#1=\numexpr#2+1\relax\relax
\setcounter{comparerefs}{2}%
\else
\setcounter{comparerefs}{3}%
\fi
\fi
\fi
}
\newcommand{\seccomparerefs}[4]{%
\ifnum#1=#3\relax
\chapcomparerefs{#2}{#4}%
\else
\setcounter{comparerefs}{3}%
\fi}
\newcommand{\subseccomparerefs}[6]{%
\ifnum#1=#4\relax
\seccomparerefs{#2}{#3}{#5}{#6}%
\else
\setcounter{comparerefs}{3}%
\fi
}
\begin{document}
\chapter{Abc}\label{x}
\section{Abc}
\subsection{Def}\label{A}
\subsection{Ghi}\label{B}
\section{XXX}
\subsection{UUU}\label{C}
\chapter{YYY}\label{y}
\chapter{ZZZ}\label{z}
%\comparerefs{x}{x}\showthe\value{comparerefs}
%\comparerefs{x}{y}\showthe\value{comparerefs}
%\comparerefs{y}{x}\showthe\value{comparerefs}
%\comparerefs{x}{z}\showthe\value{comparerefs}
\comparerefs{A}{A}\showthe\value{comparerefs}
\comparerefs{A}{B}\showthe\value{comparerefs}
\comparerefs{B}{A}\showthe\value{comparerefs}
\comparerefs{A}{C}\showthe\value{comparerefs}
\end{document}
我使用的\SplitArgument
功能解析获取已拆分成组件的引用。我只支持最多三个级别(子部分),并且没有检查引用是否不兼容。
如果其中一个引用尚未解析,则计数器将设置为 -1。
首先,我们决定引用的是章节、节还是小节。因此,\chapcomparerefs
是主要的,并将计数器设置为请求的值。如果是,\seccomparerefs
我们检查第一个组件是否相等,然后将控制权传递给\chapcomparerefs
第二个组件,否则我们将计数器设置为 3。同样,\subseccomparerefs
如果第一个组件不同,则将计数器设置为 3,否则调用\seccomparerefs
。
如果您比较不同类型的参考文献,就会出现“缺失数字”错误。
第二个答案
\documentclass[a4paper]{book}
\usepackage{xparse,refcount,etoolbox}
\newcounter{comparerefs}
\newcommand{\comparerefs}[2]{%
\begingroup\edef\x{\endgroup
\noexpand\docomparerefs{\getrefnumber{#1}}{\getrefnumber{#2}}}\x}
\NewDocumentCommand{\docomparerefs}{ >{\SplitArgument{20}{.}}m >{\SplitArgument{20}{.}}m }{
\setcounter{comparerefs}{0}\begingroup
\def\NoValue{-1000}\edef\x{\endgroup
\noexpand\xcomparerefs#1\relax#2\relax}\x}
\def\xcomparerefs#1#2#3\relax#4#5#6\relax{%
\ycomparerefs{#1}{#2}{#4}{#5}{{#2}#3\relax{#5}#6\relax}}
\def\ycomparerefs#1#2#3#4#5{%
\ifboolexpr{ test {\ifnumcomp{#1}{=}{#3}} and
not ( test {\ifnumcomp{#2}{=}{-1000}} and test {\ifnumcomp{#4}{=}{-1000}} ) }
{\xcomparerefs#5}
{\decide{#1}{#2}{#3}{#4}}%
}
\def\decide#1#2#3#4{%
\ifboolexpr{ test {\ifnumcomp{#2}{=}{-1000}} and test {\ifnumcomp{#4}{=}{-1000}} }
{\ifnumcomp{#1}{=}{#3}{}{\xdecide{#1}{#3}}}
{\ifnumcomp{#1}{=}{#3}{\xdecide{#2}{#4}}{\setcounter{comparerefs}{3}}}%
}
\def\xdecide#1#2{%
\ifnumcomp{#1}{=}{#2-1}
{\setcounter{comparerefs}{1}}
{\ifnumcomp{#1}{=}{#2+1}
{\setcounter{comparerefs}{2}}
{\setcounter{comparerefs}{3}}%
}
}
\begin{document}
\chapter{Abc}\label{x}
\section{Abc}
\subsection{Def}\label{A}
\subsection{Ghi}\label{B}
\section{XXX}
\subsection{UUU}\label{C}
\chapter{YYY}\label{y}
\chapter{ZZZ}\label{z}
\comparerefs{x}{x}\showthe\value{comparerefs}
\comparerefs{x}{y}\showthe\value{comparerefs}
\comparerefs{y}{x}\showthe\value{comparerefs}
\comparerefs{x}{z}\showthe\value{comparerefs}
\comparerefs{A}{A}\showthe\value{comparerefs}
\comparerefs{A}{B}\showthe\value{comparerefs}
\comparerefs{B}{A}\showthe\value{comparerefs}
\comparerefs{A}{C}\showthe\value{comparerefs}
\comparerefs{A}{x}\showthe\value{comparerefs}
\end{document}
这些宏最多允许 20 个级别(应该足够了)。再次解析用于从引用中获取两个参数列表。我假设没有引用值为 -1000。测试结果存储在计数器中comparerefs
。
第三个答案
这是第二个答案的可扩展版本(将\edef
证明这一断言)。作为副产品,字符串的长度是无限的,只要它们都是规定的形式,即用句点分隔的数字。所有数字都不应为 -1000,这是用于终止递归的特殊值。
\documentclass[a4paper]{book}
\usepackage{refcount,etoolbox}
\newcommand{\comparerefs}[2]{%
\expandafter\comparerefsA\expandafter{\romannumeral-`a\getrefnumber{#2}}{#1}}
\def\comparerefsA#1#2{%
\expandafter\comparerefsB\expandafter{\romannumeral-`a\getrefnumber{#2}}{#1}}
\def\comparerefsB#1#2{%
\xcomparerefs#1.-1000\relax#2.-1000\relax}
\def\xcomparerefs#1.#2.#3\relax#4.#5.#6\relax{%
\ycomparerefs{#1}{#2}{#4}{#5}{#2.#3.-1000.-1000\relax#5.#6.-1000.-1000\relax}}
\def\ycomparerefs#1#2#3#4#5{%
\ifboolexpe{ test {\ifnumcomp{#1}{=}{#3}} and
not ( test {\ifnumcomp{#2}{=}{-1000}} and test {\ifnumcomp{#4}{=}{-1000}} ) }
{\xcomparerefs#5}
{\decide{#1}{#2}{#3}{#4}}%
}
\def\decide#1#2#3#4{%
\ifboolexpe{ test {\ifnumcomp{#2}{=}{-1000}} and test {\ifnumcomp{#4}{=}{-1000}} }
{\ifnumcomp{#1}{=}{#3}{0}{\xdecide{#1}{#3}}}
{\ifnumcomp{#1}{=}{#3}{\xdecide{#2}{#4}}{3}}%
}
\def\xdecide#1#2{%
\ifnumcomp{#1}{=}{#2-1}
{1}
{\ifnumcomp{#1}{=}{#2+1}
{2}
{3}%
}
}
\begin{document}
\chapter{Abc}\label{x}
\section{Abc}
\subsection{Def}\label{A}
\subsection{Ghi}\label{B}
\section{XXX}
\subsection{UUU}\label{C}
\chapter{YYY}\label{y}
\chapter{ZZZ}\label{z}
\comparerefs{x}{y}\par
\comparerefs{y}{x}\par
\comparerefs{x}{z}\par
\comparerefs{A}{A}\par
\comparerefs{A}{B}\par
\comparerefs{B}{A}\par
\comparerefs{A}{C}\par
\comparerefs{A}{x}\par
\end{document}
对“可扩展”解决方案的评论
宏\comparerefs
读取其参数,并首先尝试\getrefnumber
从中获取扩展。我使用了\romannumeral-`a
两次技巧:\expandafter
将达到\romannumeral
,因此从\comparerefs{X}{Y}
我们得到
\comparerefsA{<expansion of \getrefnumber{Y}}{X}
将扩大到
\expandafter\comparerefsB\expandafter
{\romannumeral-`a\getrefnumber{X}}
{<expansion of \getrefnumber{Y}}
将成为
\comparerefsB{<expansion of \getrefnumber{X}}{<expansion of \getrefnumber{Y}}
假设\getrefnumber{X}
扩展为1.1.1
并\getrefnumber{Y}
扩展为1.2
(不同长度);在这种情况下,连续扩展将是
\xcomparerefs 1.1.1.-1000.-1000\relax 1.2.-1000.-1000\relax
宏\xcomparerefs
只需选择要比较的数字,我们就会得到
\ycomparerefs{1}{1}{1}{2}{1.1.-1000\relax 2.-1000\relax}
第五个参数将在启动递归的情况下使用,并再次输入\xcomparerefs
(我们-1000
在末尾添加了一个以确保数字永远不会用完,因为两个序列的长度可以不同)。
#1=#3
如果和都不同于 -1000,则开始递归:在这种情况下,我们无法确定字典顺序,因此我们砍掉第一个组件并重新#2
开始。#4
当上述条件不满足时,将发生最后阶段。如果和#2
都是#4
-1000,则如果#1
和#2
相等(两个原始序列确实相等),我们输出 0。否则,我们通过比较#1
和来选择要输出的数字#3
。
如果#2
和#4
不都是 -1000,我们再次比较#1
和#3
:如果它们不同,则这两个序列“彼此相距甚远”,我们输出 3;否则我们通过比较#2
和来选择输出#4
。
决策宏查看这两个数字,如果第一个数字比第二个数字小一,则输出 1;如果第一个数字比第二个数字多一,则输出 2;其他所有情况下,则输出 3。
\ifboolexpe
由于宏提供,所有内容均可扩展电子工具箱。
答案2
这是我的必pgfkeys
答。它能满足您的要求,但缺少可扩展性:它接受任意长度(可能不相等)的引用字符串,并根据它们是否相等、在最后一个组件中相邻或其他情况返回 0、1、2 或 3。我确实假设它们的数字高于 -1000:您没有任何非常负数的部分,对吗?
工作原理:高级
代码分为两部分:首先,我将字符串(例如
1.2.3
和4.5.6
)转换为我更喜欢的形式。我很早就决定使用来\foreach
进行循环,它允许“并行处理”形式\x/\y in {1/4, 2/5, 3/6}
,因此我需要沿句点拆分引用,然后通过匹配相应的部分(用斜线分隔)重新组合它们。此操作还使我有机会修复不等长引用:我决定,给定
1.2.3
和1.1
,对将是1/1, 2/1, 3/-1000
。这就是为什么我假设部分相当大。代码的第二部分是比较。它的工作原理正如您所想的那样:给定一个形式为 的 foreach 循环
\x/\y = {1/1, etc.}
,我只需依次检查如何\x
和\y
进行比较并相应地设置结果。我还检查我之前是否已经确定引用是不等:在这种情况下,结果始终为 3,因为这意味着它们在结尾之外的某个地方有所不同。如果其中一个比另一个短,则始终会触发此操作。
现在你可以试着跟着代码走。如果你遇到困难,后面还有更多解释:
\documentclass{article}
\usepackage{pgfkeys,pgffor,etoolbox}
\newcommand\comparerefs[2]{%
\pgfkeys{
/split and combine,
reset,
do = {#1.\Stop.}{#2.\Stop.},
}%
\pgfkeys{
/lexicographic,
compare adjacent/.expanded = {\pgfkeysvalueof{/split and combine/list of pairs}},
}%
}
% To protect against \edef
\def\Stop{\noexpand\Stop}
\pgfkeys{
/split and combine/.is family, /split and combine,
do/.code 2 args = {%
\GetPeriod{\beforeone}{\afterone}#1%
\GetPeriod{\beforetwo}{\aftertwo}#2%
\ifdefempty{\beforeone}
{%
\ifdefempty{\beforetwo}
{\pgfkeysalso{next/.style = {}}}
{\pgfkeysalso{list of pairs/.append/.expanded = {,-1000/\beforetwo}}}%
}
{%
\ifdefempty{\beforetwo}
{\pgfkeysalso{list of pairs/.append/.expanded = {,\beforeone/-1000}}}
{\pgfkeysalso{list of pairs/.append/.expanded = {,\beforeone/\beforetwo}}}%
}%
\pgfkeysalso{next}%
},
reset/.style = {
list of pairs/.initial = {0/0},
next/.style = loop,
},
loop/.style = {do/.expanded = {\afterone}{\aftertwo},},
}
% Low-level stuff
\makeatletter
\def\GetPeriod#1#2#3.{%
\ifx\Stop#3%
\expandafter\@firstoftwo
\else
\expandafter\@secondoftwo
\fi
{\def#1{}\def#2{\Stop.}}
{\def#1{#3}\GetToStop#2}%
}
\def\GetToStop#1#2\Stop.{%
\def#1{#2\Stop.}%
}
\makeatother
\pgfkeys{
/handlers/.list pairs/.code = {%
\edef\thiskey{\pgfkeyscurrentpath}%
\foreach \x/\y in {#1} {%
\globaldefs=1%
\pgfkeysalso{\thiskey/.expanded = {\x}{\y}}%
\globaldefs=0%
}%
}
}
\pgfkeys{
/lexicographic/.is family, /lexicographic,
compare adjacent/.style = {
result/.initial = 0,
compare one/.list pairs = {#1},
result
},
compare one/.code 2 args = {%
\ifnumequal{\pgfkeysvalueof{/lexicographic/result}}{0}
{%
\ifnumequal{#1}{#2}
{\pgfkeysalso{result = 0}}
{%
\ifnumequal{#1 + 1}{#2}
{\pgfkeysalso{result = 1}}
{%
\ifnumequal{#1}{#2 + 1}
{\pgfkeysalso{result = 2}}
{\pgfkeysalso{result = 3}}
}%
}%
}%
{%
\pgfkeysalso{result = 3}%
\breakforeach
}%
},
}
\begin{document}
\setlength\parindent{0pt}
\comparerefs{1}{1}
\comparerefs{1}{2}
\comparerefs{2}{1}
\comparerefs{3}{1}
\comparerefs{1.2}{1.2}
\comparerefs{1.2}{1.3}
\comparerefs{1.3}{1.2}
\comparerefs{1.3}{1.1}
\comparerefs{1.2.3}{2.2.3}
\comparerefs{1.2.3}{1.3.3}
\comparerefs{1.2.3}{1.1}
\end{document}
工作原理:低级
我的风格pgfkeys
是将所有内容放入适当命名的系列中,因此我们有两个系列:/split and combine
第一阶段和/lexicographic
第二阶段(我猜这是一种字典顺序)。
工作原理如下/split and combine
:
它是递归的:只有一个键,
do
它获取每个引用的第一个组件,将它们连接起来(-1
根据需要插入),然后如果任一引用仍有更多部分,则调用自身。pgfkeys
获取操作由非宏完成\GetPeriod
,它使用分隔参数进行一些模式匹配\def
。它的工作原理与您预期的一样,只是如果它碰巧发现引用超出部分,它不会将该after
部分清空:它会重新放入终止标志并返回它。这样,递归就可以顺利地继续,直到两个都before
部分是空的。代码中还有我最喜欢的技巧:
\Stop
是递归的,但同时也防止扩展!这样,如果\Stop
出现在 中\edef
,它会扩展为\noexpand\Stop
,然后扩展为\Stop
,最后停止。这样,我就可以完全扩展after
递归时的各个部分,而不必担心无限循环或者关于\Stop
下次重新插入。尽管我一直在推动将其pgfkeys
作为 TeX 编程的更好替代品,但这就是让 TeX 变得有趣的事情。
工作原理如下/lexicographic
:
compare adjacent
针对每对单独的参考号重复调用compare one
。就是这样。我确实必须去添加一个处理程序
.list pairs
,因为pgfkeys
默认情况下没有“斜线”语法的接口,这给我带来了很多麻烦,因为\foreach
它将其主体埋在组内,因此所有内容都是本地的。result
但是,我需要一劳永逸地设置,因此必须覆盖它;解决方案,正如我从答案中学到的有没有办法使用“pgfkeys”设置*全局*键值?,就是设置\globaldefs=1
。我尽量不要做得过分;毫无疑问,我已经像这样破坏了保存堆栈。
我对过度使用条件语句有些不满。我真的很想使用pgfkeys
' 键测试逻辑 ( .try
/ .retry
) 来实现if/then/else
语句,但效果并不好。当然,可以定义一个非常通用的 " if, then, else
" 键来执行此操作,但我太懒了,只是把它变成了代码。不过,我仍然从中受益匪浅pgfkeys
,因为它的.expanded
处理程序简单,语法也很好。只是没有我想要的那么多。