2

I am translating ancient text that contain lots of Chinese names. I would like to have something that allows me to simply enter the Chinese characters so that they would be automatically translated into Pinyin (so not showing the Chinese characters, which are shown in parallel text in my document). The reason for this is that it not eliminates a lot of work (looking up the correct pinyin and entering it), but also lots of accidental errors.

So I would like to have something like \pyc{诸葛 亮} to substitute this in the output with "Zhūgě Liàng". (Best with options to retain spaces and capitalize words.)

The xpinyin package has all the machinery to annotate Chinese characters with Pinyin, but the output is inflexible.

Or is there another package out there that does something similar?

2 Answers 2

2

I have come up with a temporary solution. The major drawback is the treatment of spaces when there are punctuations. As you can see, no punctuation has been added into the example so far.

enter image description here

\documentclass{article}
\usepackage{xpinyin}
\usepackage{fontspec}
\usepackage{expl3}


\ExplSyntaxOn

\tl_new:N \l_doc_tmpa_tl
\tl_new:N \l_doc_tmpb_tl
\tl_new:N \l_doc_tmpc_tl
\tl_new:N \l_doc_tmpd_tl
\tl_new:N \l_doc_tmpe_tl

\cs_set:Npn \__doc_group_helper:n #1 {
    {\exp_not:n {#1}}
}

\newcommand{\pyonly}[1]{
    \tl_set:Nn \l_doc_tmpa_tl {#1}
    \tl_clear:N \l_doc_tmpd_tl
    
    \bool_do_until:nn {\tl_if_empty_p:N \l_doc_tmpa_tl} {
        \exp_args:NV \tl_if_head_is_group:nTF \l_doc_tmpa_tl {
             \tl_set:Nx \l_doc_tmpb_tl {\tl_head:N \l_doc_tmpa_tl}
             \tl_set:Nx \l_doc_tmpa_tl {\tl_tail:N \l_doc_tmpa_tl}
             \tl_put_right:Nx \l_doc_tmpd_tl {
                \exp_args:NV \__doc_group_helper:n \l_doc_tmpb_tl
             }
        } {
             \tl_set:Nx \l_doc_tmpb_tl {\tl_head:N \l_doc_tmpa_tl}
             \tl_set:Nx \l_doc_tmpa_tl {\tl_tail:N \l_doc_tmpa_tl}
             \bool_if:nTF {
                 \bool_lazy_any_p:n {
                     {\exp_args:NV \token_if_letter_p:N \l_doc_tmpb_tl}
                     {\exp_args:NV \token_if_other_p:N \l_doc_tmpb_tl}
                 }
             } {
                 \tl_set:Nx \l_doc_tmpc_tl { \exp_args:NV \__xpinyin_to_unicode:n \l_doc_tmpb_tl }
                 \cs_if_exist:cTF { c__xpinyin_ \l_doc_tmpc_tl _tl }{
                     \tl_put_right:Nx \l_doc_tmpd_tl {\tl_use:c {c__xpinyin_ \l_doc_tmpc_tl _tl}~}
                 } {
                     \tl_put_right:NV \l_doc_tmpd_tl \l_doc_tmpb_tl
                 }
             } {
                 \tl_put_right:NV \l_doc_tmpd_tl \l_doc_tmpb_tl
             }   
        }
    }
    
    \tl_show:N \l_doc_tmpd_tl
    \tl_use:N \l_doc_tmpd_tl
}

\newcommand{\pyor}[2]{
    \pinyin{#2}~
}

\ExplSyntaxOff

\begin{document}

\par \pyonly{你好我的名字是小明}
\par \pyonly{我的读音不\pyor{一}{er4}样}

\end{document}

This problem has a better solution in LuaTeX. It is simply too daunting to implement something with pure LaTeX. See the code below. To get it running, you need to download chn-to-pinyin.lua and save it in your working directory.

enter image description here

\documentclass{article}
\usepackage[fontset=fandol]{ctex}
\usepackage{luacode}
\usepackage{xcolor}
\usepackage{xparse}


\begin{luacode*}
require"chn-to-pinyin"

pinyin_vowel_v_mapping = {
    "ü", "Ü"
}

pinyin_vowel_tone_mapping = {
    ["a"] = {
        "ā", "á", "ǎ", "à"
    },
    ["e"] = {
        "ē", "é", "ě", "è"
    },
    ["i"] = {
        "ī", "í", "ǐ", "ì"
    },
    ["o"] = {
        "ō", "ó", "ǒ", "ò"
    },
    ["u"] = {
        "ū", "ú", "ǔ", "ù"
    },
    ["v"] = {
        "ǖ", "ǘ", "ǚ", "ǜ"
    },
    ["A"] = {
        "Ā", "Á", "Ǎ", "À"
    },
    ["E"] = {
        "Ē", "É", "Ě", "È"
    },
    ["I"] = {
        "Ī", "Í", "Ǐ", "Ì"
    },
    ["O"] = {
        "Ō", "Ó", "Ǒ", "Ò"
    },
    ["U"] = {
        "Ū", "Ú", "Ǔ", "Ù"
    },
    ["V"] = {
        "Ǖ", "Ǘ", "Ǚ", "Ǜ"
    }
}

pinyin_vowel_letters_lower = {"a", "e", "i", "o", "u", "v"}

pinyin_vowel_to_lower = {}
pinyin_vowel_to_upper = {}

for ind, lower in ipairs(pinyin_vowel_letters_lower) do
    local upper = lower:upper()
    local tbl1 = pinyin_vowel_tone_mapping[lower]
    local tbl2 = pinyin_vowel_tone_mapping[upper]
    assert(#tbl1 == #tbl2, "inconsistent pinyin configuration")
    for i=1,#tbl1 do
        pinyin_vowel_to_lower[tbl2[i]] = tbl1[i]
        pinyin_vowel_to_upper[tbl1[i]] = tbl2[i]
    end
end
pinyin_vowel_to_upper[pinyin_vowel_v_mapping[1]] = pinyin_vowel_v_mapping[2]
pinyin_vowel_to_lower[pinyin_vowel_v_mapping[2]] = pinyin_vowel_v_mapping[1]

pinyin_consonants = {
    "zh", "ch", "sh", "b", "p", "m", "f", "d", 
    "t", "n", "l", "g", "k", "h", "j", "q", "x", 
    "r", "z", "c", "s", "w", "y"
}

pinyin_vowels = {
    "iang", "uang", "ueng", "iong", "uai", "uei", "iao", "ian", 
    "uan", "van", "ang", "eng", "ing", "ong", "ia", "ua", "uo", 
    "ie", "ve", "ai", "ei", "ao", "ou", "iu", "an", "en", "in", 
    "un", "vn", "er", "i", "u", "v", "a", "o", "e"
}

pinyin_vowel_tone_locs = {
    2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 2, 2, 2, 2, 2, 
    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1
}

assert(#pinyin_vowels == #pinyin_vowel_tone_locs, "inconsistent pinyin configuration")

function find_next_consonant(s, s_lower, loc)
    local b
    for _, consonant in ipairs(pinyin_consonants) do
        b, _ = s_lower:find(consonant, loc)
        if b == loc then
            return s:sub(loc, loc + #consonant - 1)
        end
    end
    return nil
end

function find_next_vowel(s, s_lower, loc)
    local b
    for vowel_ind, vowel in ipairs(pinyin_vowels) do
        b, _ = s_lower:find(vowel, loc)
        if b == loc then
            return s:sub(loc, loc + #vowel - 1), vowel_ind
        end
    end
    return nil
end

function find_next_tone(s, s_lower, loc)
    local char = s_lower:sub(loc, loc)
    if char ~= nil and char:len() > 0 then
        local byte = char:byte()
        if byte >= 48 and byte <= 57 then
            return char
        end
    end
    return nil
end

function render_pinyin(consonant, vowel, vowel_ind, tone)
    local res = ""
    if consonant ~= nil then
        res = res .. consonant
    end
    local tone_loc = -1
    if tone ~= nil then
         tone_loc = pinyin_vowel_tone_locs[vowel_ind]
    end
    local char, tone_char
    for i=1,vowel:len() do
        char = vowel:sub(i, i)
        if i == tone_loc then
            tone_char = pinyin_vowel_tone_mapping[char][tonumber(tone)]
            assert(tone_char ~= nil, string.format("invalid tone combination: '%s', %s", vowel, tone))
            res = res .. tone_char
        else
            -- deal with "v"
            if char == "v" then
                res = res .. pinyin_vowel_v_mapping[1]
            elseif char == "V" then
                res = res .. pinyin_vowel_v_mapping[2]
            else
                res = res .. char
            end
        end
    end
    return res
end

function latin_to_pinyin(s)
    -- discard spaces
    s = s:gsub("%s", "")
    local s_lower = s:lower()
    local loc = 1
    local old_loc = loc
    local consonant, vowel, tone, vowel_ind, render
    local result = {}
    while loc <= s:len() do
        -- start by finding consonant
        consonant = find_next_consonant(s, s_lower, loc)
        if consonant ~= nil then
            loc = loc + consonant:len()
        end
        -- proceed to find vowel
        vowel, vowel_ind = find_next_vowel(s, s_lower, loc)
        assert(vowel ~= nil, string.format("cannot find vowel for '%s...'", s:sub(loc, loc + 10)))
        loc = loc + vowel:len()
        -- peek next char to see if we can find a tone
        tone = find_next_tone(s, s_lower, loc)
        if tone ~= nil then
            loc = loc + tone:len()
        end
        if loc == old_loc then
            error("latin to pinyin algorithm is stuck, please check the integrity of latin input")
        end
        texio.write_nl(string.format("pinyin-debug: %s, %s, %s", consonant, vowel, tone))
        render = render_pinyin(consonant, vowel, vowel_ind, tone)
        table.insert(result, render)
        old_loc = loc
    end
    return result
end

function chinese_to_pinyin(s)
    local code = utf8.codepoint(s)
    local char = utf8.char(code)
    local pinyin = chn_to_pinyin[char]
    return char, pinyin
end


function pinyin_upper(s)
    cap_first = cap_first or false
    local res = ""
    local query, char
    for p, code in utf8.codes(s) do
        char = utf8.char(code)
        query = pinyin_vowel_to_upper[char]
        if query ~= nil then
            res = res .. query
        else
            res = res .. char:upper()
        end
    end
    return res
end

function chinese_to_pinyin_latex(s, cap_first)
    cap_first = cap_first or false
    local char, pinyin = chinese_to_pinyin(s)
    local out_val  = ""
    if pinyin==nil then
        -- special treatment for "*"
        if char == "*" then
            out_val = char
        else
            out_val = [[\textcolor{red}{]] .. char .. "}"
        end
    else
        if cap_first then
            local first_code = utf8.codepoint(pinyin)
            local first_code_offset = utf8.offset(pinyin, 2)
            pinyin = pinyin_upper(utf8.char(first_code)) .. pinyin:sub(first_code_offset)
        end
        out_val = pinyin
    end
    token.set_macro("l_doc_tmpc_tl", out_val)
end



\end{luacode*}

\newcommand{\latintopinyin}[1]{%
    \directlua{
        local pinyin_table = latin_to_pinyin("\luaescapestring{#1}")
        tex.print(table.concat(pinyin_table, " "))
    }%
}

\ExplSyntaxOn

\tl_new:N \l_doc_tmpa_tl
\tl_new:N \l_doc_tmpb_tl
\tl_new:N \l_doc_tmpc_tl

\NewDocumentCommand{\chntopinyin}{sm}{
    \group_begin:
    \tl_set:Nn \l_doc_tmpa_tl {#2}
    \tl_clear:N \l_doc_tmpb_tl
    \bool_do_until:nn {\tl_if_empty_p:N \l_doc_tmpa_tl} {
        \exp_args:NV \tl_if_head_is_group:nTF \l_doc_tmpa_tl {
            \tl_put_right:Nx \l_doc_tmpb_tl {{\tl_head:N \l_doc_tmpa_tl}}
        } {
            \exp_args:Nx \token_if_cs:NTF {\tl_head:N \l_doc_tmpa_tl} {
                \tl_put_right:Nx \l_doc_tmpb_tl {\tl_head:N \l_doc_tmpa_tl}
            } {
                \IfBooleanTF{#1}{
                    \directlua{
                        chinese_to_pinyin_latex("\luaescapestring{\tl_head:N\l_doc_tmpa_tl}", true)
                    }
                    \tl_put_right:NV \l_doc_tmpb_tl \l_doc_tmpc_tl
                }{
                    \directlua{
                        chinese_to_pinyin_latex("\luaescapestring{\tl_head:N\l_doc_tmpa_tl}")
                    }
                    \tl_put_right:NV \l_doc_tmpb_tl \l_doc_tmpc_tl
                }
                \tl_put_right:NV \l_doc_tmpb_tl \space
            }
        }
        \tl_set:Nx \l_doc_tmpa_tl {\tl_tail:N \l_doc_tmpa_tl}
    }
    \tl_use:N \l_doc_tmpb_tl
    \group_end:
}


\newcommand{\pinyin}[2]{
    \latintopinyin{#2}\space
}

\ExplSyntaxOff

\begin{document}
\par\latintopinyin{zhuang1jia1huan2chuang4}
\par\latintopinyin{yi1Er2SAn1SI4}
\par Supported characters are highlighted in red
\par\chntopinyin{测试\chntopinyin*{中文}到\pinyin{拼}{pin3}音\pinyin{转}{Zhuan3}换ひらがな}
\par\chntopinyin{\chntopinyin*{诸葛亮}六出祁山}
\par\chntopinyin*{二}
\end{document}
6
  • This is not a bad start, it solves my problem at least part way. But is there a good spot to capitalize the first pinyin? My use case is mainly to include proper names in pinyin, so I need something \pyonly{诸葛 亮} that would output "Zhūgě Liàng".(explanation for those unfamiliar with Chinese: it is a famous general whose familiy name is Zhūgě, in proper Chinese the three characters for his name would always be written together 诸葛亮, but the added space would be a hint for the pinyin transliteration to retain that space)
    – ludwig
    Dec 26, 2020 at 15:02
  • @ludwig I just found out that xpinyin does not support capitalization.
    – Alan Xiang
    Dec 26, 2020 at 16:54
  • Thanks for looking at it, I am struggling to understand how xpinyin's machinery works under the hood. It is so close to doing what I need, that it is a shame that there are no hooks to change the formatting in it, e.g. when looking up a character from the pinyin table to capitalize it at that point. It is obviously not a use case it was designed for. Thanks a lot for your effort.
    – ludwig
    Dec 27, 2020 at 0:23
  • It took me a while to understand the mechanism as well. In short, the xeCJK package will call the macro \CJKsymbol to render CJK symbols. xpinyin override it with their own version which renders both character and pinyin.
    – Alan Xiang
    Dec 27, 2020 at 0:31
  • @ludwig I feel like this problem is easier to solve in LuaTeX. However, it might be a bit uncomfortable to shift away from XeTeX tool chain.
    – Alan Xiang
    Dec 27, 2020 at 15:54
0

There are multiple aspects to the question: (1) printing pinyin; (2) formatting names; (3) dealing with spaces.

Here, the first aspect is addressed, with reading the pinyin database directly demonstrated, along with the definition of a command to use expl3's replace function to replace each character with its pinyin, printing single-reading pinyin and either the first of multiple readings (marked with a *), or the full set of multiple readings (enclosed in brackets)

thus:

pinyin

If names are known beforehand, and formatted with a space, they can be pinyinized (with first reading) and titlecased:

names

Implies markup will be needed in cases of multiple possible readings.

MWE

Code can run under lualatex. Only expl3 is used. No dependency on xpinyin package (it is tied to xelatex via xeCJK dependencies).

\documentclass{article}
\usepackage{fontspec}
\setmainfont{Noto Serif}
\setsansfont{Noto Sans}
\setmonofont{Noto Sans Mono}
\usepackage{xparse}
\newfontfamily\cjk{NotoSerifCJKtc-Regular}
%-------------------------------------------------------------
\ExplSyntaxOn

% Adapted from xpinyin.sty:
% #1 (character), rather than #2 (Unicode codepoint),
% becomes part of the control sequence name.
% #3 is the pinyin. All three are in the database.
\cs_new_protected:Npn \xpinyin_customary:nnn #1#2
  { \cs_gset_nopar:cpn { c__xpinyin_#1_tl } }
\cs_new_protected:Npn \xpinyin_multiple:nnn #1#2
  { \cs_gset_nopar:cpn { c__xpinyin_multiple_#1_clist } }
\group_begin:
  \cs_set_eq:NN \XPYU  \xpinyin_customary:nnn
  \cs_set_eq:NN \XPYUM \xpinyin_multiple:nnn
  \file_input:n { xpinyin-database.def }
\group_end:



        \cs_generate_variant:Nn 
            \tl_replace_all:Nnn 
            { Nxx }

        \cs_generate_variant:Nn 
            \str_replace_all:Nnn 
            { Nxx }

%-----------------------------
    \cs_set:Npn \gl_funcxpp:n #1 { 
     \tl_set:Nn \l_tmpb_tl { #1 }
%    \tl_show:N \l_tmpb_tl

   \tl_replace_all:Nxx
    \l_tmpa_tl
    { \l_tmpb_tl }
    {
        \clist_if_exist:cTF
                { c__xpinyin_multiple_ \l_tmpb_tl _clist }
                { % there is a comma list for the character
        \bool_if:NTF
        \g__gloss_printfirstvar_bool
        { % bool true = print first reading
                        \clist_item:cn 
                                { c__xpinyin_multiple_ \l_tmpb_tl _clist }
                                { 1 }
                                * %=multi
            }
            {   % bool false = print all readings               
                                (
                                \clist_use:cn                               
                                { c__xpinyin_multiple_ \l_tmpb_tl _clist }
                                { , }
                                )                               
     }
                                \tex_space:D
                 }
                { % not a comma list = only one reading
                    \cs_if_exist:cT
                    { c__xpinyin_ \l_tmpb_tl _tl }
                    {
                     \use:c { c__xpinyin_ \l_tmpb_tl _tl }
                     \tex_space:D
                    }
                }
                }

%   \cs_show:c { c__xpinyin_ \l_tmpb_tl _tl }
}

\tl_new:N \l_gl_item_tl
%-----------------------------
    \cs_set:Npn \gl_functitlecase:n #1
      {~
            \tl_set:Nn \l_gl_item_tl { #1 }
        \text_titlecase:nn {en} { \tl_use:N \l_gl_item_tl }
%        \tex_space:D
      }
      
%-----------------------------
    \cs_set:Npn \gl_funcxppname:n #1 { 
     \tl_set:Nn \l_tmpb_tl { #1 }

   \tl_replace_all:Nxx
    \l_tmpa_tl
    { \l_tmpb_tl }
    {
        \clist_if_exist:cTF
                { c__xpinyin_multiple_ \l_tmpb_tl _clist }
                { % there is a comma list for the character
                        \clist_item:cn 
                                { c__xpinyin_multiple_ \l_tmpb_tl _clist }
                                { 1 }
                 }
                { % not a comma list = only one reading
                    \cs_if_exist:cTF
                    { c__xpinyin_ \l_tmpb_tl _tl }
                    {
                     \use:c { c__xpinyin_ \l_tmpb_tl _tl }
                    }
                    { % anything else
                        #1
                    }
                }
                }
}







\bool_new:N \g__gloss_printfirstvar_bool
\NewDocumentCommand \xpppinyin { s m }
  {
  \IfBooleanTF {#1 }
        { \bool_gset_false:N \g__gloss_printfirstvar_bool }
        { \bool_gset_true:N \g__gloss_printfirstvar_bool }
  
        \tl_set:Nn \l_tmpa_tl { #2 }

        \tl_map_function:NN
        \l_tmpa_tl 
        \gl_funcxpp:n 

        \tl_use:N \l_tmpa_tl

  }

\NewDocumentCommand \xppname { m }
  {
  
        \tl_set:Nn \l_tmpa_tl { #1 }

        \tl_map_function:NN
        \l_tmpa_tl
        \gl_funcxppname:n 

        \seq_set_split:NnV \l_tmpa_seq { ~ } \l_tmpa_tl

    \seq_map_function:NN 
                \l_tmpa_seq 
                \gl_functitlecase:n
  }




\ExplSyntaxOff
    
\begin{document}

{\cjk 我诸葛 亮} $\mapsto$ \xpppinyin{我诸葛 亮}

{\cjk 我诸葛 亮} $\mapsto$ \xpppinyin*{我诸葛 亮}

\xppname{诸葛 亮}


\end{document}


Addendum

Added a basic key-value mechanism for selecting the ith pinyin of the kth character, for up to 3 characters a,b,c:

key value added

MWE

\documentclass{article}
\usepackage{fontspec}
\setmainfont{Noto Serif}
\setsansfont{Noto Sans}
\setmonofont{Noto Sans Mono}
\usepackage{xparse}
\newfontfamily\cjk{NotoSerifCJKtc-Regular}
\newfontface\sym{DejaVu Sans}
\newcommand\tick{{\sym ✓}}
%-------------------------------------------------------------
\ExplSyntaxOn

% Adapted from xpinyin.sty:
% #1 (character), rather than #2 (Unicode codepoint),
% becomes part of the control sequence name.
% #3 is the pinyin. All three are in the database.
\cs_new_protected:Npn \xpinyin_customary:nnn #1#2
  { \cs_gset_nopar:cpn { c__xpinyin_#1_tl } }
\cs_new_protected:Npn \xpinyin_multiple:nnn #1#2
  { \cs_gset_nopar:cpn { c__xpinyin_multiple_#1_clist } }
\group_begin:
  \cs_set_eq:NN \XPYU  \xpinyin_customary:nnn
  \cs_set_eq:NN \XPYUM \xpinyin_multiple:nnn
  \file_input:n { xpinyin-database.def }
\group_end:

%-----------------------------
%-----------------------------




        \cs_generate_variant:Nn 
            \tl_replace_all:Nnn 
            { Nxx }

        \cs_generate_variant:Nn 
            \str_replace_all:Nnn 
            { Nxx }

        \cs_generate_variant:Nn 
                    \seq_set_split:Nnn 
                { cnx }

%-----------------------------
\keys_define:nn { mpy }
{
mpya .tl_set:c = { l_mpy_char1_tl },
mpyb .tl_set:c = { l_mpy_char2_tl },
mpyc .tl_set:c = { l_mpy_char3_tl },
}
\seq_new:c { l_mpy_char1_seq }
\seq_new:c { l_mpy_char2_seq }
\seq_new:c { l_mpy_char3_seq }

\int_new:c { l_mpy_char1item_int }
\int_new:c { l_mpy_char2item_int }
\int_new:c { l_mpy_char3item_int }

\int_new:c { l_mpy_char1py_int }
\int_new:c { l_mpy_char2py_int }
\int_new:c { l_mpy_char3py_int }


%-----------------------------
    \cs_set:Npn \gl_funcxpp:n #1 { 
     \tl_set:Nn \l_tmpb_tl { #1 }
%    \tl_show:N \l_tmpb_tl

   \tl_replace_all:Nxx
    \l_tmpa_tl
    { \l_tmpb_tl }
    {
        \clist_if_exist:cTF
                { c__xpinyin_multiple_ \l_tmpb_tl _clist }
                { % there is a comma list for the character
        \bool_if:NTF
        \g__mpy_printfirstvar_bool
        { % bool true = print first reading
                        \clist_item:cn 
                                { c__xpinyin_multiple_ \l_tmpb_tl _clist }
                                { 1 }
                                * %=multi
            }
            {   % bool false = print all readings               
                                (
                                \clist_use:cn                               
                                { c__xpinyin_multiple_ \l_tmpb_tl _clist }
                                { , }
                                )                               
     }
                                \tex_space:D
                 }
                { % not a comma list = only one reading
                    \cs_if_exist:cT
                    { c__xpinyin_ \l_tmpb_tl _tl }
                    {
                     \use:c { c__xpinyin_ \l_tmpb_tl _tl }
                     \tex_space:D
                    }
                }
                }

%   \cs_show:c { c__xpinyin_ \l_tmpb_tl _tl }
}

\tl_new:N \l_gl_item_tl
%-----------------------------
    \cs_set:Npn \gl_functitlecase:n #1
      {~
            \tl_set:Nn \l_gl_item_tl { #1 }
        \text_titlecase:nn {en} { \tl_use:N \l_gl_item_tl }
%        \tex_space:D
      }
      
%-----------------------------
    \cs_set:Npn \gl_funcxppname:n #1 { 

        \int_gincr:c { l_mpy_char1count_int }
    
     \tl_set:Nn \l_tmpb_tl { #1 }

   \tl_replace_all:Nxx
    \l_tmpa_tl
    { \l_tmpb_tl }
    {
        \clist_if_exist:cTF
                { c__xpinyin_multiple_ \l_tmpb_tl _clist }
                { % there is a comma list for the character
                   % nth character's mth pinyin
                        \int_compare:nNnTF
                                  { \int_use:c { l_mpy_char1count_int } }
                                   = 
                                   { \int_use:c { l_mpy_char2item_int } }
                                  {
                        \clist_item:cn 
                                { c__xpinyin_multiple_ \l_tmpb_tl _clist }
                                { \int_use:c { l_mpy_char2py_int } }
                                        }%%
                            { %not 2            
                   % nth character's mth pinyin
                        \int_compare:nNnTF
                                  { \int_use:c { l_mpy_char1count_int } }
                                   = 
                                   { \int_use:c { l_mpy_char3item_int } }
                                  {
                        \clist_item:cn 
                                { c__xpinyin_multiple_ \l_tmpb_tl _clist }
                                { \int_use:c { l_mpy_char3py_int } }
                                        }%%
                    { % not 3
                   % nth character's mth pinyin
                        \int_compare:nNnTF
                                  { \int_use:c { l_mpy_char1count_int } }
                                   = 
                                   { \int_use:c { l_mpy_char1item_int } }
                                  {
                        \clist_item:cn 
                                { c__xpinyin_multiple_ \l_tmpb_tl _clist }
                                { \int_use:c { l_mpy_char1py_int } }
                                        }%%
                                        { % not 1, therefore 1st pinyin
                        \clist_item:cn 
                                { c__xpinyin_multiple_ \l_tmpb_tl _clist }
                                { 1 }
                                }%false1
                                }%false2
                                }%false3
                 }
                { % not a comma list = only one reading
                    \cs_if_exist:cTF
                    { c__xpinyin_ \l_tmpb_tl _tl }
                    {
                     \use:c { c__xpinyin_ \l_tmpb_tl _tl }
                    }
                    { % anything else
                        #1
                    }
                }
                }
}







\bool_new:N \g__mpy_printfirstvar_bool
\NewDocumentCommand \xpppinyin { s m }
  {
  \IfBooleanTF {#1 }
        { \bool_gset_false:N \g__mpy_printfirstvar_bool }
        { \bool_gset_true:N \g__mpy_printfirstvar_bool }
  
        \tl_set:Nn \l_tmpa_tl { #2 }

        \tl_map_function:NN
        \l_tmpa_tl 
        \gl_funcxpp:n 

        \tl_use:N \l_tmpa_tl

  }


\int_new:c { l_mpy_char1count_int }
\NewDocumentCommand \xppname { O{mpya={},
mpyb={},
mpyc={},} m }
  {
  
     \int_gset:cn { l_mpy_char1count_int } { 0 }
     \int_gset:cn { l_mpy_char1item_int  } { 0 } 
     \int_gset:cn { l_mpy_char2item_int  } { 0 } 
     \int_gset:cn { l_mpy_char3item_int  } { 0 } 
          
      \IfNoValueF{#1}
    {
        \keys_set:nn { mpy } { #1 } 
        }
  
  
  %----- if the option has been set
        \tl_if_empty:cF { l_mpy_char1_tl }
        {  
                %----- store the record
                    \seq_set_split:cnx
                             { l_mpy_char1_seq }
                            { ; } % separator
                            { \tl_use:c { l_mpy_char1_tl } }
                %----- store the fields
                \int_set:cn
                        { l_mpy_char1item_int }
                        {
                                \seq_item:cn
                                    { l_mpy_char1_seq }
                                    { 1 } %the i-th character
                        }
                \int_set:cn
                        { l_mpy_char1py_int }
                        {
                                \seq_item:cn
                                    { l_mpy_char1_seq }
                                    { 2 } %the k-th pinyin reading
                        }

        }%end1
  %----- if the option has been set
        \tl_if_empty:cF { l_mpy_char2_tl }
        {  
                %----- store the record
                    \seq_set_split:cnx
                             { l_mpy_char2_seq }
                            { ; } % separator
                            { \tl_use:c { l_mpy_char2_tl } }
                %----- store the fields
                \int_set:cn
                        { l_mpy_char2item_int }
                        {
                                \seq_item:cn
                                    { l_mpy_char2_seq }
                                    { 1 } %the i-th character
                        }
                \int_set:cn
                        { l_mpy_char2py_int }
                        {
                                \seq_item:cn
                                    { l_mpy_char2_seq }
                                    { 2 } %the k-th pinyin reading
                        }

        }%end2
  %----- if the option has been set
        \tl_if_empty:cF { l_mpy_char3_tl }
        {  
                %----- store the record
                    \seq_set_split:cnx
                             { l_mpy_char3_seq }
                            { ; } % separator
                            { \tl_use:c { l_mpy_char3_tl } }
                %----- store the fields
                \int_set:cn
                        { l_mpy_char3item_int }
                        {
                                \seq_item:cn
                                    { l_mpy_char3_seq }
                                    { 1 } %the i-th character
                        }
                \int_set:cn
                        { l_mpy_char3py_int }
                        {
                                \seq_item:cn
                                    { l_mpy_char3_seq }
                                    { 2 } %the k-th pinyin reading
                        }

        }%end3
  
  





  
  %-----
        \tl_set:Nn \l_tmpa_tl { #2 }

        \tl_map_function:NN
        \l_tmpa_tl
        \gl_funcxppname:n 

        \seq_set_split:NnV \l_tmpa_seq { ~ } \l_tmpa_tl

    \seq_map_function:NN 
                \l_tmpa_seq 
                \gl_functitlecase:n
  }




\ExplSyntaxOff
    
\begin{document}

{\cjk 诸葛 亮} $\mapsto$ \xpppinyin{诸葛 亮}

{\cjk 诸葛 亮} $\mapsto$ \xpppinyin*{诸葛 亮}

\xppname{诸葛 亮}, ...

%  mpya - for a character setting ``a''
%  = - set it to
%  2 - character position in the argument
%  ; - delimiter
%  2 - pinyin position in the comma list
\xppname[mpya=2;2]{诸葛 亮}, ...

\bigskip
\xpppinyin*{𪀩𪘬𩞀}

\tick 3:2:3 \xppname[mpya=2;2,
mpyb=3;3,
mpyc=1;3,
]{𪀩𪘬 𩞀}

\tick 1:1:2 \xppname[mpya=3;2,
mpyb=1;1,
mpyc=2;1,
]{𪀩𪘬 𩞀}

\tick 2:3:- \xppname[mpya={},
mpyb=1;2,
mpyc=2;3,
]{𪀩𪘬 𩞀}

\tick -:-:- \xppname[%mpya={},
mpyb={},
mpyc={},
]{𪀩𪘬 𩞀}

\tick -:-:2 \xppname[mpya=3;2]{𪀩𪘬 𩞀}

\tick -:-:- \xppname{𪀩𪘬 𩞀}

\tick 1:1:2 \xppname[mpya=3;2,
mpyb=1;1,
mpyc=2;1,
]{𪀩𪘬 𩞀}


\tick -:-:- \xppname{𪀩𪘬 𩞀}

\tick -:-:2 \xppname[mpya=3;2]{𪀩𪘬 𩞀}


\tick -:-:- \xppname{𪀩𪘬 𩞀}


\end{document}

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .