从头开始实现一个非常基本的索引

从头开始实现一个非常基本的索引

我正在创建一份文档,其中在第一章中我定义了一些概念 - 每个概念都有自己专门的段落(例如:概念一、概念二、概念三等) - 并且在所有其他章节中我应用我在第一章中定义的概念来分析事物。

我想在每个“概念段落”末尾添加一些内容,例如“要了解此概念的应用,请参阅第 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.styamsmidx.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&#1&%
        % 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}

结果

hereapplies.sty 的示例

--madmurphy

相关内容