Take the 2-minute tour ×
TeX - LaTeX Stack Exchange is a question and answer site for users of TeX, LaTeX, ConTeXt, and related typesetting systems. It's 100% free, no registration required.

Is there any package which can convert numbered pinyin (e.g. dian4 nao3) to UTF-8 pinyin with tone marks (e.g. diàn​ nǎo)?

I found this (http://stackoverflow.com/a/8200388/2421048) Python script, so it should be possible with LuaLaTeX using directlua.

share|improve this question
    
See also Typesetting pinyin finals –  Leo Liu Jul 23 '13 at 10:40

3 Answers 3

up vote 3 down vote accepted

I have translated the Python script mentioned in the question to Lua. Compile it with LuaLaTex and it should work:

\documentclass{article}
\usepackage{fontspec}

\usepackage{luacode}
\begin{luacode*}
PinyinToneMark = {
  {'ā', 'á', 'ǎ', 'à'},
  {'ē', 'é', 'ě', 'è'},
  {'ī', 'í', 'ǐ', 'ì'},
  {'ō', 'ó', 'ǒ', 'ò'},
  {'ū', 'ú', 'ǔ', 'ù'},
  {'ǖ', 'ǘ', 'ǚ', 'ǜ'}
}

function convertPinyin(str)
  if str~= nil and string.len(str)>0 then
  local s = string.lower(str)
  local r = ''
  local t = ''
  for i = 1, string.len(s) do
    local c = s:sub(i,i)
    if c >= 'a' and c <= 'z' then
      t = t .. c
    elseif c >= '0' and c <= '5' then
        local tone = tonumber(c)
        if tone ~= 0 then
          if string.find(t, 'a') ~= nil then
              t = string.gsub(t, "a", PinyinToneMark[1][tone])
          elseif string.find(t, 'e') ~= nil then
              t = string.gsub(t, "e", PinyinToneMark[2][tone])
          elseif string.find(t, 'i') ~= nil then
              t = string.gsub(t, "i", PinyinToneMark[3][tone])
          elseif string.find(t, 'o') ~= nil then
              t = string.gsub(t, "o", PinyinToneMark[4][tone])
          elseif string.find(t, 'u') ~= nil then
              t = string.gsub(t, "u", PinyinToneMark[5][tone])
          elseif string.find(t, 'v') ~= nil then
              t = string.gsub(t, "v", PinyinToneMark[6][tone])
          end
        end
      r = r .. t
      t = ""
    end
  end
  tex.print(r)
  end
end
\end{luacode*}

\begin{document}
 \directlua{convertPinyin("dian4 nao3")}
\end{document}

This version handles only v and not u: or ü.

share|improve this answer
    
This isn’t going to work for ü since it’s two bytes in utf. You’ll have to resort to the unicode libary or just use string.utfcharacters() right away. –  phg Jul 23 '13 at 11:36
    
@phg Thanks, I updated my answer. –  dumm dumm Jul 23 '13 at 11:44

If you want to use xeCJK package, XeLaTeX is necessary instead of LuaLaTeX.

You can use xpinyin package for pinyin.

\documentclass{article}
\usepackage{xeCJK}
\setCJKmainfont{SimSun}
\usepackage{xpinyin}
\begin{document}

电脑 \pinyin{dian4 nao3}

\end{document}

You will get

电脑 diàn nǎo
share|improve this answer

In Luatex, the conversion can be achieved without extra packages. Below is some example code, operating on input strings. Note that I don’t know anything about Chinese in general, nor Pinyin in particular. All I did was follow the WP entry which seemed fairly straightforward. Thus I expect the conversion to require further tweaking. If it produces incorrect results then please extend your question with more examples.

Here is the main TeX document (in Plain, but it should translate to Latex):

%% load some font that covers the diacritic marks
\input luaotfload.sty
\font\diacritics = "file:lmroman10-regular.otf:mode=node" at 10pt
\diacritics

%% --------------------------------------------------------------------
%% load conversion routines; adjust filename here
\directlua{dofile "\jobname.lua"}

%% wrap converter in a TeX macro
\protected\def\convertpinyin#1{%
  %% switch to appropriate hyphenation pattern goes here
  \directlua{packagedata.pinyintones.convert ([==[#1]==])}%
}

%% --------------------------------------------------------------------
%% demo

\def\showtest#1{((#1) (\convertpinyin{#1}))\par}

\def\testa{dian4 nao3}
\def\testb{ma ma1 ma2 ma3 ma4}

\showtest\testa
\showtest\testb

\bye

It loads a Lua file of the same name, but you can change the call to dofile() to something that fits your setup. Here is the code:

local utf                 = utf or require "unicode.utf8"
local lpeg                = require "lpeg"

local unpack              = unpack or table.unpack
local type                = type
local iowrite             = io.write
local stringformat        = string.format
local tableconcat         = table.concat
local utfchar             = utf.char
local texsprint           = tex.sprint

local C, Cg, Ct           = lpeg.C, lpeg.Cg, lpeg.Ct
local P, R, S, lpegmatch  = lpeg.P, lpeg.R, lpeg.S, lpeg.match

packagedata               = packagedata or { }
packagedata.pinyintones   = packagedata.pinyintones or { }
local pinyintones         = packagedata.pinyintones

-----------------------------------------------------------------------
---                           conversion
-----------------------------------------------------------------------

local toneno      = R"05"
local consonant   = S"bcdfghjklmnpqrstvwxyz" + S"BCDFGHJKLMNPQRSTVWXYZ"
local vowel       = S"aeiou" + S"AEIOU" + "ü" + "Ü"
local nucleus     = Ct(C(vowel)^1)
local syllable    = Cg((consonant^1)^-1, "onset")
                  * Cg(nucleus,          "nucleus")
                  * Cg((consonant^1)^-1, "coda")
                  * Cg(toneno^-1,        "tone")
local skip        = (1 - syllable)^1 --- keep this stuff
local pinyin      = Ct((Ct(syllable) + C(skip))^1)

local vowelpositions = function (nucleus)
  local tmp = { }
  for pos, vowel in next, nucleus do
    tmp[vowel] = pos
  end
  return tmp
end

local precedence = { "a", "e", "o" }

local cmacron = utfchar (0x0304)
local cacute  = utfchar (0x0301)
local ccaron  = utfchar (0x030c)
local cgrave  = utfchar (0x0300)

local todiacritic = function (str)
  local result   = { }
  local analyzed = lpegmatch (pinyin, str)
  --inspect (analyzed)
  for i = 1, #analyzed do
    local elm = analyzed[i]
    local t   = type (elm)

    if t == "table" then --- syllable
      local nucleus  = elm.nucleus
      local nvowels  = #nucleus
      local tone     = elm.tone
      if tone then
        tone = tonumber (tone)
      end

      if not tone then --- add unmodified
        result[#result+1] = elm.onset
        result[#result+1] = tableconcat (nucleus)
        result[#result+1] = elm.coda
      else
        local tonified
        if nvowels == 1 then --- single vowel receives tone
          local vowel = nucleus[1]
          if tone == 1 then --- inlined for performance
            tonified = vowel .. cmacron
          elseif tone == 2 then
            tonified = vowel .. cacute
          elseif tone == 3 then
            tonified = vowel .. ccaron
          elseif tone == 4 then
            tonified = vowel .. cgrave
          else
            tonified = vowel
          end

        elseif nvowels > 1 then
          local positions = vowelpositions (nucleus)
          local pos

          --- 1) locate correct vowel
          for j = 1, 3 do
            pos = positions[precedence[j]]
            if pos then
              break
            end
          end
          if not pos then -- iu or ui, thus second gets tone
            pos = 2
          end

          local vowel = nucleus[pos]

          --- 2) place tone mark
          if tone == 1 then
            nucleus[pos] = vowel .. cmacron
          elseif tone == 2 then
            nucleus[pos] = vowel .. cacute
          elseif tone == 3 then
            nucleus[pos] = vowel .. ccaron
          elseif tone == 4 then
            nucleus[pos] = vowel .. cgrave
          end

          tonified = tableconcat (nucleus)
        else --- no vowel, could be mismatch
          tonified = ""
        end
        result[#result+1] = elm.onset
        result[#result+1] = tonified
        result[#result+1] = elm.coda
      end

    elseif t == "string" then
      result[#result+1] = elm
    end

  end
  return tableconcat (result)
end

-----------------------------------------------------------------------
--- export
-----------------------------------------------------------------------

pinyintones.convert = function (str)
  local converted = todiacritic (str)
  if converted then
    texsprint (converted)
  end
end

Result:

pinyin conversion demo

For convenience, I created a gist of the code.

share|improve this answer

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.