Fix breakages that prevended -DPERL_POISON from compiling.
[p5sagit/p5-mst-13.2.git] / lib / Unicode / UCD.pm
index 3ce4a95..1496663 100644 (file)
@@ -3,7 +3,9 @@ package Unicode::UCD;
 use strict;
 use warnings;
 
-our $VERSION = '0.2';
+our $VERSION = '0.23';
+
+use Storable qw(dclone);
 
 require Exporter;
 
@@ -14,7 +16,8 @@ our @EXPORT_OK = qw(charinfo
                    charblocks charscripts
                    charinrange
                    compexcl
-                   casefold casespec);
+                   casefold casespec
+                   namedseq);
 
 use Carp;
 
@@ -31,7 +34,7 @@ Unicode::UCD - Unicode character database
     my $charblock  = charblock($codepoint);
 
     use Unicode::UCD 'charscript';
-    my $charscript = charblock($codepoint);
+    my $charscript = charscript($codepoint);
 
     use Unicode::UCD 'charblocks';
     my $charblocks = charblocks();
@@ -46,12 +49,15 @@ Unicode::UCD - Unicode character database
     use Unicode::UCD 'compexcl';
     my $compexcl = compexcl($codepoint);
 
+    use Unicode::UCD 'namedseq';
+    my $namedseq = namedseq($named_sequence_name);
+
     my $unicode_version = Unicode::UCD::UnicodeVersion();
 
 =head1 DESCRIPTION
 
-The Unicode::UCD module offers a simple interface to the Unicode Character
-Database.
+The Unicode::UCD module offers a simple interface to the Unicode
+Character Database.
 
 =cut
 
@@ -62,6 +68,7 @@ my $VERSIONFH;
 my $COMPEXCLFH;
 my $CASEFOLDFH;
 my $CASESPECFH;
+my $NAMEDSEQFH;
 
 sub openunicode {
     my ($rfh, @path) = @_;
@@ -108,14 +115,14 @@ as defined by the Unicode standard:
     title            titlecase equivalent mapping
 
     block            block the character belongs to (used in \p{In...})
-    script           script the character belongs to 
+    script           script the character belongs to
 
 If no match is found, a reference to an empty hash is returned.
 
-The C<block> property is the same as as returned by charinfo().  It is
+The C<block> property is the same as returned by charinfo().  It is
 not defined in the Unicode Character Database proper (Chapter 4 of the
-Unicode 3.0 Standard) but instead in an auxiliary database (Chapter 14
-of TUS3).  Similarly for the C<script> property.
+Unicode 3.0 Standard, aka TUS3) but instead in an auxiliary database
+(Chapter 14 of TUS3).  Similarly for the C<script> property.
 
 Note that you cannot do (de)composition and casing based solely on the
 above C<decomposition> and C<lower>, C<upper>, C<title>, properties,
@@ -123,97 +130,43 @@ you will need also the compexcl(), casefold(), and casespec() functions.
 
 =cut
 
