202 lines
5.9 KiB
Perl
202 lines
5.9 KiB
Perl
package Encode::CN::HZ;
|
|
|
|
use strict;
|
|
use warnings;
|
|
use utf8 ();
|
|
|
|
use vars qw($VERSION);
|
|
$VERSION = do { my @r = ( q$Revision: 2.10 $ =~ /\d+/g ); sprintf "%d." . "%02d" x $#r, @r };
|
|
|
|
use Encode qw(:fallbacks);
|
|
|
|
use parent qw(Encode::Encoding);
|
|
__PACKAGE__->Define('hz');
|
|
|
|
# HZ is a combination of ASCII and escaped GB, so we implement it
|
|
# with the GB2312(raw) encoding here. Cf. RFCs 1842 & 1843.
|
|
|
|
# not ported for EBCDIC. Which should be used, "~" or "\x7E"?
|
|
|
|
sub needs_lines { 1 }
|
|
|
|
sub decode ($$;$) {
|
|
my ( $obj, $str, $chk ) = @_;
|
|
return undef unless defined $str;
|
|
|
|
my $GB = Encode::find_encoding('gb2312-raw');
|
|
my $ret = substr($str, 0, 0); # to propagate taintedness
|
|
my $in_ascii = 1; # default mode is ASCII.
|
|
|
|
while ( length $str ) {
|
|
if ($in_ascii) { # ASCII mode
|
|
if ( $str =~ s/^([\x00-\x7D\x7F]+)// ) { # no '~' => ASCII
|
|
$ret .= $1;
|
|
|
|
# EBCDIC should need ascii2native, but not ported.
|
|
}
|
|
elsif ( $str =~ s/^\x7E\x7E// ) { # escaped tilde
|
|
$ret .= '~';
|
|
}
|
|
elsif ( $str =~ s/^\x7E\cJ// ) { # '\cJ' == LF in ASCII
|
|
1; # no-op
|
|
}
|
|
elsif ( $str =~ s/^\x7E\x7B// ) { # '~{'
|
|
$in_ascii = 0; # to GB
|
|
}
|
|
else { # encounters an invalid escape, \x80 or greater
|
|
last;
|
|
}
|
|
}
|
|
else { # GB mode; the byte ranges are as in RFC 1843.
|
|
no warnings 'uninitialized';
|
|
if ( $str =~ s/^((?:[\x21-\x77][\x21-\x7E])+)// ) {
|
|
my $prefix = $1;
|
|
$ret .= $GB->decode( $prefix, $chk );
|
|
}
|
|
elsif ( $str =~ s/^\x7E\x7D// ) { # '~}'
|
|
$in_ascii = 1;
|
|
}
|
|
else { # invalid
|
|
last;
|
|
}
|
|
}
|
|
}
|
|
$_[1] = '' if $chk; # needs_lines guarantees no partial character
|
|
return $ret;
|
|
}
|
|
|
|
sub cat_decode {
|
|
my ( $obj, undef, $src, $pos, $trm, $chk ) = @_;
|
|
my ( $rdst, $rsrc, $rpos ) = \@_[ 1 .. 3 ];
|
|
|
|
my $GB = Encode::find_encoding('gb2312-raw');
|
|
my $ret = '';
|
|
my $in_ascii = 1; # default mode is ASCII.
|
|
|
|
my $ini_pos = pos($$rsrc);
|
|
|
|
substr( $src, 0, $pos ) = '';
|
|
|
|
my $ini_len = bytes::length($src);
|
|
|
|
# $trm is the first of the pair '~~', then 2nd tilde is to be removed.
|
|
# XXX: Is better C<$src =~ s/^\x7E// or die if ...>?
|
|
$src =~ s/^\x7E// if $trm eq "\x7E";
|
|
|
|
while ( length $src ) {
|
|
my $now;
|
|
if ($in_ascii) { # ASCII mode
|
|
if ( $src =~ s/^([\x00-\x7D\x7F])// ) { # no '~' => ASCII
|
|
$now = $1;
|
|
}
|
|
elsif ( $src =~ s/^\x7E\x7E// ) { # escaped tilde
|
|
$now = '~';
|
|
}
|
|
elsif ( $src =~ s/^\x7E\cJ// ) { # '\cJ' == LF in ASCII
|
|
next;
|
|
}
|
|
elsif ( $src =~ s/^\x7E\x7B// ) { # '~{'
|
|
$in_ascii = 0; # to GB
|
|
next;
|
|
}
|
|
else { # encounters an invalid escape, \x80 or greater
|
|
last;
|
|
}
|
|
}
|
|
else { # GB mode; the byte ranges are as in RFC 1843.
|
|
if ( $src =~ s/^((?:[\x21-\x77][\x21-\x7F])+)// ) {
|
|
$now = $GB->decode( $1, $chk );
|
|
}
|
|
elsif ( $src =~ s/^\x7E\x7D// ) { # '~}'
|
|
$in_ascii = 1;
|
|
next;
|
|
}
|
|
else { # invalid
|
|
last;
|
|
}
|
|
}
|
|
|
|
next if !defined $now;
|
|
|
|
$ret .= $now;
|
|
|
|
if ( $now eq $trm ) {
|
|
$$rdst .= $ret;
|
|
$$rpos = $ini_pos + $pos + $ini_len - bytes::length($src);
|
|
pos($$rsrc) = $ini_pos;
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
$$rdst .= $ret;
|
|
$$rpos = $ini_pos + $pos + $ini_len - bytes::length($src);
|
|
pos($$rsrc) = $ini_pos;
|
|
return ''; # terminator not found
|
|
}
|
|
|
|
sub encode($$;$) {
|
|
my ( $obj, $str, $chk ) = @_;
|
|
return undef unless defined $str;
|
|
|
|
my $GB = Encode::find_encoding('gb2312-raw');
|
|
my $ret = substr($str, 0, 0); # to propagate taintedness;
|
|
my $in_ascii = 1; # default mode is ASCII.
|
|
|
|
no warnings 'utf8'; # $str may be malformed UTF8 at the end of a chunk.
|
|
|
|
while ( length $str ) {
|
|
if ( $str =~ s/^([[:ascii:]]+)// ) {
|
|
my $tmp = $1;
|
|
$tmp =~ s/~/~~/g; # escapes tildes
|
|
if ( !$in_ascii ) {
|
|
$ret .= "\x7E\x7D"; # '~}'
|
|
$in_ascii = 1;
|
|
}
|
|
$ret .= pack 'a*', $tmp; # remove UTF8 flag.
|
|
}
|
|
elsif ( $str =~ s/(.)// ) {
|
|
my $s = $1;
|
|
my $tmp = $GB->encode( $s, $chk || 0 );
|
|
last if !defined $tmp;
|
|
if ( length $tmp == 2 ) { # maybe a valid GB char (XXX)
|
|
if ($in_ascii) {
|
|
$ret .= "\x7E\x7B"; # '~{'
|
|
$in_ascii = 0;
|
|
}
|
|
$ret .= $tmp;
|
|
}
|
|
elsif ( length $tmp ) { # maybe FALLBACK in ASCII (XXX)
|
|
if ( !$in_ascii ) {
|
|
$ret .= "\x7E\x7D"; # '~}'
|
|
$in_ascii = 1;
|
|
}
|
|
$ret .= $tmp;
|
|
}
|
|
}
|
|
else { # if $str is malformed UTF8 *and* if length $str != 0.
|
|
last;
|
|
}
|
|
}
|
|
$_[1] = $str if $chk;
|
|
|
|
# The state at the end of the chunk is discarded, even if in GB mode.
|
|
# That results in the combination of GB-OUT and GB-IN, i.e. "~}~{".
|
|
# Parhaps it is harmless, but further investigations may be required...
|
|
|
|
if ( !$in_ascii ) {
|
|
$ret .= "\x7E\x7D"; # '~}'
|
|
$in_ascii = 1;
|
|
}
|
|
utf8::encode($ret); # https://rt.cpan.org/Ticket/Display.html?id=35120
|
|
return $ret;
|
|
}
|
|
|
|
1;
|
|
__END__
|
|
|
|
=head1 NAME
|
|
|
|
Encode::CN::HZ -- internally used by Encode::CN
|
|
|
|
=cut
|