#!/usr/bin/perl use XML::Parser; use Pod::Usage; use Getopt::Std; use Font::TTF::PSNames; getopts('c:hp'); unless ($ARGV[0] || $opt_h) { pod2usage(1); exit; } if ($opt_h) { pod2usage(-verbose => 2, -noperldoc => 1); exit; } my ($indent, $currclass, $text, %classes, %properties, $isempty); my ($classlist, $propdict); my ($xml) = XML::Parser->new(Handlers => { Start => sub { my ($xp, $tag, %attrs) = @_; if ($tag eq 'class') { $currclass = [$attrs{'exts'}]; $classes{$attrs{'name'}} = $currclass; } elsif ($tag eq 'property') { $currclass = [$attrs{'value'}, $attrs{'exts'}]; $pname = $attrs{'name'}; push (@{$properties{$pname}}, $currclass); } $text = ''; }, End => sub { my ($xp, $tag) = @_; if ($tag eq 'class') { push (@{$currclass}, [split(' ', $text)]); } elsif ($tag eq 'property') { push (@{$currclass}, [split(' ', $text)]); if ($opt_p) { $classes{"${pname}_$currclass->[0]"} = [split(' ', $text)]; } } }, Char => sub { my ($xp, $str) = @_; $text .= $str; }}); $xml->parsefile($opt_c); $text = ''; my ($xml) = XML::Parser->new(Handlers => { Init => sub { print "\n"; }, Start => sub { my ($xp, $tag, %attrs) = @_; $isempty = dotext($text, $isempty); print ">" if ($isempty); start($xp, \$tag, \%attrs); print "\n" . (" " x $indent) . "<$tag"; foreach $k (sort keys %attrs) { print " $k='$attrs{$k}'"; } $isempty = 1; $indent += 4; $text = ''; }, End => sub { my ($xp, $tag) = @_; $isempty = dotext($text, $isempty); $isempty = end($xp, \$tag, $isempty); $indent -= 4; if ($isempty) { print "/>"; $isempty = 0; } else { print "\n" . (" " x $indent) . ""; } $text = ''; }, Char => sub { my ($xp, $str) = @_; $text .= $str; }}); if ($ARGV[1]) { open(OFH, "> $ARGV[1]") || die "Can't open $ARGV[1] for writing"; select OFH; } $xml->parsefile($ARGV[0]); sub dotext { my ($str, $isempty) = @_; $str =~ s/^\s+//o; $str =~ s/\s+$//o; if ($str) { print ">" if ($isempty); print $str; $isempty = 0; } return $isempty; } sub start { my ($xp, $tagr, $attrs) = @_; if ($$tagr eq 'glyph') { $classlist = get_classes(\%classes, $attrs->{'PSName'}, $attrs->{'UID'}); $propdict = get_properties(\%properties, $attrs->{'PSName'}, $attrs->{'UID'}); } elsif ($$tagr eq 'property' && $attrs->{'name'} eq 'classes') { my ($val) = merge_classes($classlist, $attrs->{'value'}); $attrs->{'value'} = $val; $classlist = undef; } elsif ($$tagr eq 'property' && defined $propdict->{$attrs->{'name'}}) { $attrs->{'value'} = delete $propdict->{$attrs->{'name'}}; } } sub end { my ($xp, $tagr, $isempty) = @_; my ($p); if ($$tagr eq 'glyph') { print ">" if ($isempty); if ($classlist) { print "\n" . (" " x $indent) . ""; } foreach $p (sort keys %{$propdict}) { print "\n" . (" " x $indent) . ""; } return 0; } return $isempty; } sub get_classes { my ($classes, $psname, $uid) = @_; my ($c, $res); foreach $c (keys %$classes) { my ($g, $e); foreach $e (split(' ', $classes->{$c}[0]), '') { foreach $g (@{$classes->{$c}[1]}) { if (match_glyph("$g$e", $psname, $uid)) { $res .= "$c "; last; } } } } $res =~ s/\s+$//o; return $res; } sub merge_classes { my ($list, $base) = @_; my (%list) = map {$_ => 1} split(' ', $list); my (%base) = map {$_ => 1} split(' ', $base); my (%res) = (%base, %list); my ($res) = join(" ", sort keys %res); return $res; } sub get_properties { my ($properties, $psname, $uid) = @_; my ($res, $p, $q, $g, $e); foreach $p (keys %{$properties}) { foreach $q (@{$properties->{$p}}) { foreach $e (split(' ', $q->[1]), '') { foreach $g (@{$q->[2]}) { if (match_glyph("$g$e", $psname, $uid)) { $res->{$p} = $q->[0]; } } } } } return $res; } # %name_cache to cache results of slow Font::TTF::PSName::parse(). Significant speed up. %name_cache = (); sub match_glyph { my ($name, $psname, $uid) = @_; my ($cname, $cpsname); return 1 if ($psname eq $name); $name_cache{$name} = canon($name) if (!defined $name_cache{$name}); $cname = $name_cache{$name}; return 1 if ($cname eq $uid); $name_cache{$psname} = canon($psname) if (!defined $name_cache{$psname}); $cpsname = $name_cache{$psname}; return 1 if ($cname eq $cpsname); } sub canon { my ($name) = @_; my ($uids, $exts) = Font::TTF::PSNames::parse($name); return $name unless scalar(@{$uids}); my ($res) = join("_", map {sprintf("%04X", $_)} @{$uids}); $res .= "." . join(".", @$exts) if (scalar @$exts); $res; } __END__ =head1 TITLE add_classes - add class information to an attachment point database =head1 SYNOPSIS add_classes -c classes.xml infile.xml For each glyph in the infile.xml attachment point database, find all the classes in classes.xml containing names that match the glyph. Ensure that the classes property contains a list of those classes that match. Print the results to stdout. =head1 OPTIONS -c classes.xml List of classes and their contents -h print manpage -p Create classes for each property value called property_value =head1 DESCRIPTION Inserting a classes property in an attachment point database allows one to create context classes in the generated GDL or VOLT. The DTD for the classes file is: =head1 SEE ALSO