+# NB: This function is duplicated in charnames.pm
 sub _getcode {
     my $arg = shift;
 
-    if ($arg =~ /^\d+$/) {
+    if ($arg =~ /^[1-9]\d*$/) {
        return $arg;
-    } elsif ($arg =~ /^(?:U\+|0x)?([[:xdigit:]]+)$/) {
+    } elsif ($arg =~ /^(?:[Uu]\+|0[xX])?([[:xdigit:]]+)$/) {
        return hex($1);
     }
 
     return;
 }
 
-sub han_charname {
-    my $arg  = shift;
-    my $code = _getcode($arg);
-    croak __PACKAGE__, "::han_charname: unknown code '$arg'"
-       unless defined $code;
-    croak __PACKAGE__, "::han_charname: outside CJK Unified Ideographs '$arg'"
-        unless 0x3400  <= $code && $code <= 0x4DB5  
-            || 0x4E00  <= $code && $code <= 0x9FA5  
-            || 0x20000 <= $code && $code <= 0x2A6D6;
-    sprintf "CJK UNIFIED IDEOGRAPH-%04X", $code;
+# Lingua::KO::Hangul::Util not part of the standard distribution
+# but it will be used if available.
+
+eval { require Lingua::KO::Hangul::Util };
+my $hasHangulUtil = ! $@;
+if ($hasHangulUtil) {
+    Lingua::KO::Hangul::Util->import();
 }
 
-my @JamoL = ( # Leading Consonant (HANGUL CHOSEONG)
-    "G", "GG", "N", "D", "DD", "R", "M", "B", "BB",
-    "S", "SS", "", "J", "JJ", "C", "K", "T", "P", "H",
-  );
-
-my @JamoV = ( # Medium Vowel (HANGUL JUNGSEONG)
-    "A", "AE", "YA", "YAE", "EO", "E", "YEO", "YE", "O",
-    "WA", "WAE", "OE", "YO", "U", "WEO", "WE", "WI",
-    "YU", "EU", "YI", "I",
-  );
-
-my @JamoT = ( # Trailing Consonant (HANGUL JONGSEONG)
-    "", "G", "GG", "GS", "N", "NJ", "NH", "D", "L", "LG", "LM",
-    "LB", "LS", "LT", "LP", "LH", "M", "B", "BS",
-    "S", "SS", "NG", "J", "C", "K", "T", "P", "H",
-  );
-
-my %HangulConst = (
-   SBase  => 0xAC00,
-   LBase  => 0x1100,
-   VBase  => 0x1161,
-   TBase  => 0x11A7,
-   LCount => 19,     # scalar @JamoL
-   VCount => 21,     # scalar @JamoV
-   TCount => 28,     # scalar @JamoT
-   NCount => 588,    # VCount * TCount
-   SCount => 11172,  # LCount * NCount
-   Final  => 0xD7A3, # SBase -1 + SCount
-  );
-
-sub hangul_charname {
-    my $arg  = shift;
-    my $code = _getcode($arg);
-    croak __PACKAGE__, "::hangul_charname: unknown code '$arg'"
-       unless defined $code;
-    croak __PACKAGE__, "::hangul_charname: outside Hangul Syllables '$arg'"
-        unless $HangulConst{SBase} <= $code && $code <= $HangulConst{Final};
-    my $SIndex = $code - $HangulConst{SBase};
-    my $LIndex = int( $SIndex / $HangulConst{NCount});
-    my $VIndex = int(($SIndex % $HangulConst{NCount}) / $HangulConst{TCount});
-    my $TIndex =      $SIndex % $HangulConst{TCount};
-    return join('',
-        "HANGUL SYLLABLE ",
-        $JamoL[$LIndex],
-        $JamoV[$VIndex],
-        $JamoT[$TIndex],
-      );
+sub hangul_decomp { # internal: called from charinfo
+    if ($hasHangulUtil) {
+       my @tmp = decomposeHangul(shift);
+       return sprintf("%04X %04X",      @tmp) if @tmp == 2;
+       return sprintf("%04X %04X %04X", @tmp) if @tmp == 3;
+    }
+    return;
 }
 
-sub hangul_decomp {
-    my $arg  = shift;
-    my $code = _getcode($arg);
-    croak __PACKAGE__, "::hangul_decomp: unknown code '$arg'"
-       unless defined $code;
-    croak __PACKAGE__, "::hangul_decomp: outside Hangul Syllables '$arg'"
-        unless $HangulConst{SBase} <= $code && $code <= $HangulConst{Final};
-    my $SIndex = $code - $HangulConst{SBase};
-    my $LIndex = int( $SIndex / $HangulConst{NCount});
-    my $VIndex = int(($SIndex % $HangulConst{NCount}) / $HangulConst{TCount});
-    my $TIndex =      $SIndex % $HangulConst{TCount};
-
-    return join(" ",
-        sprintf("%04X", $HangulConst{LBase} + $LIndex),
-        sprintf("%04X", $HangulConst{VBase} + $VIndex),
-      $TIndex ?
-        sprintf("%04X", $HangulConst{TBase} + $TIndex) : (),
-    );
+sub hangul_charname { # internal: called from charinfo
+    return sprintf("HANGUL SYLLABLE-%04X", shift);
+}
+
+sub han_charname { # internal: called from charinfo
+    return sprintf("CJK UNIFIED IDEOGRAPH-%04X", shift);
 }
 
 my @CharinfoRanges = (
@@ -224,7 +177,7 @@ my @CharinfoRanges = (
 # CJK Ideographs
   [ 0x4E00,   0x9FA5,   \&han_charname,   undef  ],
 # Hangul Syllables
-  [ 0xAC00,   0xD7A3,   \&hangul_charname, \&hangul_decomp  ],
+  [ 0xAC00,   0xD7A3,   $hasHangulUtil ? \&getHangulName : \&hangul_charname,  \&hangul_decomp ],
 # Non-Private Use High Surrogates
   [ 0xD800,   0xDB7F,   undef,   undef  ],
 # Private Use High Surrogates
@@ -259,11 +212,12 @@ sub charinfo {
         last;
       }
     }
-    openunicode(\$UNICODEFH, "Unicode.txt");
+    openunicode(\$UNICODEFH, "UnicodeData.txt");
     if (defined $UNICODEFH) {
        use Search::Dict 1.02;
        if (look($UNICODEFH, "$hexk;", { xfrm => sub { $_[0] =~ /^([^;]+);(.+)/; sprintf "%06X;$2", hex($1) } } ) >= 0) {
            my $line = <$UNICODEFH>;
+           return unless defined $line;
            chomp $line;
            my %prop;
            @prop{qw(
@@ -327,19 +281,20 @@ sub charinrange {
     my $charblock = charblock("0x263a");
     my $charblock = charblock("U+263a");
 
-    my $ranges    = charblock('Armenian');
+    my $range     = charblock('Armenian');
 
-With a B<code point argument> charblock() returns the block the character
+With a B<code point argument> charblock() returns the I<block> the character
 belongs to, e.g.  C<Basic Latin>.  Note that not all the character
 positions within all blocks are defined.
 
-If supplied with an argument that can't be a code point, charblock()
-tries to do the opposite and interpret the argument as a character
-block.  The return value is a I<range>: an anonymous list that
-contains anonymous lists, which in turn contain I<start-of-range>,
-I<end-of-range> code point pairs.  You can test whether a code point
-is in a range using the L</charinrange> function.  If the argument is
-not a known charater block, C<undef> is returned.
+See also L</Blocks versus Scripts>.
+
+If supplied with an argument that can't be a code point, charblock() tries
+to do the opposite and interpret the argument as a character block. The
+return value is a I<range>: an anonymous list of lists that contain
+I<start-of-range>, I<end-of-range> code point pairs. You can test whether
+a code point is in a range using the L</charinrange> function. If the
+argument is not a known character block, C<undef> is returned.
 
 =cut
 
@@ -349,6 +304,7 @@ my %BLOCKS;
 sub _charblocks {
     unless (@BLOCKS) {
        if (openunicode(\$BLOCKSFH, "Blocks.txt")) {
+           local $_;
            while (<$BLOCKSFH>) {
                if (/^([0-9A-F]+)\.\.([0-9A-F]+);\s+(.+)/) {
                    my ($lo, $hi) = (hex($1), hex($2));
@@ -373,7 +329,7 @@ sub charblock {
        _search(\@BLOCKS, 0, $#BLOCKS, $code);
     } else {
        if (exists $BLOCKS{$arg}) {
-           return $BLOCKS{$arg};
+           return dclone $BLOCKS{$arg};
        } else {
            return;
        }
@@ -388,18 +344,19 @@ sub charblock {
     my $charscript = charscript(1234);
     my $charscript = charscript("U+263a");
 
-    my $ranges     = charscript('Thai');
+    my $range      = charscript('Thai');
 
-With a B<code point argument> charscript() returns the script the
+With a B<code point argument> charscript() returns the I<script> the
 character belongs to, e.g.  C<Latin>, C<Greek>, C<Han>.
 
-If supplied with an argument that can't be a code point, charscript()
-tries to do the opposite and interpret the argument as a character
-script.  The return value is a I<range>: an anonymous list that
-contains anonymous lists, which in turn contain I<start-of-range>,
-I<end-of-range> code point pairs.  You can test whether a code point
-is in a range using the L</charinrange> function.  If the argument is
-not a known charater script, C<undef> is returned.
+See also L</Blocks versus Scripts>.
+
+If supplied with an argument that can't be a code point, charscript() tries
+to do the opposite and interpret the argument as a character script. The
+return value is a I<range>: an anonymous list of lists that contain
+I<start-of-range>, I<end-of-range> code point pairs. You can test whether a
+code point is in a range using the L</charinrange> function. If the
+argument is not a known character script, C<undef> is returned.
 
 =cut
 
@@ -409,6 +366,7 @@ my %SCRIPTS;
 sub _charscripts {
     unless (@SCRIPTS) {
        if (openunicode(\$SCRIPTSFH, "Scripts.txt")) {
+           local $_;
            while (<$SCRIPTSFH>) {
                if (/^([0-9A-F]+)(?:\.\.([0-9A-F]+))?\s+;\s+(\w+)/) {
                    my ($lo, $hi) = (hex($1), $2 ? hex($2) : hex($1));
@@ -436,7 +394,7 @@ sub charscript {
        _search(\@SCRIPTS, 0, $#SCRIPTS, $code);
     } else {
        if (exists $SCRIPTS{$arg}) {
-           return $SCRIPTS{$arg};
+           return dclone $SCRIPTS{$arg};
        } else {
            return;
        }
@@ -452,11 +410,13 @@ sub charscript {
 charblocks() returns a reference to a hash with the known block names
 as the keys, and the code point ranges (see L</charblock>) as the values.
 
+See also L</Blocks versus Scripts>.
+
 =cut
 
 sub charblocks {
     _charblocks() unless %BLOCKS;
-    return \%BLOCKS;
+    return dclone \%BLOCKS;
 }
 
 =head2 charscripts
@@ -468,11 +428,13 @@ sub charblocks {
 charscripts() returns a hash with the known script names as the keys,
 and the code point ranges (see L</charscript>) as the values.
 
+See also L</Blocks versus Scripts>.
+
 =cut
 
 sub charscripts {
     _charscripts() unless %SCRIPTS;
-    return \%SCRIPTS;
+    return dclone \%SCRIPTS;
 }
 
 =head2 Blocks versus Scripts
@@ -480,13 +442,13 @@ sub charscripts {
 The difference between a block and a script is that scripts are closer
 to the linguistic notion of a set of characters required to present
 languages, while block is more of an artifact of the Unicode character
-numbering and separation into blocks of 256 characters.
+numbering and separation into blocks of (mostly) 256 characters.
 
 For example the Latin B<script> is spread over several B<blocks>, such
 as C<Basic Latin>, C<Latin 1 Supplement>, C<Latin Extended-A>, and
 C<Latin Extended-B>.  On the other hand, the Latin script does not
 contain all the characters of the C<Basic Latin> block (also known as
-the ASCII): it includes only the letters, not for example the digits
+the ASCII): it includes only the letters, and not, for example, the digits
 or the punctuation.
 
 For blocks see http://www.unicode.org/Public/UNIDATA/Blocks.txt
@@ -495,22 +457,21 @@ For scripts see UTR #24: http://www.unicode.org/unicode/reports/tr24/
 
 =head2 Matching Scripts and Blocks
 
-Both scripts and blocks can be matched using the regular expression
-construct C<\p{In...}> and its negation C<\P{In...}>.
-
-The name of the script or the block comes after the C<In>, for example
-C<\p{InCyrillic}>, C<\P{InBasicLatin}>.  Spaces and dashes ('-') are
-removed from the names for the C<\p{In...}>, for example
-C<LatinExtendedA> instead of C<Latin Extended-A>.
-
-There are a few cases where there exists both a script and a block by
-the same name, in these cases the block version has C<Block> appended:
-C<\p{InKatakana}> is the script, C<\p{InKatakanaBlock}> is the block.
+Scripts are matched with the regular-expression construct
+C<\p{...}> (e.g. C<\p{Tibetan}> matches characters of the Tibetan script),
+while C<\p{In...}> is used for blocks (e.g. C<\p{InTibetan}> matches
+any of the 256 code points in the Tibetan block).
 
 =head2 Code Point Arguments
 
-A <code point argument> is either a decimal or a hexadecimal scalar,
-or "U+" followed by hexadecimals.
+A I<code point argument> is either a decimal or a hexadecimal scalar
+designating a Unicode character, or C<U+> followed by hexadecimals
+designating a Unicode character.  In other words, if you want a code
+point to be interpreted as a hexadecimal number, you must prefix it
+with either C<0x> or C<U+>, because a string like e.g. C<123> will
+be interpreted as a decimal code point.  Also note that Unicode is
+B<not> limited to 16 bits (the number of Unicode characters is
+open-ended, in theory unlimited): you may have more than 4 hexdigits.
 
 =head2 charinrange
 
@@ -545,9 +506,10 @@ my %COMPEXCL;
 
 sub _compexcl {
     unless (%COMPEXCL) {
-       if (openunicode(\$COMPEXCLFH, "CompExcl.txt")) {
+       if (openunicode(\$COMPEXCLFH, "CompositionExclusions.txt")) {
+           local $_;
            while (<$COMPEXCLFH>) {
-               if (/^([0-9A-F]+) \# /) {
+               if (/^([0-9A-F]+)\s+\#\s+/) {
                    my $code = hex($1);
                    $COMPEXCL{$code} = undef;
                }
@@ -572,7 +534,7 @@ sub compexcl {
 
     use Unicode::UCD 'casefold';
 
-    my %casefold = casefold("09dc");
+    my $casefold = casefold("00DF");
 
 The casefold() returns the locale-independent case folding of the
 character specified by a B<code point argument>.
@@ -615,7 +577,8 @@ my %CASEFOLD;
 
 sub _casefold {
     unless (%CASEFOLD) {
-       if (openunicode(\$CASEFOLDFH, "CaseFold.txt")) {
+       if (openunicode(\$CASEFOLDFH, "CaseFolding.txt")) {
+           local $_;
            while (<$CASEFOLDFH>) {
                if (/^([0-9A-F]+); ([CFSI]); ([0-9A-F]+(?: [0-9A-F]+)*);/) {
                    my $code = hex($1);
@@ -644,7 +607,7 @@ sub casefold {
 
     use Unicode::UCD 'casespec';
 
-    my %casespec = casespec("09dc");
+    my $casespec = casespec("FB00");
 
 The casespec() returns the potentially locale-dependent case mapping
 of the character specified by a B<code point argument>.  The mapping
@@ -667,7 +630,12 @@ more I<locales> or I<contexts>, separated by spaces (other than as
 used to separate elements, spaces are to be ignored).  A condition
 list overrides the normal behavior if all of the listed conditions are
 true.  Case distinctions in the condition list are not significant.
-Conditions preceded by "NON_" represent the negation of the condition
+Conditions preceded by "NON_" represent the negation of the condition.
+
+Note that when there are multiple case folding definitions for a
+single code point because of different locales, the value returned by
+casespec() is a hash reference which has the locales as the keys and
+hash references as described above as the values.
 
 A I<locale> is defined as a 2-letter ISO 3166 country code, possibly
 followed by a "_" and a 2-letter ISO language code (possibly followed
@@ -690,15 +658,51 @@ my %CASESPEC;
 
 sub _casespec {
     unless (%CASESPEC) {
-       if (openunicode(\$CASESPECFH, "SpecCase.txt")) {
+       if (openunicode(\$CASESPECFH, "SpecialCasing.txt")) {
+           local $_;
            while (<$CASESPECFH>) {
                if (/^([0-9A-F]+); ([0-9A-F]+(?: [0-9A-F]+)*)?; ([0-9A-F]+(?: [0-9A-F]+)*)?; ([0-9A-F]+(?: [0-9A-F]+)*)?; (\w+(?: \w+)*)?/) {
-                   my $code = hex($1);
-                   $CASESPEC{$code} = { code      => $1,
-                                        lower     => $2,
-                                        title     => $3,
-                                        upper     => $4,
-                                        condition => $5 };
+                   my ($hexcode, $lower, $title, $upper, $condition) =
+                       ($1, $2, $3, $4, $5);
+                   my $code = hex($hexcode);
+                   if (exists $CASESPEC{$code}) {
+                       if (exists $CASESPEC{$code}->{code}) {
+                           my ($oldlower,
+                               $oldtitle,
+                               $oldupper,
+                               $oldcondition) =
+                                   @{$CASESPEC{$code}}{qw(lower
+                                                          title
+                                                          upper
+                                                          condition)};
+                           if (defined $oldcondition) {
+                               my ($oldlocale) =
+                               ($oldcondition =~ /^([a-z][a-z](?:_\S+)?)/);
+                               delete $CASESPEC{$code};
+                               $CASESPEC{$code}->{$oldlocale} =
+                               { code      => $hexcode,
+                                 lower     => $oldlower,
+                                 title     => $oldtitle,
+                                 upper     => $oldupper,
+                                 condition => $oldcondition };
+                           }
+                       }
+                       my ($locale) =
+                           ($condition =~ /^([a-z][a-z](?:_\S+)?)/);
+                       $CASESPEC{$code}->{$locale} =
+                       { code      => $hexcode,
+                         lower     => $lower,
+                         title     => $title,
+                         upper     => $upper,
+                         condition => $condition };
+                   } else {
+                       $CASESPEC{$code} =
+                       { code      => $hexcode,
+                         lower     => $lower,
+                         title     => $title,
+                         upper     => $upper,
+                         condition => $condition };
+                   }
                }
            }
            close($CASESPECFH);
@@ -714,14 +718,72 @@ sub casespec {
 
     _casespec() unless %CASESPEC;
 
-    return $CASESPEC{$code};
+    return ref $CASESPEC{$code} ? dclone $CASESPEC{$code} : $CASESPEC{$code};
+}
+
+=head2 namedseq()
+
+    use Unicode::UCD 'namedseq';
+
+    my $namedseq = namedseq("KATAKANA LETTER AINU P");
+    my @namedseq = namedseq("KATAKANA LETTER AINU P");
+    my %namedseq = namedseq();
+
+If used with a single argument in a scalar context, returns the string
+consisting of the code points of the named sequence, or C<undef> if no
+named sequence by that name exists.  If used with a single argument in
+a list context, returns list of the code points.  If used with no
+arguments in a list context, returns a hash with the names of the
+named sequences as the keys and the named sequences as strings as
+the values.  Otherwise, returns C<undef> or empty list depending
+on the context.
+
+(New from Unicode 4.1.0)
+
+=cut
+
+my %NAMEDSEQ;
+
+sub _namedseq {
+    unless (%NAMEDSEQ) {
+       if (openunicode(\$NAMEDSEQFH, "NamedSequences.txt")) {
+           local $_;
+           while (<$NAMEDSEQFH>) {
+               if (/^(.+)\s*;\s*([0-9A-F]+(?: [0-9A-F]+)*)$/) {
+                   my ($n, $s) = ($1, $2);
+                   my @s = map { chr(hex($_)) } split(' ', $s);
+                   $NAMEDSEQ{$n} = join("", @s);
+               }
+           }
+           close($NAMEDSEQFH);
+       }
+    }
+}
+
+sub namedseq {
+    _namedseq() unless %NAMEDSEQ;
+    my $wantarray = wantarray();
+    if (defined $wantarray) {
+       if ($wantarray) {
+           if (@_ == 0) {
+               return %NAMEDSEQ;
+           } elsif (@_ == 1) {
+               my $s = $NAMEDSEQ{ $_[0] };
+               return defined $s ? map { ord($_) } split('', $s) : ();
+           }
+       } elsif (@_ == 1) {
+           return $NAMEDSEQ{ $_[0] };
+       }
+    }
+    return;
 }
 
 =head2 Unicode::UCD::UnicodeVersion
 
 Unicode::UCD::UnicodeVersion() returns the version of the Unicode
 Character Database, in other words, the version of the Unicode
-standard the database implements.
+standard the database implements.  The version is a string
+of numbers delimited by dots (C<'.'>).
 
 =cut
 
@@ -742,7 +804,12 @@ sub UnicodeVersion {
 
 The first use of charinfo() opens a read-only filehandle to the Unicode
 Character Database (the database is included in the Perl distribution).
-The filehandle is then kept open for further queries.
+The filehandle is then kept open for further queries.  In other words,
+if you are wondering where one of your filehandles went, that's where.
+
+=head1 BUGS
+
+Does not yet support EBCDIC platforms.
 
 =head1 AUTHOR