接受参数中的特殊字符并进一步传递它们

接受参数中的特殊字符并进一步传递它们

我想定义一个非常类似于的宏\url,它接受或多或少任意的字符串(包括哈希/ )#并将它们传递给其他命令(即,,,\href)。我尝试应用一些\url\nolinkurl其他 解决方案(例如,本地更改 catcodes 等),但失败了。我还希望我的新宏可以传递给其他宏,例如\footnote

以下不起作用的 MWE 展示了一种实现,将任意字符串作为输入,将它们转换为转义的 tex 兼容字符串(通过使用\tl_to_str:nxparse命令处理器),并将它们传递下去,没有任何错误。然而,这完全破坏了宏的含义,它只是输出转义的文本(我真的不明白为什么)。

理想情况下,宏甚至会在将相应的参数传递给时检测哈希并对其进行转义\href,因为这是必需的(但对于原始的则不是\url)。

\documentclass{article}
\PassOptionsToPackage{hyphens}{url}
\usepackage[hidelinks]{hyperref}

\usepackage{xparse}
\usepackage{letltxmacro}

\ExplSyntaxOn
\LetLtxMacro\oldurl\url
\RenewDocumentCommand\url{>{\tl_to_str:n}m>{\tl_to_str:n}g}{%
    \IfNoValueTF{#2}{%
      \oldurl{#1}%
    }{%
      \href{#1}{#2}%
    }%
}
\ExplSyntaxOff

\setlength{\parindent}{0pt}
\begin{document}

\smallskip
\noindent\rule{\linewidth}{1ex}

\smallskip
url\hfill\hspace{0.6\linewidth}\url{https://www.yahoo.com/break/#me}

\smallskip
2url\#1\hfill\hspace{0.6\linewidth}\url{https://www.yahoo.com/break-me/#please}{www.yahoo.com/break/me/please}

\smallskip
2url\#2\hfill\hspace{0.6\linewidth}\url{https://www.yahoo.com/break-me/please}{www.yahoo.com/break/me/#please}

\smallskip
2url\#3\hfill\hspace{0.6\linewidth}\url{https://www.yahoo.com/break-me/#please}{www.yahoo.com/break-me/#please}

\noindent\rule{\linewidth}{1ex}

footnote: \footnote{\url{https://www.yahoo.com/break-me/#please}{www.yahoo.com/break-me/#please}}
\end{document}

在此处输入图片描述

答案1

你不能在参数处理器中放置任意命令。参数处理器必须将其结果保留在 中\ProcessedArgument,因此而不是:

\RenewDocumentCommand\url{>{\tl_to_str:n}m}{...}

你需要:

\cs_new_protected:Npn \Detokenize #1
  { \tl_set:Nx \ProcessedArgument { \tl_to_str:n {#1} } }
\RenewDocumentCommand\url{>{\Detokenize}m}{...}

也就是说,\detokenize哈希值加倍,因此您需要取消加倍才能获得正确的 URL。我定义了一个\UndoubleHashes参数处理器,它使用 将参数转换为字符串\tl_to_str:n,然后循环查找双重哈希并将其替换为单个哈希。

此外,您必须确保特殊字符具有\url预期的 catcode,因此您需要使\^^M\%\#\&\_、处于\~活动\$状态(但“其他”也可以)。

经过此更改,PDF 查看器的工具提示将显示正确的 URL:

在此处输入图片描述

\documentclass{article}
\PassOptionsToPackage{hyphens}{url}
\usepackage[hidelinks]{hyperref}

\usepackage{xparse}
\usepackage{letltxmacro}

\makeatletter
\DeclareRobustCommand*{\breakhref}[3][]{%
  \begingroup
    \setkeys{href}{#1}%
    \hyper@normalise\breakhref@{#2}{#3}}
\begingroup
  \lccode`\~=`\#
  \lowercase{\endgroup
  \def\breakhref@#1{\expandafter\breakhref@split#1~~\\}%
  \def\breakhref@split#1~#2~#3\\#4{%
    \hyper@@link{#1}{#2}{#4}%
    \endgroup}}%
\makeatother
\ExplSyntaxOn
\cs_new_protected:Npn \UndoubleHashes #1
  {
    \tl_set:Nx \ProcessedArgument
      { \stefanct_undouble_hashes:n {#1} }
  }
\cs_new:Npx \stefanct_undouble_hashes:n #1
  {
    \exp_not:N \exp_last_unbraced:No
      \exp_not:N \__stefanct_undouble_hashes:w { \exp_not:N \tl_to_str:n {#1} }
        \c_hash_str \c_hash_str \exp_not:N \q_nil \s_stop
  }
\use:x
  {
    \cs_new:Npn \exp_not:N \__stefanct_undouble_hashes:w
        ##1 \c_hash_str \c_hash_str ##2
      {
        ##1
        \exp_not:N \quark_if_nil:nTF {##2}
          { \exp_not:N \use_none_delimit_by_s_stop:w }
          { \c_hash_str ##2 }
        \exp_not:N \__stefanct_undouble_hashes:w
      }
  }

\tl_new:N \l__stefanct_tmpa_tl
\cs_generate_variant:Nn \tl_replace_all:Nnn { Nx }
\LetLtxMacro\oldurl\url
\RenewDocumentCommand\url { }
  {
    \group_begin:
      \tl_map_inline:nn { \^^M \% \# \& \_ \~ \$ }
        { \char_set_catcode_active:n { `##1 } }
      \char_set_catcode_other:n { `\# }
      \urlaux
  }
\group_begin:
\char_set_catcode_active:n { `\# }
\char_set_catcode_parameter:n { `\$ }
\cs_new_protected:Npn \stefanct_url:nn $1 $2
  {
    \tl_set:Nn \l__stefanct_tmpa_tl {$2}
    \tl_replace_all:Nxn \l__stefanct_tmpa_tl { \c_hash_str } { # }
    \char_set_active_eq:NN # \__stefanct_breakable_hash:
    \IfNoValueTF {$2}
      { \oldurl {$1} }
      { \exp_args:NnV \breakhref {$1} \l__stefanct_tmpa_tl }
    \group_end:
  }
\group_end:
\cs_new_protected:Npn \__stefanct_breakable_hash:
  {
    \penalty \UrlBreakPenalty
    \c_hash_str
  }
\NewDocumentCommand\urlaux{>{\UndoubleHashes}m>{\UndoubleHashes}g}
  { \stefanct_url:nn {#1} {#2} }
\ExplSyntaxOff

\setlength{\parindent}{0pt}
\begin{document}

\smallskip
\noindent\rule{\linewidth}{1ex}

\smallskip
url\hfill\hspace{0.6\linewidth}\url{https://www.yahoo.com/break/#me%too}

\smallskip
url\hfill\hspace{0.6\linewidth}\url{httpswwwyahoocombreak#me%too}

\smallskip
2url\#1\hfill\hspace{0.6\linewidth}\url{https://www.yahoo.com/break-me/#please}{www.yahoo.com/break/me/please}

\smallskip
2url\#2\hfill\hspace{0.6\linewidth}\url{https://www.yahoo.com/break-me/please}{www.yahoo.com/break/me/#please}

\smallskip
2url\#3\hfill\hspace{0.6\linewidth}\url{https://www.yahoo.com/break-me/#please}{www.yahoo.com/break-me/#please}

\noindent\rule{\linewidth}{1ex}

footnote: \footnote{\url{https://www.yahoo.com/break-me/#please}{www.yahoo.com/break-me/#please}}
\end{document}

代码允许在#符号前拆分 URL。要拆分它a 、改变定义中的和 的#顺序。\penalty \UrlBreakPenalty\c_hash_str\__stefanct_breakable_hash:

相关内容