我正在创建一份文档,其中在第一章中我定义了一些概念 - 每个概念都有自己专门的段落(例如:概念一、概念二、概念三等) - 并且在所有其他章节中我应用我在第一章中定义的概念来分析事物。
我想在每个“概念段落”末尾添加一些内容,例如“要了解此概念的应用,请参阅第 13、19、55 页……”
通常的做法是给文档的各个部分添加标签,然后\pageref
在相应的“概念段落”下为每个部分添加一个。但是,我想通过反转方法来实现自动化,在其他章节中执行类似
\applyconcept{concept-three}As you can see, “concept three” applies to this case ...
...
\applyconcept{concept-four}As you can see, “concept four” applies to this case ...
etc.
这样每次\applyconcept
调用宏时它都会填充第一章中相应的“概念段落”。
我试图做的基本上是创建单独的索引。不幸的是,通常的索引包并不像我希望的那样可定制,因此我尝试破解multind.sty
和amsmidx.sty
以便将\index
宏重命名为\applyconcept
,但我发现即使是原始\index
宏也不适合我的情况。
有人能告诉我如何从头开始实现一个非常基本的索引吗?
答案1
你可以使用我的参考文献此类软件包。遗憾的是,CTAN 上没有该软件包,因此您需要自行安装。
它的灵感来自于分布式文件框架,因此您可以声明可以设置各种属性的对象。 在您的例子中,每次使用\applyconcept
都应创建一个新对象,并应将概念名称和页码设置为该对象的属性。
然后,您可以定义另一个命令,例如命名为\conceptpages
,它将搜索具有给定概念名称的所有对象,并打印使用它们的页码。
我创建了一个简单的包,applyconcept.sty
它在以下的帮助下实现了这些命令rdfref
:
\ProvidesPackage{applyconcept}
\RequirePackage{rdfref-user}
\RequirePackage{rdfref-query}
\RequirePackage{hyperref}
\newcommand\applyconcept[1]{%
% define a new object, with anonymous name
\BlankNode%
% set basic object properties
% this identifies the current object as the con:concept type
\AddProperty{rdf:type}{con:concept}%
% save the concept name, so it can be later looked up
\AddProperty{con:name}{#1}%
% save the page number for this concept
\AddPropertyEx{doc:pageNo}{\thepage}%
% define target for hyperlink, using the current object
% name as a label.
\hypertarget{\CurrentObject}{}%
}
\newcommand\conceptpages[1]{%
% we will want to separate page numbers with commans, but not the first one
\def\currentseparator{}%
% process all objects with the con:name property equal to the parameter
% it executes the code for each object that matches the condition
\Bind{?obj}{con:name}{#1}{%
% the object name is saved in the ?obj variable, we can get it's value and save it to a macro
% for a simpler access:
\edef\currobject{\GetVal{?obj}}%
% print the page number with link to the page%
\currentseparator\hyperlink{\currobject}{\GetProperty{\currobject}{doc:pageNo}}%%
% the subsequent pages will be separated using comma and space
\gdef\currentseparator{,\space}%
}%
}
\endinput
它定义如下命令:
\newcommand\applyconcept[1]{%
% define a new object, with anonymous name
\BlankNode%
% set basic object properties
% this identifies the current object as the con:concept type
\AddProperty{rdf:type}{con:concept}%
% save the concept name, so it can be later looked up
\AddProperty{con:name}{#1}%
% save the page number for this concept
\AddPropertyEx{doc:pageNo}{\thepage}%
% define target for hyperlink, using the current object
% name as a label.
\hypertarget{\CurrentObject}{}%
}
首先,它使用命令定义一个新的匿名对象\BlankNode
,然后使用\AddProperty
或\AddPropertyEx
(用于扩展值)添加各种属性。
属性rdf:type
应该用于所有对象,以标识对象类型。在 RDF 中,属性被命名为prefix:name
,因此我坚持使用这个名称,即使它对于我们的使用来说并不是真正必要的。
属性con:name
是概念名称。必须保存它,以便能够使用查询语言搜索此概念的所有用法。
我还添加了\hypertarget
命令,这样就可以将超链接准确地链接到定义概念的地方。该\CurrentObject
命令保存当前对象名称,通常类似于_:blank1
匿名对象。我们将在查询中获取对象名称,因此我们将能够链接到该位置。
现在查询命令:
\newcommand\conceptpages[1]{%
% we will want to separate page numbers with commans, but not the first one
\def\currentseparator{}%
% process all objects with the con:name property equal to the parameter
% it executes the code for each object that matches the condition
\Bind{?obj}{con:name}{#1}{%
% the object name is saved in the ?obj variable, we can get it's value and save it to a macro
% for a simpler access:
\edef\currobject{\GetVal{?obj}}%
% print the page number with link to the page%
\currentseparator\hyperlink{\currobject}{\GetProperty{\currobject}{doc:pageNo}}%%
% the subsequent pages will be separated using comma and space
\gdef\currentseparator{,\space}%
}%
}
该\currentseparator
命令用于正确处理分隔逗号,因此它并不是很有趣。
该\Bind
命令搜索查询。它需要四个参数:
\Bind{object}{property}{value}{code to be executed}
在我们的例子中,我们知道我们想要搜索具有属性的对象,该属性con:name
的值作为参数传递(例如concept-three
)。?obj
在对象位置使用的 表示该对象未知。该\Bind
命令将循环遍历所有对象,测试它们是否具有给定的属性和值,并在匹配时执行代码。然后我们可以使用该\GetVal{?obj}
命令获取对象名称。
然后,我们可以将对象名称用于两个任务:用于超链接和获取页码。为了方便使用,我在命令中保存了对象名称\currobject
。然后,我们可以使用以下命令打印超链接和页码:
\hyperlink{\currobject}{\GetProperty{\currobject}{doc:pageNo}}
该包可以像这样使用:
\documentclass{article}
\usepackage{applyconcept}
\begin{document}
\applyconcept{concept-three}As you can see, “concept three” applies to this case ...
\applyconcept{concept-four}As you can see, “concept four” applies to this case ...
\newpage
\applyconcept{concept-three}Apply concept three again.
To see this concept applied, please see pp.~\conceptpages{concept-three}.
\end{document}
结果如下:
答案2
强调“非常基本”的要求,以下代码实现了\applyconcept
。
这个想法是使用宏\conceptone
等\concepttwo
,每次\applyconcept
调用时当前页码都会添加到该宏的先前内容中。
最后,为每个概念创建一个标签,其中包含概念宏的全部内容。然后,此标签将用于\ref
介绍性段落。
从代码角度来说,基本思想是:
\def\someconcept{\someconcept,\thepage}
但是,这是一个递归定义。为了避免循环,应该扩展定义,即,\someconcept
应该用实际值替换第二次出现的 以避免递归。为此,您可以使用\edef
(expanded def)。在这种情况下,我们需要全局范围,为此\xdef
使用 (global expand def):
\xdef\someconcept{\someconcept,\thepage}
现在这应该进入 的宏定义\applyconcept
。在此定义中,参数#1
应该在 内使用,\xdef
而不是上面的名称\someconcept
。为了使参数成为#1
的有效输入\xdef
,需要将其转换为控制序列。这是通过 完成的\csname [name of the macro] \endcsname
。所以:
\xdef\csname#1\endcsname{\csname#1\endcsname, \thepage}
最后一步是确保\xdef
不尝试定义\csname
,即,定义被延迟,直到\csname
完成构造宏名为止。这是通过以下方式完成的\expandafter
:
\expandafter\xdef\csname#1\endcsname{\csname#1\endcsname, \thepage}
一个细节是,第一次提到概念时不应包含先前的内容和逗号。为了保持代码整洁,我们可以使用包\ifcsvoid
中的etoolbox
代码,它检查控制序列是否存在且不为空,并允许在存在或不存在控制序列时执行不同的代码。
最后需要填充标签。这可以通过定义 来完成\@currentlabel
。设置此宏后,所有后续常规\label
语句都将采用 的值\@currentlabel
。
完整 MWE:
\documentclass{article}
\usepackage{etoolbox}
\def\applyconcept#1{%
\ifcsvoid{#1}{%
\expandafter\xdef\csname#1\endcsname{\thepage}%
}{%
\expandafter\xdef\csname#1\endcsname{\csname#1\endcsname, \thepage}%
}%
}
\begin{document}
This is concept one. To see this concept applied, please see pp. \ref{conceptonelabel}.
This is concept four. To see this concept applied, please see pp. \ref{conceptfourlabel}.
\setcounter{page}{13}
\par This is page \thepage. \applyconcept{conceptone}As you can see, ``concept one'' applies here.
\setcounter{page}{15}
\par This is page \thepage. \applyconcept{conceptfour}As you can see, ``concept four'' applies here.
\setcounter{page}{19}
\par This is page \thepage. \applyconcept{conceptone}As you can see, ``concept one'' applies here.
\setcounter{page}{26}
\par This is page \thepage. \applyconcept{conceptfour}As you can see, ``concept four'' applies here.
\setcounter{page}{55}
\par This is page \thepage. \applyconcept{conceptone}As you can see, ``concept one'' applies here.
\makeatletter
\def\@currentlabel{\conceptone}\label{conceptonelabel}
\def\@currentlabel{\conceptfour}\label{conceptfourlabel}
\makeatother
\end{document}
结果:
然而,您可能需要的是一个词汇表,glossaries
可以使用该包:
\documentclass{article}
\usepackage[sort=def, nopostdot]{glossaries}
\makeglossaries
\newglossaryentry{conceptone}
{
name=Concept one,
description={This is an important concept. It is the first one, and therefore the most important of all concepts. See pages:}
}
\newglossaryentry{conceptfour}
{
name=Concept four,
description={This is not a very important concept. It is only the fourth concept, the first three are clearly more important. See pages:}
}
\begin{document}
\printglossary[type=main,title={Definition of concepts}]
\setcounter{page}{13}
\par This is page \thepage. As you can see, \gls{conceptone} applies here.
\newpage
\setcounter{page}{15}
\par This is page \thepage. As you can see, \gls{conceptfour} applies here.
\newpage
\setcounter{page}{19}
\par This is page \thepage. As you can see, \gls{conceptone} applies here.
\newpage
\setcounter{page}{26}
\par This is page \thepage. As you can see, \gls{conceptfour} applies here.
\newpage
\setcounter{page}{55}
\par This is page \thepage. As you can see, \gls{conceptone} applies here.
\end{document}
答案3
这是一个实现。
环境concept
设置一个 clist 变量,其名称取决于参数(概念的常规名称,不一定是确切的名称)。
此类 clist 由命令填充\applyconcept
。在文档末尾我们设置另一个 clist 并将其保存在文件中.aux
,以便在下次运行时可以在相关环境末尾使用它concept
。
所需的标签由 自行生成\applyconcept
。
\documentclass{book}
\usepackage{lipsum}
\ExplSyntaxOn
\NewDocumentEnvironment{concept}{m}
{
\clist_new:c { g__madmurphy_concept_in_#1_clist }
\seq_gput_right:Nn \g__madmurphy_concept_list_seq { #1 }
}
{
To~see~this~concept~applied,~please~see~pages~\__madmurphy_concept_show:n { #1 }.
\par\addvspace{\topsep}
}
\NewDocumentCommand{\applyconcept}{m}
{
\int_gincr:N \g__madmurphy_concept_int
\par
\label{concept @ \int_to_arabic:n { \g__madmurphy_concept_int }}
\clist_gput_right:cx { g__madmurphy_concept_in_#1_clist }
{
\exp_not:N \pageref{concept @ \int_to_arabic:n { \g__madmurphy_concept_int }}
}
}
\int_new:N \g__madmurphy_concept_int
\seq_new:N \g__madmurphy_concept_list_seq
\cs_new:Nn \__madmurphy_concept_show:n
{
\clist_use:cnnn { g__madmurphy_concept_out_#1_clist } { ~and~ } { ,~ } { ,~and~ }
}
\AtEndDocument
{
\iow_now:cn { @auxout } { \ExplSyntaxOn }
\seq_map_function:NN \g__madmurphy_concept_list_seq \__madmurphy_concept_write:n
\iow_now:cn { @auxout } { \ExplSyntaxOff }
}
\cs_new_protected:Nn \__madmurphy_concept_write:n
{
\iow_now:cx { @auxout }
{
\clist_clear_new:c { g__madmurphy_concept_out_#1_clist }
}
\iow_now:cx { @auxout }
{
\clist_gset:cn { g__madmurphy_concept_out_#1_clist }
{
\clist_use:cn { g__madmurphy_concept_in_#1_clist } { , }
}
}
}
\ExplSyntaxOff
\begin{document}
\chapter{Concepts}
\begin{concept}{gnu}
The first concept we introduce is \emph{gnu}.
Let's describe it with nonsense words.
Let's describe it with nonsense words.
Let's describe it with nonsense words.
Let's describe it with nonsense words.
\end{concept}
\begin{concept}{gnat}
The second concept we introduce is \emph{gnat}.
Let's describe it with nonsense words.
Let's describe it with nonsense words.
Let's describe it with nonsense words.
Let's describe it with nonsense words.
\end{concept}
\chapter{Some title}
\applyconcept{gnu}
\lipsum[1]
\clearpage
\applyconcept{gnat}
\lipsum[2]
\chapter{Some other title}
\applyconcept{gnat}
\lipsum[3]
\clearpage
\applyconcept{gnu}
\lipsum[4]
\ExplSyntaxOn
\end{document}
答案4
感谢大家的精彩回答。我尝试找到自己的解决方案,这就是我想出的(我制作了软件包可在 GitHub 上获取也)。
我的解决方案仅依赖于一个依赖项(hyperref
包)并提供两个简单的宏:\whereapplies
和\hereapplies
- 后者还有一个带星号的版本(\hereapplies*
)。
编辑:发现一个小错误后询问寻求我已采用的另一个主题的帮助David Carlisle 的错误修复。下面的代码已相应更新。
hereapplies.sty:
% -*- Mode: latex; indent-tabs-mode: t; c-basic-offset: 4; tab-width: 4 -*-
%
% hereapplies.sty
%
% A LaTeX package for cross-linking applications of concepts
%
% https://github.com/madmurphy/hereapplies.sty
%
% Version 0.2.0
%
% Copyright (c) 2022 madmurphy <[email protected]>
%
% **Here Applies** is free software: you can redistribute it and/or modify it
% under the terms of the GNU Affero General Public License as published by the
% Free Software Foundation, either version 3 of the License, or (at your
% option) any later version.
%
% **Here Applies** is distributed in the hope that it will be useful, but
% WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
% FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License
% for more details.
%
% You should have received a copy of the GNU Affero General Public License
% along with this program. If not, see <http://www.gnu.org/licenses/>.
%
%
%
% Example usage:
%
% \documentclass{article}
%
% \usepackage{hereapplies}
%
% \begin{document}
%
% \title{Some title}
% \author{Some author}
%
% \maketitle
%
% This is concept one. To see this concept applied, please
% see \whereapplies{conceptone}.
%
% This is concept four. To see this concept applied, please
% see \whereapplies{conceptfour}.
%
% \newpage
%
% \hereapplies{conceptone}This is page \thepage. As you can see, ``concept
% one'' applies here.\newpage
%
% \hereapplies{conceptfour}This is page \thepage. As you can see,
% ``concept four'' applies here.\newpage
%
% \hereapplies{conceptone}This is page \thepage. As you can see, ``concept
% one'' applies here.\newpage
%
% \hereapplies{conceptfour}This is page \thepage. As you can see,
% ``concept four'' applies here.\newpage
%
% \hereapplies[myref]{conceptone}This is page \thepage. As you can
% see, ``concept one'' applies here. This point in the document is
% labeled \texttt{appl:conceptone:myref}.
%
% \end{document}
%
%
\ProvidesPackage{hereapplies}
\RequirePackage{hyperref}
%
%
%
% PRIVATE ENVIRONMENT
% ===================
%
%
% Assign a unique number to each applicable concept
\newcounter{@ha@concept@counter}
%
%
% Macro `\@ha@newapplicable{concept_name}`
% *****************************************************************************
%
% Initialize a new applicable concept
%
% Thie macro is for internal purposes only. When invoked it sets up the helper
% macros, counters and auxiliary files needed for keeping track of a concept.
% If the concept was already initialized this macro will be no op.
%
\newcommand{\@ha@newapplicable}[1]{%
% Was this concept already initialized?
\expandafter\ifx\csname @ha@concept@#1@cursor\endcsname\relax%
% The concept was never initialized
% Move the counter to the current id
\stepcounter{@ha@concept@counter}%
% Count the unnamed occurrences
\newcounter{@ha@concept@#1@unnamed@counter}%
% The content of this macro will be saved in the .haN file
\expandafter\def\csname @ha@concept@#1@output\endcsname{\textbf{??}}%
% The last page that applies
\expandafter\def\csname @ha@concept@#1@cursor\endcsname{-1}%
% Store the id of the current concept
\expandafter\edef\csname @ha@concept@#1@id\endcsname{\arabic{@ha@concept@counter}}%
% Initialize the .haN file
% The previous solution generated unwanted whitespaces:
%\@starttoc{ha\csname @ha@concept@#1@id\endcsname}%
% I am thankful to David Carlisle for the following line:
{\endlinechar=\m@ne\@starttoc{ha\csname @ha@concept@#1@id\endcsname}}%
% Store all the occurrences when the document reaches the end
\AtEndDocument{%
% Set the .haN file as output
\addtocontents{ha\csname @ha@concept@#1@id\endcsname}{%
% Check that there are occurrences
\expandafter\ifcsname @ha@concept@#1@cache\endcsname%
% There are occurrences - write "p./pp. ..." to the output
\gdef\expandafter\protect\csname @ha@concept@#1@output\endcsname{%
\csname @ha@concept@#1@preamble\endcsname\csname @ha@concept@#1@cache\endcsname%
}%
\else%
% There are no occurrences - write "??" to the output
\gdef\expandafter\protect\csname @ha@concept@#1@output\endcsname{%
\textbf{??}%
}%
\fi%
}%
}%
\fi%
}
%
%
% Macro: `\starred@hereapplies[occurrence_name]{concept_name}`
% *****************************************************************************
%
% Equivalent to `\hereapplies*`
%
% Thie macro is for internal purposes - but nothing forbids invoking it
% directly.
%
\newcommand{\starred@hereapplies}[2][]{%
% Make sure that the concept has been initialized
\@ha@newapplicable{#2}%
\ifx&%
% The macro has been called with only one argument
% Assign a unique number to the unnamed occurrence
\stepcounter{@ha@concept@#2@unnamed@counter}%
% Call `\starred@hereapplies` again (recursion), but this time with 2 arguments
\edef\tmp{\noexpand\starred@hereapplies[__unnamed\arabic{@ha@concept@#2@unnamed@counter}__]{#2}}\tmp%
\else%
% The macro has been called with two arguments
% Assign a label to this occurrence
\label{appl:#2:#1}%
% If the cursor already points to the current page do nothing
\unless\ifnum\csname @ha@concept@#2@cursor\endcsname=\thepage%
% Make the cursor point to the current page
\expandafter\edef\csname @ha@concept@#2@cursor\endcsname{\thepage}%
% Is this the first occurrence?
\expandafter\ifcsname @ha@concept@#2@cache\endcsname%
% This is *not* the first occurrence
% Use "pp." for the preamble when there are multiple occurrences
\expandafter\def\csname @ha@concept@#2@preamble\endcsname{pp.~}%
% Populate the cache
\expandafter\g@addto@macro\csname @ha@concept@#2@cache\endcsname{, \pageref{appl:#2:#1}}%
\else%
% This is the first occurrence
% Use "p." for the preamble when there is only one occurrence
\expandafter\def\csname @ha@concept@#2@preamble\endcsname{p.~}%
% Initialize the cache
\expandafter\def\csname @ha@concept@#2@cache\endcsname{\pageref{appl:#2:#1}}%
\fi%
\fi%
\fi%
}
%
%
%
% PUBLIC ENVIRONMENT
% ===================
%
%
% Macro: `\hereapplies[occurrence_name]{concept_name}`
% *****************************************************************************
%
% Notify the document that here a particular concept applies
%
% If the `occurrence_name` argument is passed, a new label will be created in
% the form `appl:[concept_name]:[occurrence_name]`. Without passing the
% `occurrence_name` argument, an opaque name will be assigned to the label.
%
% The starred version of this macro (`\hereapplies*`) will not invoke the
% `\phantomsection` directive before generating the label.
%
\newcommand{\hereapplies}{%
% Check if a star is present in the invocation of the command
\@ifstar{\starred@hereapplies}{\phantomsection\relax\starred@hereapplies}%
}
%
%
% Macro: `\whereapplies{concept_name}`
% *****************************************************************************
%
% Print all the applications of a concept in the form "p./pp. ..."
%
\newcommand{\whereapplies}[1]{%
% Make sure that the applicable concept is initialized
\@ha@newapplicable{#1}%
% Print all the applications of the concept
\csname @ha@concept@#1@output\endcsname%
}
例子.tex(感谢@marijn提供的可破解示例):
\documentclass{article}
\usepackage{hereapplies}
\begin{document}
\title{Some title}
\author{Some author}
\maketitle
This is concept one. To see this concept applied, please
see \whereapplies{conceptone}.
This is concept four. To see this concept applied, please
see \whereapplies{conceptfour}.
\newpage
\hereapplies{conceptone}This is page \thepage. As you can see, ``concept
one'' applies here.\newpage
\hereapplies{conceptfour}This is page \thepage. As you can see,
``concept four'' applies here.\newpage
\hereapplies{conceptone}This is page \thepage. As you can see, ``concept
one'' applies here.\newpage
\hereapplies{conceptfour}This is page \thepage. As you can see,
``concept four'' applies here.\newpage
\hereapplies[myref]{conceptone}This is page \thepage. As you can
see, ``concept one'' applies here. This point in the document is
labeled \texttt{appl:conceptone:myref}.
\end{document}
结果:
--madmurphy