1eb411b4bSmrg#! /usr/bin/perl
2eb411b4bSmrg#
35efbdfc3Smrg# Copyright (c) 2009, 2010, Oracle and/or its affiliates.
4eb411b4bSmrg#
5eb411b4bSmrg# Permission is hereby granted, free of charge, to any person obtaining a
6eb411b4bSmrg# copy of this software and associated documentation files (the "Software"),
7eb411b4bSmrg# to deal in the Software without restriction, including without limitation
8eb411b4bSmrg# the rights to use, copy, modify, merge, publish, distribute, sublicense,
9eb411b4bSmrg# and/or sell copies of the Software, and to permit persons to whom the
10eb411b4bSmrg# Software is furnished to do so, subject to the following conditions:
11eb411b4bSmrg#
12eb411b4bSmrg# The above copyright notice and this permission notice (including the next
13eb411b4bSmrg# paragraph) shall be included in all copies or substantial portions of the
14eb411b4bSmrg# Software.
15eb411b4bSmrg#
16eb411b4bSmrg# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17eb411b4bSmrg# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18eb411b4bSmrg# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
19eb411b4bSmrg# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20eb411b4bSmrg# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21eb411b4bSmrg# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
22eb411b4bSmrg# DEALINGS IN THE SOFTWARE.
23eb411b4bSmrg#
24eb411b4bSmrg
25eb411b4bSmrg#
26eb411b4bSmrg# Make a DocBook chart showing compose combinations for a locale
27eb411b4bSmrg#
28eb411b4bSmrg# See perldoc at end (or run with --help or --man options) for details
29eb411b4bSmrg# of command-line options.
30eb411b4bSmrg#
31eb411b4bSmrg
32eb411b4bSmrg# Compose file grammar is defined in modules/im/ximcp/imLcPrs.c
33eb411b4bSmrg
34eb411b4bSmrguse strict;
35eb411b4bSmrguse warnings;
36eb411b4bSmrguse Getopt::Long;
37eb411b4bSmrguse Pod::Usage;
38eb411b4bSmrg
39eb411b4bSmrgmy $error_count = 0;
40eb411b4bSmrg
41eb411b4bSmrgmy $charset;
42eb411b4bSmrgmy $locale_name;
43eb411b4bSmrgmy $output_filename = '-';
44eb411b4bSmrgmy $man = 0;
45eb411b4bSmrgmy $help = 0;
46eb411b4bSmrgmy $make_index = 0;
47eb411b4bSmrg
48eb411b4bSmrgGetOptions ('charset:s' => \$charset,
49eb411b4bSmrg	    'locale=s' => \$locale_name,
50eb411b4bSmrg	    'output=s' => \$output_filename,
51eb411b4bSmrg	    'index' => \$make_index,
52eb411b4bSmrg	    'help|?' => \$help,
53eb411b4bSmrg	    'man' => \$man)
54eb411b4bSmrg    or pod2usage(2);
55eb411b4bSmrgpod2usage(1) if $help;
56eb411b4bSmrgpod2usage(-exitstatus => 0, -verbose => 2) if $man;
57eb411b4bSmrg
58eb411b4bSmrgif (!defined($charset) || ($charset eq "")) {
59eb411b4bSmrg  if (defined($locale_name)) {
60eb411b4bSmrg    my $guessed_charset = $locale_name;
61eb411b4bSmrg    $guessed_charset =~ s{^.*\.}{};
62eb411b4bSmrg    if ($guessed_charset =~ m{^(utf-8|gbk|gb18030)$}i) {
63eb411b4bSmrg      $charset = $1;
64eb411b4bSmrg    } elsif ($guessed_charset =~ m{iso8859-(\d+)}i) {
65eb411b4bSmrg      $charset = "iso-8859-$1";
66eb411b4bSmrg    } elsif ($guessed_charset =~ m{^microsoft-cp(125\d)$}) {
67eb411b4bSmrg      $charset = "windows-$1";
68eb411b4bSmrg    }
69eb411b4bSmrg  }
70eb411b4bSmrg  if (!defined($charset) || ($charset eq "")) {
71eb411b4bSmrg    $charset = "utf-8";
72eb411b4bSmrg  }
73eb411b4bSmrg}
74eb411b4bSmrg
75eb411b4bSmrgif ($make_index) {
76eb411b4bSmrg  # Print Docbook output
77eb411b4bSmrg  open my $OUTPUT, '>', $output_filename
78eb411b4bSmrg      or die "Could not create $output_filename: $!";
79eb411b4bSmrg
80eb411b4bSmrg  print $OUTPUT
81eb411b4bSmrg      join ("\n",
82eb411b4bSmrg	    qq(<?xml version="1.0" encoding="$charset" ?>),
83eb411b4bSmrg	    q(<!DOCTYPE article PUBLIC "-//OASIS//DTD DocBook XML V4.3//EN"),
84eb411b4bSmrg	    q( "http://www.oasis-open.org/docbook/xml/4.3/docbookx.dtd">),
85eb411b4bSmrg	    q(<article id="libX11-keys">),
86eb411b4bSmrg	    q(  <articleinfo>),
87eb411b4bSmrg	    q(    <title>Xlib Compose Key Charts</title>),
88eb411b4bSmrg	    q(  </articleinfo>),
89eb411b4bSmrg	    ( map { qq(  <xi:include xmlns:xi="http://www.w3.org/2001/XInclude"  href="$_.xml">\
90eb411b4bSmrg    <xi:fallback><section><title>$_</title><para></para></section></xi:fallback>\
91eb411b4bSmrg  </xi:include>) }
92eb411b4bSmrg	      @ARGV ),
93eb411b4bSmrg	    q(</article>),
94eb411b4bSmrg	    "\n"
95eb411b4bSmrg      );
96eb411b4bSmrg
97eb411b4bSmrg  close $OUTPUT or die "Couldn't write $output_filename: $!";
98eb411b4bSmrg
99eb411b4bSmrg  exit(0);
100eb411b4bSmrg}
101eb411b4bSmrg
102eb411b4bSmrgforeach my $a (@ARGV) {
103eb411b4bSmrg  $error_count += make_compose_chart($a);
104eb411b4bSmrg}
105eb411b4bSmrg
106eb411b4bSmrgexit($error_count);
107eb411b4bSmrg
108eb411b4bSmrgsub make_compose_chart {
109eb411b4bSmrg  my ($filename) = @_;
110eb411b4bSmrg  my $errors = 0;
111eb411b4bSmrg
112eb411b4bSmrg  my @compose_table = ();
113eb411b4bSmrg  my @included_files = ();
114eb411b4bSmrg
115eb411b4bSmrg  my $line = 0;
116eb411b4bSmrg  my $pre_file = ($filename =~ m{\.pre$}) ? 1 : 0;
117eb411b4bSmrg  my $in_c_comment = 0;
118eb411b4bSmrg  my $in_comment = 0;
119eb411b4bSmrg  my $keyseq_count = 0;
120eb411b4bSmrg
121eb411b4bSmrg  open my $COMPOSE, '<', $filename or die "Could not open $filename: $!";
122eb411b4bSmrg
123eb411b4bSmrg COMPOSE_LINE:
124eb411b4bSmrg  while (my $cl = <$COMPOSE>) {
125eb411b4bSmrg    $line++;
126eb411b4bSmrg    chomp($cl);
127eb411b4bSmrg    my $original_line = $cl;
128eb411b4bSmrg
129eb411b4bSmrg    # Special handling for changes cpp makes to .pre files
130eb411b4bSmrg    if ($pre_file == 1) {
131eb411b4bSmrg      if ($in_c_comment) {		# Look for end of multi-line C comment
132eb411b4bSmrg	if ($cl =~ m{\*/(.*)$}) {
133eb411b4bSmrg	  $cl = $1;
134eb411b4bSmrg	  $in_c_comment = 0;
135eb411b4bSmrg	} else {
136eb411b4bSmrg	  next;
137eb411b4bSmrg	}
138eb411b4bSmrg      }
139eb411b4bSmrg      $cl =~ s{/\*.\**/}{};		# Remove single line C comments
140eb411b4bSmrg      if ($cl =~ m{^(.*)/\*}) {		# Start of a multi-line C comment
141eb411b4bSmrg	$cl = $1;
142eb411b4bSmrg	$in_c_comment = 1;
143eb411b4bSmrg      }
144eb411b4bSmrg      $cl =~ s{^\s*XCOMM}{#};		# Translate pre-processing comments
145eb411b4bSmrg    }
146eb411b4bSmrg
147eb411b4bSmrg    chomp($cl);
148eb411b4bSmrg
149eb411b4bSmrg    if ($cl =~ m{^\s*#\s*(.*)$}) {	# Comment only lines
1509c019ec5Smaya      # Combine comment blocks
151eb411b4bSmrg      my $comment = $1;
152eb411b4bSmrg
153eb411b4bSmrg      if ($in_comment) {
154eb411b4bSmrg	my $prev_comment = pop @compose_table;
155eb411b4bSmrg	$comment = join(' ', $prev_comment->{-comment}, $comment);
156eb411b4bSmrg      } else {
157eb411b4bSmrg	$in_comment = 1;
158eb411b4bSmrg      }
159eb411b4bSmrg
160eb411b4bSmrg      push @compose_table, { -type => 'comment', -comment => $comment };
161eb411b4bSmrg      next COMPOSE_LINE;
162eb411b4bSmrg    }
163eb411b4bSmrg
164eb411b4bSmrg    $in_comment = 0;
165eb411b4bSmrg
166eb411b4bSmrg    if ($cl =~ m{^\s*$}) {		# Skip blank lines
167eb411b4bSmrg      next COMPOSE_LINE;
168eb411b4bSmrg    }
169eb411b4bSmrg    elsif ($cl =~ m{^(STATE\s+|END_STATE)}) {
170eb411b4bSmrg      # Sun extension to compose file syntax
171eb411b4bSmrg      next COMPOSE_LINE;
172eb411b4bSmrg    }
173eb411b4bSmrg    elsif ($cl =~ m{^([^:]+)\s*:\s*(.+)$}) {
174eb411b4bSmrg      my ($seq, $action) = ($1, $2);
175eb411b4bSmrg      $seq =~ s{\s+$}{};
176eb411b4bSmrg
177eb411b4bSmrg      my @keys = grep { $_ !~ m/^\s*$/ } split /[\s\<\>]+/, $seq;
178eb411b4bSmrg
179eb411b4bSmrg      push @compose_table, {
180eb411b4bSmrg	-type => 'keyseq',
181eb411b4bSmrg	-keys => [ @keys ],
182eb411b4bSmrg	-action => $action
183eb411b4bSmrg      };
184eb411b4bSmrg      $keyseq_count++;
185eb411b4bSmrg      next COMPOSE_LINE;
186eb411b4bSmrg    } elsif ($cl =~ m{^(STATE_TYPE:|\@StartDeadKeyMap|\@EndDeadKeyMap)}) {
187eb411b4bSmrg      # ignore
188eb411b4bSmrg      next COMPOSE_LINE;
189eb411b4bSmrg    } elsif ($cl =~ m{^include "(.*)"}) {
190eb411b4bSmrg      my $incpath = $1;
191eb411b4bSmrg      $incpath =~ s{^X11_LOCALEDATADIR/(.*)/Compose}{the $1 compose table};
192eb411b4bSmrg
193eb411b4bSmrg      push @included_files, $incpath;
194eb411b4bSmrg      next COMPOSE_LINE;
195eb411b4bSmrg    } else {
196eb411b4bSmrg      print STDERR ('Unrecognized pattern in ', $filename,
197eb411b4bSmrg		    ' on line #', $line, ":\n  ", $cl, "\n");
198eb411b4bSmrg    }
199eb411b4bSmrg  }
200eb411b4bSmrg  close $COMPOSE;
201eb411b4bSmrg
202eb411b4bSmrg  if ($errors > 0) {
203eb411b4bSmrg    return $errors;
204eb411b4bSmrg  }
205eb411b4bSmrg
206eb411b4bSmrg  # Print Docbook output
207eb411b4bSmrg  open my $OUTPUT, '>', $output_filename
208eb411b4bSmrg      or die "Could not create $output_filename: $!";
209eb411b4bSmrg
210eb411b4bSmrg  print $OUTPUT
211eb411b4bSmrg      join ("\n",
212eb411b4bSmrg	    qq(<?xml version="1.0" encoding="$charset" ?>),
213eb411b4bSmrg	    q(<!DOCTYPE section PUBLIC "-//OASIS//DTD DocBook XML V4.3//EN"),
214eb411b4bSmrg	    q( "http://www.oasis-open.org/docbook/xml/4.3/docbookx.dtd">),
215eb411b4bSmrg	    qq(<section id="$locale_name">),
216eb411b4bSmrg	    qq(<title>Xlib Compose Keys for $locale_name</title>),
217eb411b4bSmrg	    q(<para>Applications using Xlib input handling should recognize),
218eb411b4bSmrg	    q( these compose key sequences in locales using the),
219eb411b4bSmrg	    qq( $locale_name compose table.</para>),
220eb411b4bSmrg	    "\n"
221eb411b4bSmrg      );
222eb411b4bSmrg
223eb411b4bSmrg  if (@included_files) {
224eb411b4bSmrg    print $OUTPUT
225eb411b4bSmrg	q(<para>This compose table includes the non-conflicting),
226eb411b4bSmrg	q( entries from: ),
227eb411b4bSmrg	join(',', @included_files),
228eb411b4bSmrg	q(.  Those entries are not shown here - see those charts for the),
229eb411b4bSmrg	q( included key sequences.</para>),
230eb411b4bSmrg	"\n";
231eb411b4bSmrg  }
232eb411b4bSmrg
233eb411b4bSmrg  my @pretable_comments = ();
234eb411b4bSmrg
235eb411b4bSmrg  if ($keyseq_count == 0) {
236eb411b4bSmrg    @pretable_comments = @compose_table;
237eb411b4bSmrg  } elsif ($compose_table[0]->{-type} eq 'comment') {
238eb411b4bSmrg    push @pretable_comments, shift @compose_table;
239eb411b4bSmrg  }
240eb411b4bSmrg
241eb411b4bSmrg  foreach my $comment_ref (@pretable_comments) {
242eb411b4bSmrg    print $OUTPUT
243eb411b4bSmrg	qq(<para>), xml_escape($comment_ref->{-comment}), qq(</para>\n);
244eb411b4bSmrg  }
245eb411b4bSmrg
246eb411b4bSmrg  if ($keyseq_count > 0) {
247eb411b4bSmrg    start_table($OUTPUT);
248eb411b4bSmrg    my $row_count = 0;
249eb411b4bSmrg
250eb411b4bSmrg    foreach my $cr (@compose_table) {
251eb411b4bSmrg
252eb411b4bSmrg      if ($row_count++ > 750) {
253eb411b4bSmrg	# Break tables every 750 rows to avoid overflowing
254eb411b4bSmrg	# xmlto/xsltproc limits on the largest tables
255eb411b4bSmrg	end_table($OUTPUT);
256eb411b4bSmrg	start_table($OUTPUT);
257eb411b4bSmrg	$row_count = 0;
258eb411b4bSmrg      }
259eb411b4bSmrg
260eb411b4bSmrg      if ($cr->{-type} eq 'comment') {
261eb411b4bSmrg	print $OUTPUT
262eb411b4bSmrg	    qq(<row><entry namest='seq' nameend='action'>),
263eb411b4bSmrg	    xml_escape($cr->{-comment}), qq(</entry></row>\n);
264eb411b4bSmrg      } elsif ($cr->{-type} eq 'keyseq') {
265eb411b4bSmrg	my $action = join(" ", xml_escape($cr->{-action}));
266eb411b4bSmrg	if ($action =~ m{^\s*"\\([0-7]+)"}) {
267eb411b4bSmrg	  my $char = oct($1);
268eb411b4bSmrg	  if ($char >= 32) {
269eb411b4bSmrg	    $action =~ s{^\s*"\\[0-7]+"}{"&#$char;"};
270eb411b4bSmrg	  }
271eb411b4bSmrg	}
272eb411b4bSmrg	$action =~ s{^\s*"(.+)"}{"$1"};
273eb411b4bSmrg
274eb411b4bSmrg	print $OUTPUT
275eb411b4bSmrg	    qq(<row><entry>),
276eb411b4bSmrg	    qq(<keycombo action='seq'>),
277eb411b4bSmrg	    (map { qq(<keysym>$_</keysym>) } xml_escape(@{$cr->{-keys}})),
278eb411b4bSmrg	    qq(</keycombo>),
279eb411b4bSmrg	    qq(</entry><entry>),
280eb411b4bSmrg	    $action,
281eb411b4bSmrg	    qq(</entry></row>\n);
282eb411b4bSmrg      }
283eb411b4bSmrg    }
284eb411b4bSmrg
285eb411b4bSmrg    end_table($OUTPUT);
286eb411b4bSmrg  } else {
287eb411b4bSmrg    print $OUTPUT
288eb411b4bSmrg	qq(<para><emphasis>),
289eb411b4bSmrg	qq(This compose table defines no sequences of its own.),
290eb411b4bSmrg	qq(</emphasis></para>\n);
291eb411b4bSmrg  }
292eb411b4bSmrg  print $OUTPUT "</section>\n";
293eb411b4bSmrg
294eb411b4bSmrg  close $OUTPUT or die "Couldn't write $output_filename: $!";
295eb411b4bSmrg
296eb411b4bSmrg  return $errors;
297eb411b4bSmrg}
298eb411b4bSmrg
299eb411b4bSmrgsub xml_escape {
300eb411b4bSmrg  my @output;
301eb411b4bSmrg
302eb411b4bSmrg  foreach my $l (@_) {
303eb411b4bSmrg      $l =~ s{\&}{&amp;}g;
304eb411b4bSmrg      $l =~ s{\<}{&lt;}g;
305eb411b4bSmrg      $l =~ s{\>}{&gt;}g;
306eb411b4bSmrg      push @output, $l;
307eb411b4bSmrg  }
308eb411b4bSmrg  return @output;
309eb411b4bSmrg}
310eb411b4bSmrg
311eb411b4bSmrgsub start_table {
312eb411b4bSmrg  my ($OUTPUT) = @_;
313eb411b4bSmrg
314eb411b4bSmrg  print $OUTPUT
315eb411b4bSmrg      join("\n",
316eb411b4bSmrg	   qq(<table><title>Compose Key Sequences for $locale_name</title>),
317eb411b4bSmrg	   qq(<tgroup cols='2'>),
318eb411b4bSmrg	   qq( <colspec colname='seq' /><colspec colname='action' />),
319eb411b4bSmrg	   qq( <thead><row>),
320eb411b4bSmrg	   qq(  <entry>Key Sequence</entry><entry>Action</entry>),
321eb411b4bSmrg	   qq( </row></thead>),
322eb411b4bSmrg	   qq( <tbody>\n),
323eb411b4bSmrg      );
324eb411b4bSmrg}
325eb411b4bSmrg
326eb411b4bSmrgsub end_table {
327eb411b4bSmrg  my ($OUTPUT) = @_;
328eb411b4bSmrg
329eb411b4bSmrg  print $OUTPUT "</tbody>\n</tgroup>\n</table>\n";
330eb411b4bSmrg}
331eb411b4bSmrg
332eb411b4bSmrg__END__
333eb411b4bSmrg
334eb411b4bSmrg=head1 NAME
335eb411b4bSmrg
336eb411b4bSmrgcompose-chart - Make DocBook/XML charts of compose table entries
337eb411b4bSmrg
338eb411b4bSmrg=head1 SYNOPSIS
339eb411b4bSmrg
340eb411b4bSmrgcompose-chart [options] [file ...]
341eb411b4bSmrg
342eb411b4bSmrg Options:
343eb411b4bSmrg    --charset[=<cset>]	character set to specify in XML doctype
344eb411b4bSmrg    --locale=<locale>	name of locale to display in chart
345eb411b4bSmrg    --output=<file>	filename to output chart to
346eb411b4bSmrg    --index		make index of charts instead of individual chart
347eb411b4bSmrg    --help		brief help message
348eb411b4bSmrg    --man		full documentation
349eb411b4bSmrg
350eb411b4bSmrg=head1 OPTIONS
351eb411b4bSmrg
352eb411b4bSmrg=over 8
353eb411b4bSmrg
354eb411b4bSmrg=item B<--charset>[=I<cset>]
355eb411b4bSmrg
356eb411b4bSmrgSpecify a character set to list in the doctype declaration in the XML output.
357eb411b4bSmrgIf not specified, attempts to guess from the locale name, else default to
358eb411b4bSmrg"utf-8".
359eb411b4bSmrg
360eb411b4bSmrg=item B<--locale>=I<locale>
361eb411b4bSmrg
362eb411b4bSmrgSpecify the locale name to use in the chart titles and introductory text.
363eb411b4bSmrg
364eb411b4bSmrg=item B<--output>=I<file>
365eb411b4bSmrg
366eb411b4bSmrgSpecify the output file to write the DocBook output to.
367eb411b4bSmrg
368eb411b4bSmrg=item B<--index>
369eb411b4bSmrg
370eb411b4bSmrgGenerate an index of the listed locale charts instead of a chart for a
371eb411b4bSmrgspecific locale.
372eb411b4bSmrg
373eb411b4bSmrg=item B<--help>
374eb411b4bSmrg
375eb411b4bSmrgPrint a brief help message and exit.
376eb411b4bSmrg
377eb411b4bSmrg=item B<--man>
378eb411b4bSmrg
379eb411b4bSmrgPrint the manual page and exit.
380eb411b4bSmrg
381eb411b4bSmrg=back
382eb411b4bSmrg
383eb411b4bSmrg=head1 DESCRIPTION
384eb411b4bSmrg
385eb411b4bSmrgThis program will read the given compose table file(s) and generate
386eb411b4bSmrgDocBook/XML charts listing the available characters for end-user reference.
387eb411b4bSmrg
388eb411b4bSmrg=cut
389