在本网站成立初期,我们有一个问题自动将标题中的单词首字母大写?只有一个解决方案。由于 expl3 从那时起已经很成熟,我已经使用 制定了一个部分解决方案expl3
。它在一些异常上失败了,我正在寻找改进它的方法。
\documentclass{article}
\usepackage{expl3,xparse}
\usepackage{hyperref}
\ExplSyntaxOn
\clist_gset:Nn \title_words_not_capitalized_en
{ a, an, the, at, by, for, in, of, on, to, up, and, as, but, it, or, nor, do, for, this, be,
A, An, The, At, By, For, In, Of, On, To, Up, And, As, But, It, Or, Nor, Do, For, This, Be }
\cs_new:Npn \ucfirst_aux:w #1#2 \q_stop { \uppercase { #1 } #2 }
\cs_new:Npn \ucfirst #1 {
\exp_after:wN \ucfirst_aux:w #1 \q_stop
}
\cs_new:Npn \lowerfirst #1 {
\tex_lowercase:D {#1}
}
\DeclareDocumentCommand\UppercaseTitle {s +m }
{
\IfBooleanTF { #1 } { {\bfseries {#2} } }
{
\seq_set_split:Nnn \g_tmpa_seq {~} {#2}
\seq_use:Nn \g_tmpa_seq {~}\\
\seq_pop_left:NN \g_tmpa_seq \l_tmpa_tl
{\bfseries\ucfirst \l_tmpa_tl \space}
\seq_map_inline:Nn \g_tmpa_seq
{
\clist_if_in:NnTF \title_words_not_capitalized_en { ##1 }
{ {\bfseries \lowerfirst {##1}~}} { {\bfseries \ucfirst{##1}~ } }
}
}
}
\ExplSyntaxOff
\begin{document}
\tableofcontents
\parindent0pt
\section{\UppercaseTitle {The l3seq package Sequence and stacks}}
\UppercaseTitle {Top ten things to do in Paris}\\
\UppercaseTitle {How to use {\LaTeX} sequence lists effectively}\\
\UppercaseTitle {Senate Votes to Confirm Elena Kagan For U.S. Supreme Court}\\
\UppercaseTitle {what would be a ``correct'' capitalization for the title of this question?}\\
\UppercaseTitle* {How about {$e=mc^2$}? }\\
\end{document}
例外情况包括:
- 未能将引号中的单词大写。
- 数学有问题
- 不想使用括号宏(不方便用户使用)
- 如果两个或多个句子标题中的第二个句子的第一个单词以例外词开头,则会失败。
- 诸如“p-adic”的“p 次方根”之类的例外情况。
欢迎对 LaTeX3 编码发表评论。我还是新手!
答案1
Joseph 提到了我曾经想出的一个实现方案;这里就是。警告:这是从一封旧邮件中粘贴过来的,可能不再按预期运行,:)
我无法在工作时(轻松)测试……
您可以随意将任何想法融入您的代码中,或扩展它,或忽略它,一切按照您认为合适的方式进行!(此代码的一个显着特点是它是可扩展的 - 或者应该是。)
\documentclass[a4paper]{article}
\usepackage{expl3}
\usepackage{etoolbox}
\begin{document}
\ExplSyntaxOn
% Make some commands engine-robust
\robustify\textbf
% Set up to exclude items from case changing
\tl_put_right:Nn \l_tl_case_change_exclude_tl { \NoChangeCase }
\cs_new:Npn \NoChangeCase #1 {#1}
% First split the input string into sentence chunks, where `sentence' means anything after . ? ! : etc.
\cs_new:Npn \titlecase #1
{
\tl_if_blank:nF {#1}
{ \titlecase_split_period:w #1 . \q_recursion_tail \q_recursion_stop }
}
%% This is the basic idea of splitting:
%
%\cs_new:Npn \titlecase_split_period:w #1 . #2
% {
% \titlecase_split_colon:w #1 : \q_recursion_tail \q_recursion_stop
% \quark_if_recursion_tail_stop:n {#2}
% . \c_space_tl
% \titlecase_split_period:w #2
% }
% Use an obfuscated macro to avoid lengthy definitions:
\cs_new:Nn \titlecase_new_split_fn:NNNN
{
\cs_set:Npn #2 ##1 #1 ##2
{
#4 ##1 #3 \q_recursion_tail \q_recursion_stop
\quark_if_recursion_tail_stop:n {##2}
#1 \c_space_tl
#2 ##2
}
}
% The reason to use an obfuscated macro is to pass in properly sanitised `characters':
% (Could possibly be done more easily with some clever expansion.)
\exp_after:wN \titlecase_new_split_fn:NNNN
\exp_after:wN .
\exp_after:wN \titlecase_split_period:w \token_to_str:N :
\titlecase_split_colon:w
\exp_after:wN \titlecase_new_split_fn:NNNN
\token_to_str:N : \titlecase_split_colon:w ! \titlecase_split_exclam:w
\titlecase_new_split_fn:NNNN ! \titlecase_split_exclam:w ? \titlecase_split_question:w
\titlecase_new_split_fn:NNNN ? \titlecase_split_question:w {~} \titlecase_first:w
%% These are the internals that do the processing of each word.
% The first and last words needs special attention;
% they should always be capitalised unless there's a special exception.
\cs_new:Npn \titlecase_firstlast:nn #1 #2
{
\titlecase_exact_exceptions:nn {#1} {#2}
\tl_mixed_case:n {#1} #2
}
\cs_new:Npn \titlecase_middle:nn #1 #2
{
\titlecase_exact_exceptions:nn {#1} {#2} % catch exact matches which will never change
\titlecase_lc_exceptions:nn {#1} {#2} % catch words that should be lowercase
\tl_mixed_case:n {#1} #2 % otherwise capitalise normally
}
%% After all the splitting, we now enter the word-by-word titlecasing.
\cs_new:Npn \titlecase_first:w #1 ~ #2
{
\tl_if_blank:nT {#1} { \quark_if_recursion_tail_stop:n {#2} }
\titlecase_catch_punct:Nn \titlecase_firstlast:nn {#1}
\use_none_delimit_by_q_nil:w
\q_nil
\quark_if_recursion_tail_stop:n {#2}
\c_space_tl
\titlecase:w #2
}
% The regular loop:
\cs_new:Npn \titlecase:w #1 ~ #2
{
\quark_if_recursion_tail_stop_do:nn {#2}
{
\titlecase_catch_punct:Nn \titlecase_firstlast:nn {#1}
\use_none_delimit_by_q_nil:w
\q_nil
}
\titlecase_catch_punct:Nn \titlecase_middle:nn {#1}
\use_none_delimit_by_q_nil:w
\q_nil
\c_space_tl
\titlecase:w #2
}
\cs_new:Npn \__tl_last:n #1
{
\__tl_last:w #1 \q_recursion_tail \q_recursion_tail \q_recursion_stop
}
\cs_new:Npn \__tl_last:w #1 #2
{
\quark_if_recursion_tail_stop_do:nn {#2} {#1}
\__tl_last:w #2
}
\cs_new:Npn \titlecase_catch_punct:Nn #1 #2
{
\str_if_eq_x:nnT { \__tl_last:n {#2} } {,}
{
\exp_args:No #1 { \titlecase_catch_comma:w #2 }{,}
\use_none_delimit_by_q_nil:w
}
\str_if_eq_x:nnT { \__tl_last:n {#2} } {;}
{
\exp_args:No #1 { \titlecase_catch_semicolon:w #2 }{;}
\use_none_delimit_by_q_nil:w
}
#1 {#2}
}
\typeout{ LAST:~\__tl_last:n {foo bar .}}
\cs_new:Npn \titlecase_catch_comma:w #1, {#1}
\cs_new:Npn \titlecase_catch_semicolon:w #1; {#1}
% The exception catching functions contain series of functions like the following to match and if so jump to the end after their specific processing.
\cs_new:Npn \titlecase_exception:nnn #1 #2 #3
{
\str_if_eq_x:nnT { \tl_lower_case:n {#1} } {#3}
{ \tl_lower_case:n {#1} #2 \use_none_delimit_by_q_nil:w }
}
\cs_new:Npn \titlecase_exact_exception:nnn #1 #2 #3
{
\str_if_eq:nnT {#1} {#3}
{ #1 #2 \use_none_delimit_by_q_nil:w }
}
% These are the functions to define the exception catching functions.
\cs_new:Npn \titlecase_set_small_exceptions:n #1
{
\seq_set_from_clist:Nn \l_titlecase_seq {#1}
\cs_set:Npx \titlecase_lc_exceptions:nn ##1 ##2
{
\seq_map_function:NN \l_titlecase_seq \titlecase_small_except:n
}
}
\cs_new:Npn \titlecase_set_exact_exceptions:n #1
{
\seq_set_from_clist:Nn \l_titlecase_seq {#1}
\cs_set:Npx \titlecase_exact_exceptions:nn ##1 ##2
{
\seq_map_function:NN \l_titlecase_seq \titlecase_exact_except:n
}
}
\cs_new:Npn \titlecase_small_except:n #1
{
\exp_not:N \titlecase_exception:nnn {\exp_not:N ## 1} {\exp_not:N ## 2} {#1}
}
\cs_new:Npn \titlecase_exact_except:n #1
{
\exp_not:N \titlecase_exact_exception:nnn {\exp_not:N ## 1} {\exp_not:N ## 2} {#1}
}
\titlecase_set_small_exceptions:n {a,an,and,as,at,but,by,en,for,if,in,of,on,or,the,to,v,via,vs}
\titlecase_set_exact_exceptions:n {TV,iTunes,iPhone}% an example only
\ExplSyntaxOff
\def\TCTEST#1{{\titlecase{#1}}\par\bigskip}
%\def\TCTEST#1{\typeout{\titlecase{#1}}}
\TCTEST{Modern words like \NoChangeCase{\textbf{iPhone}} and iTunes, \NoChangeCase{eyeTV}, and even just TV are annoying}
\TCTEST{This is a sentence. But this is capitalised.}
\TCTEST{iTunes, here to stay like iPhone}
\TCTEST{Hello friends of the world, whom I am also friends of}
\TCTEST{}
\TCTEST{ }
\TCTEST{and}
\TCTEST{and and}
\TCTEST{and and and}
\TCTEST{This Was All Capitalised But Some Words Shouldn't Be If They're Small}
\TCTEST{Colon test: An example of an exception}
\TCTEST{A Question? An answer?}
\TCTEST{A Question! An answer!}
\TCTEST{iTunes and TV}
\TCTEST{David v Goliath and Goliath vs Mittelbach}
\TCTEST{Hard to say whether `quotes are too hard'}
\ExplSyntaxOn
\typeout{ [This~ is~ plain~ title_case~ with~ quotes]~ \tl_mixed_case:n {`Hello'} }
[This~ is~ plain~ title_case~ with~ quotes]~ \tl_mixed_case:n {`Hello'}
\par\bigskip [This~ is~ plain~ title_case~ with~ braces]~ \tl_mixed_case:n {{`Hello'}}
\par\bigskip [This~ is~ plain~ title_case~ with~ bold]~ \tl_mixed_case:n {\textbf{`Hello'}}
\par\bigskip [This~ is~ plain~ title_case~ with~ bold~ and~ braces]~ \tl_mixed_case:n {{\textbf{`Hello'}}}
\ExplSyntaxOff
\end{document}