top
Loading...
如何創建語言輔助函數

在現在這樣一個全球化環境中,因為在不同的語言中有很多不同的語法規則,所以以前很多簡單的任務現在都變得很困難。你可以將一門特定的語言分成一組語法規則和針對這些規則的異常(以及一個基本詞語),從而將這些任務一般化。在一些編程語言(比如 Perl 和 Java)中,有一些公共域(domain)模塊可以用來對文本完成語言轉換。

下面給出一個稍微簡單一點兒的例子,假設我們要將一個數字轉換成其拼寫版本(例如需要填寫支票和法律合同)。這個訣竅在 Oracle 出現的早期已經有了,一般都以如下方式使用:

selectto_char(to_date(12345,'J'),'Jsp') from dual;

Twelve Thousand Three Hundred Forty-Five
TO_DATE 函數使用 Julian 日期格式將數字轉換成一個日期。然后,TO_CHAR 接受一個日期參數并再次將其格式化為一個表示 Julian 日期的拼寫數字版本的字符串。但是這個決竅有一些限制。

首先,在 Oracle 中 Julian 日期的最大有效值是9999年,所以日期的最大值只能取到5373484,而最小值是1或4712BC。而且,因為沒有第“零”年,所以如果不額外使用一個 DECODE 或 CASE 語句就不可能生成文本“零”。第三個大的限制是它會忽略掉你的 NLS 設置。不管你使用的是哪種語言,數字總是以美國英語拼寫出。一些簡單的操作也存在這樣的問題,比如拼寫出天。例如,嘗試生成西班牙語短語“Cinco de Mayo”:

alter session set nls_language = 'SPANISH';
select to_char(to_date('0505','MMDD'),'Ddspth Month') from dual;

Fifth Mayo
在為大多數語言生成數字時涉及的語法實際上相當簡單。主體工作包括收集所有不同的語法規則并建立起足夠的規則來生成正確的語法模式。(現在我將回避涉及到匹配數字和性別的問題。)

首先,我將創建兩個表:第一個表保存基本的單詞和異常,第二個表保存用于生成文本的一些簡單的模板模式。如果在第一個表中有數字,那么我的語言函數就返回那個文本。對于其它每個數字,我將試圖在一系列模式中匹配它,并應用一個模板來生成正確的文本。

create table numwords
(
langvarchar2(2),
num integer,
wordvarchar2(30),
constraint numwords_pk primary key (lang,num)
);

create table numrules
(
langvarchar2(2),
seq integer,
p1integer,
p2integer,
temp0 varchar2(30),
tempvarchar2(30),
constraint numrules_pk primary key (lang,seq)
);

下面是生成一個數字拼寫版本所需的代碼。這里我將按照基數來(比如1、2和3);而事實上,這些函數可以通過為每種語言列出更多異常和模式來生成序數(第1、第2、第三)和復數版本。

REM -- create a table of base words and exceptions
create or replace package genword
as
function get_word(n number) return varchar2;
function cardinal(n number) return varchar2;
end genword;
/
show errors;

create or replace package body genword
as
function get_word(n number) return varchar2
is
l_wordnumwords.word
%type;
begin
select word into l_word from numwords
where lang = sys_context('userenv','lang') and num = n;
return l_word;
exception
when no_data_found then
return null;
end;
--
function cardinal(n number) return varchar2
is
p
number; -- power
t varchar2(30); -- template
v number; -- lower portion
l_wordnumwords.word%type;
begin
if n < 0 then
l_word :
= get_word(-1);
if l_word is null then
return null;
end if;
return l_word||' '||cardinal(-n);
end if;
l_word:
= get_word(n);
if l_word is not null then
return l_word;
end if;
for row in
(
select * from numrules
where lang = sys_context('userenv','lang')
order by seq
)
loop
if length(n) <= row.p1 + row.p2 then
p :
= power(10,row.p2);
v :
= mod(n,p);
if row.seq = 0 then
if n < 20 then
return replace(row.temp0,''2',cardinal(v));
end if;
else
if v = 0 then
return replace(row.temp0,''1',cardinal(n/p));
else
return replace(replace(nvl(row.temp,''1 '2'),
''1',cardinal(n-v)),
''2',cardinal(v));
end if;
end if;
end if;
end loop;
return 'NUMBER TOO LARGE';
end cardinal;
end genword;
/
show errors;

最后,這里是我為英語和德語收集的一些數據。我還將數據從美國英語拷貝到英國英語中并使用術語“thousand million”和“million million”代替“billion”和“trillion”(美國用法),在美國之外這兩個短語通常是混淆的來源。這些數據對生成-999,999,999,999到999,999,999,999之間所有整數(包括零)的拼寫版本已經足夠了。

REM -- create a table of base words and exceptions
create or replace package genword
as
function get_word(n number) return varchar2;
function cardinal(n number) return varchar2;
end genword;
/
show errors;

create or replace package body genword
as
function get_word(n number) return varchar2
is
l_wordnumwords.word
%type;
begin
select word into l_word from numwords
where lang = sys_context('userenv','lang') and num = n;
return l_word;
exception
when no_data_found then
return null;
end;
--
function cardinal(n number) return varchar2
is
p
number; -- power
t varchar2(30); -- template
v number; -- lower portion
l_wordnumwords.word%type;
begin
if n < 0 then
l_word :
= get_word(-1);
if l_word is null then
return null;
end if;
return l_word||' '||cardinal(-n);
end if;
l_word:
= get_word(n);
if l_word is not null then
return l_word;
end if;
for row in
(
select * from numrules
where lang = sys_context('userenv','lang')
order by seq
)
loop
if length(n) <= row.p1 + row.p2 then
p :
= power(10,row.p2);
v :
= mod(n,p);
if row.seq = 0 then
if n < 20 then
return replace(row.temp0,''2',cardinal(v));
end if;
else
if v = 0 then
return replace(row.temp0,''1',cardinal(n/p));
else
return replace(replace(nvl(row.temp,''1 '2'),
''1',cardinal(n-v)),
''2',cardinal(v));
end if;
end if;
end if;
end loop;
return 'NUMBER TOO LARGE';
end cardinal;
end genword;
/
show errors;
下面是一些簡單的 SQL 語句,這些語句使用了前面提供到函數和數據。你可以試一下將語言設成‘GERMAN’,或‘ENGLISH’來測試其它兩組數據:
SQL> alter session set nls_language = 'AMERICAN';SQL> select genword.cardinal(123456789) from dual;one hundred twenty-three million four hundred fifty-six thousand seven hundredeighty-nine

(e129)
作者:http://www.zhujiangroad.com
來源:http://www.zhujiangroad.com
北斗有巢氏 有巢氏北斗