dump_selects.pl 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629
  1. #!/usr/bin/perl
  2. #
  3. # Generate select lists from ser/sip-router select initializations structs.
  4. # (run on files generated by gcc -fdump-translation-unit -c file.c,
  5. # try -h for help)
  6. # E.g.: dump_selects.pl --file cfg_core.c --defs="-DUSE_SCTP ..."
  7. #
  8. # History:
  9. # =======
  10. # 2009-10-18 initial version (Andrei Pelinescu-Onciul <[email protected]>)
  11. #
  12. # Note: uses GCC::TranslationUnit (see cpan) with the following patch:
  13. #@@ -251,6 +251,8 @@
  14. # $node->{vector}[$key] = $value;
  15. # } elsif($key =~ /^op (\d+)$/) {
  16. # $node->{operand}[$1] = $value;
  17. #+ } elsif ($key eq "val") {
  18. #+ push @{$node->{$key}}, ($value) ;
  19. # } else {
  20. # $node->{$key} = $value;
  21. # }
  22. #
  23. #
  24. # Assumptions:
  25. # - the first array of type select_row_t with an initializer is the array
  26. # with the select definitions. Only one select_row_t array per file is
  27. # supported.
  28. #
  29. # Output notes:
  30. #
  31. use strict;
  32. use warnings;
  33. use Getopt::Long;
  34. use File::Temp qw(:mktemp);
  35. use File::Basename;
  36. use lib "/home/andrei/perl/modules/share/perl/5.10.1";
  37. use GCC::TranslationUnit;
  38. # text printed if we discover that GCC::TranslationUnit is unpatched
  39. my $patch_required="$0 requires a patched GCC:TranslationUnit, see the " .
  40. "comments at the beginning of the file or try --patch\n";
  41. # gcc name
  42. my $gcc="gcc";
  43. # default defines
  44. my $c_defs="-D__CPU_i386 -D__OS_linux -DSER_VER=2099099 -DPKG_MALLOC -DSHM_MEM -DSHM_MMAP -DDNS_IP_HACK -DUSE_IPV6 -DUSE_MCAST -DUSE_TCP -DUSE_DNS_CACHE -DUSE_DNS_FAILOVER -DUSE_DST_BLACKLIST -DUSE_NAPTR -DUSE_TLS -DTLS_HOOKS -DFAST_LOCK -DCC_GCC_LIKE_ASM -DHAVE_GETHOSTBYNAME2 -DHAVE_UNION_SEMUN -DHAVE_SCHED_YIELD -DHAVE_MSG_NOSIGNAL -DHAVE_MSGHDR_MSG_CONTROL -DHAVE_ALLOCA_H -DHAVE_SCHED_SETSCHEDULER -DHAVE_EPOLL -DUSE_SCTP -DNAME='\"ser\"' -DVERSION='\"2.99.99-pre3\"' -DARCH='\"i386\"' -DOS_QUOTED='\"linux\"' -DSER_MOD_INTERFACE";
  45. # file with gcc syntax tree
  46. my $file;
  47. my $core_file;
  48. my $src_fname;
  49. # type to look for
  50. my $var_type="select_row_t";
  51. my $tu;
  52. my $node;
  53. my $i;
  54. my @sel_exports; # filled with select definitions (select_row_t)
  55. my @core_exports; # filled with select definitions from core (if -c is used)
  56. my ($sel_grp_name, $sel_var_name);
  57. my ($opt_help, $opt_txt, $opt_is_tu, $dbg, $opt_grp_name, $opt_patch);
  58. my ($opt_force_grp_name, $opt_docbook);
  59. # default output formats
  60. my $output_format_header="HEADER";
  61. my $output_format_footer="FOOTER";
  62. my $output_format_selline="SELLINE";
  63. sub show_patch
  64. {
  65. my $patch='
  66. --- GCC/TranslationUnit.pm.orig 2009-10-16 17:57:51.275963053 +0200
  67. +++ GCC/TranslationUnit.pm 2009-10-16 20:17:28.128455959 +0200
  68. @@ -251,6 +251,8 @@
  69. $node->{vector}[$key] = $value;
  70. } elsif($key =~ /^op (\d+)$/) {
  71. $node->{operand}[$1] = $value;
  72. + } elsif ($key eq "val") {
  73. + push @{$node->{$key}}, ($value) ;
  74. } else {
  75. $node->{$key} = $value;
  76. }
  77. ';
  78. print $patch;
  79. }
  80. sub help
  81. {
  82. $~ = "USAGE";
  83. write;
  84. format USAGE =
  85. Usage @* -f filename | --file filename [options...]
  86. $0
  87. Options:
  88. -f filename - use filename for input (see also -T/--tu).
  89. --file filename - same as -f.
  90. -c | --core filename - location of core selects (used to resolve
  91. module selects that refer in-core functions).
  92. -h | -? | --help - this help message.
  93. -D | --dbg | --debug - enable debugging messages.
  94. -d | --defs - defines to be passed on gcc's command line
  95. (e.g. --defs="-DUSE_SCTP -DUSE_TCP").
  96. -g | --grp name
  97. --group name - select group name used if one cannot be
  98. autodetected (e.g. no default value
  99. initializer present in the file).
  100. -G | --force-grp name
  101. --force-group name - force using a select group name, even if one
  102. is autodetected (see also -g).
  103. --gcc gcc_name - run gcc_name instead of gcc.
  104. -t | --txt - text mode output.
  105. --docbook | --xml - docbook output (xml).
  106. -T | --tu - the input file is in raw gcc translation
  107. unit format (as produced by
  108. gcc -fdump-translation-unit -c ). If not
  109. present it's assumed that the file contains
  110. C code.
  111. -s | --src | --source - name of the source file, needed only if
  112. the input file is in "raw" translation
  113. unit format (--tu) and usefull to restrict
  114. and speed-up the search.
  115. --patch - show patches needed for the
  116. GCC::TranslationUnit package.
  117. .
  118. }
  119. # escape a string for xml use
  120. # params: string to be escaped
  121. # return: escaped string
  122. sub xml_escape{
  123. my $s=shift;
  124. my %escapes = (
  125. '"' => '&quot;',
  126. "'" => '&apos;',
  127. '&' => '&amp;',
  128. '<' => '&lt;',
  129. '>' => '&gt;'
  130. );
  131. $s=~s/(["'&<>])/$escapes{$1}/g;
  132. return $s;
  133. }
  134. # escape a string according with the output requirements
  135. # params: string to be escaped
  136. # return: escaped string
  137. sub output_esc{
  138. return xml_escape(shift) if defined $opt_docbook;
  139. return shift;
  140. }
  141. # eliminate casts and expressions.
  142. # (always go on the first operand)
  143. # params: node (GCC::Node)
  144. # result: if node is an expression it will walk on operand(0) until first non
  145. # expression element is found
  146. sub expr_op0{
  147. my $n=shift;
  148. while(($n->isa('GCC::Node::Expression') || $n->isa('GCC::Node::Unary')) &&
  149. defined $n->operand(0)) {
  150. $n=$n->operand(0);
  151. }
  152. return $n;
  153. }
  154. # constants (from select.h)
  155. use constant {
  156. MAX_SELECT_PARAMS => 32,
  157. MAX_NESTED_CALLS => 4,
  158. };
  159. use constant DIVERSION_MASK => 0x00FF;
  160. use constant {
  161. DIVERSION => 1<<8,
  162. SEL_PARAM_EXPECTED => 1<<9,
  163. CONSUME_NEXT_STR => 1<<10,
  164. CONSUME_NEXT_INT => 1<<11,
  165. CONSUME_ALL => 1<<12,
  166. OPTIONAL => 1<<13,
  167. NESTED => 1<<14,
  168. FIXUP_CALL => 1<<15,
  169. };
  170. use constant {
  171. SEL_PARAM_INT => 0,
  172. SEL_PARAM_STR => 1,
  173. SEL_PARAM_DIV => 2,
  174. SEL_PARAM_PTR => 3,
  175. };
  176. # Select rules (pasted from one email from Jan):
  177. # Roughly the rules are as follows:
  178. # * The first component of the row tells the select compiler in what state the
  179. # row can be considered.
  180. # * The second component tells the compiler what type of components is expected
  181. # for the row to match. SEL_PARAM_STR means that .foo should follow,
  182. # SEL_PARAM_INT means that [1234] should follow.
  183. # * The third component is the string to be matched for string components and
  184. # STR_NULL if the next expected component is an integer.
  185. # * The fourth component is a function name. This is either the function to be
  186. # called if this is the last rule all constrains are met, or it is the next
  187. # state to transition into if we are not processing the last component of the
  188. # select identifier.
  189. #
  190. # * The fifth rule are flags that can impose further constrains on how the
  191. # given line is to be used. Some of them are:
  192. #
  193. # - CONSUME_NEXT_INT - This tells the compiler that there must be at least one
  194. # more component following the current one, but it won't transition into the
  195. # next state, instead the current function will "consume" the integer as
  196. # parameters.
  197. #
  198. # - CONSUME_NEXT_STR - Same as previous, but the next component must be a
  199. # string.
  200. # - SEL_PARAM_EXPECTED - The current row must not be last and there must be
  201. # another row to transition to.
  202. #
  203. # - OPTIONAL - There may or may not be another component, but in any case the
  204. # compiler does not transition into another state (row). This can be used
  205. # together with CONSUME_NEXT_{STR,INT} to implement optional parameters, for
  206. # example .param could return a string of all parameters, while .param.abc
  207. # will only return the value of parameter abc.
  208. #
  209. # - NESTED - When this flag is present in a rule then it means that the
  210. # previous function should be called before the current one. The original
  211. # idea was that all select identifiers would only resolve to one function
  212. # call, however, it turned out to be inconvenient in some cases so we added
  213. # this. This is typically used in selects that have URIs as components. In
  214. # that case it is desirable to support all subcomponents for uri selects, but
  215. # it does not make sense to reimplement them for every single case. In that
  216. # case the uri components sets NESTED flags which causes the "parent"
  217. # function to be called first. The "parent" function extracts only the URI
  218. # which is then passed to the corresponding URI parsing function. The word
  219. # NESTED here means "nested function call".
  220. #
  221. # - CONSUME_ALL - Any number of select identifier components may follow and
  222. # they may be of any types. This flag causes the function on the current row
  223. # to be called and it is up to the function to handle the remainder of the
  224. # select identifier.
  225. # generate all select strings starting with a specific "root" function
  226. # params:
  227. # $1 - root
  228. # $2 - crt_label/skip (if !="" skip print and use it to search the next valid
  229. # sel. param)
  230. sub gen_selects
  231. {
  232. my $root=shift;
  233. my $crt_label=shift;
  234. my $skip_next;
  235. my @matches;
  236. my ($prev, $type, $name, $new_f, $flags);
  237. my $m;
  238. my @ret=();
  239. my @sel;
  240. @matches = grep((${$_}[0] eq $root) &&
  241. (!defined $crt_label || $crt_label eq "" ||
  242. ${$_}[2] eq "" ||
  243. $crt_label eq ${$_}[2]) , @sel_exports);
  244. if ((@matches == 0) && (@core_exports > 0)) {
  245. @matches = grep((${$_}[0] eq $root) &&
  246. (!defined $crt_label || $crt_label eq "" ||
  247. ${$_}[2] eq "" ||
  248. $crt_label eq ${$_}[2]), @core_exports);
  249. }
  250. for $m (@matches) {
  251. my $s="";
  252. ($prev, $type, $name, $new_f, $flags)=@$m;
  253. if (($flags & (NESTED|CONSUME_NEXT_STR|CONSUME_NEXT_INT)) == NESTED){
  254. $skip_next=$name;
  255. }
  256. if (!($flags & NESTED) ||
  257. (($flags & NESTED) && ($type !=SEL_PARAM_INT))){
  258. # Note: unnamed NESTED params are not allowed --andrei
  259. if ($type==SEL_PARAM_INT){
  260. $s.="[integer]";
  261. }else{
  262. if ($name ne "") {
  263. if (!defined $crt_label || $crt_label eq "") {
  264. $s.=(($prev eq "0" || $prev eq "")?"@":".") . $name;
  265. }
  266. }elsif (!($flags & NESTED) &&
  267. (!defined $crt_label || $crt_label eq "")){
  268. $s.=".<string>";
  269. }
  270. }
  271. }
  272. if ( !($flags & NESTED) &&
  273. ($flags & (CONSUME_NEXT_STR|CONSUME_NEXT_INT|CONSUME_ALL)) ){
  274. #process params
  275. if ($flags & OPTIONAL){
  276. $s.="{";
  277. }
  278. # add param name
  279. if ($flags & CONSUME_NEXT_STR){
  280. $s.="[\"string\"]";
  281. }elsif ($flags & CONSUME_NEXT_INT){
  282. $s.="[integer]";
  283. }else{
  284. $s.=".*"; # CONSUME_ALL
  285. }
  286. if ($flags & OPTIONAL){
  287. $s.="}";
  288. }
  289. }
  290. if (!($flags & SEL_PARAM_EXPECTED)){
  291. # if optional terminal add it to the list along with all the
  292. # variants
  293. if ($new_f eq "" || $new_f eq "0"){
  294. # terminal
  295. push @ret, $s;
  296. }else{
  297. @sel=map("$s$_", gen_selects($new_f, $skip_next));
  298. if (@sel > 0) {
  299. push(@ret, $s) if (!($s eq "") && !($flags & NESTED));
  300. push @ret, @sel;
  301. }else{
  302. if ($flags & NESTED) {
  303. $s.="*";
  304. }
  305. push @ret, $s;
  306. }
  307. }
  308. }else{
  309. # non-terminal
  310. if (!($new_f eq "" || $new_f eq "0")){
  311. @sel=map("$s$_", gen_selects($new_f, $skip_next));
  312. if (@sel > 0) {
  313. push @ret, @sel;
  314. }elsif ($flags & NESTED){
  315. $s.="*";
  316. push @ret, $s;
  317. }
  318. } # else nothing left, but param expected => error
  319. }
  320. }
  321. return @ret;
  322. }
  323. # parse the select declaration from a file into an array.
  324. # params:
  325. # $1 - file name
  326. # $2 - ref to result list (e.g. \@res)
  327. # $3 - boolean - true if filename is a translation-unit dump.
  328. # cmd. line global options used:
  329. # $src_fname - used only if $3 is true (see --src)
  330. # $gcc
  331. # $c_defs
  332. # $dbg
  333. #
  334. #
  335. sub process_file
  336. {
  337. my $file=shift;
  338. my $sel=shift; # ref to result array
  339. my $file_is_tu=shift;
  340. my $tmp_file;
  341. my $i;
  342. my $node;
  343. my $tu;
  344. my @res; # temporary hold the result here
  345. if (! $file_is_tu){
  346. # file is not a gcc translation-unit dump
  347. # => we have to create one
  348. $src_fname=basename($file);
  349. $tmp_file = "/tmp/" . mktemp ("dump_translation_unit_XXXXXX");
  350. # Note: gcc < 4.5 will produce the translation unit dump in a file in
  351. # the current directory. gcc 4.5 will write it in the same directory as
  352. # the output file.
  353. system("$gcc -fdump-translation-unit $c_defs -c $file -o $tmp_file") == 0
  354. or die "$gcc -fdump-translation-unit $c_defs -c $file -o $tmp_file" .
  355. " failed to generate a translation unit dump from $file";
  356. if (system("if [ -f \"$src_fname\".001t.tu ]; then \
  357. mv \"$src_fname\".001t.tu $tmp_file; \
  358. else mv /tmp/\"$src_fname\".001t.tu $tmp_file; fi ") != 0) {
  359. unlink($tmp_file, "$tmp_file.o");
  360. die "could not find the gcc translation unit dump file" .
  361. " ($src_fname.001t.tu) neither in the current directory" .
  362. " or /tmp";
  363. };
  364. $tu=GCC::TranslationUnit::Parser->parsefile($tmp_file);
  365. print(STDERR "src name $src_fname\n") if $dbg;
  366. unlink($tmp_file, "$tmp_file.o");
  367. }else{
  368. $tu=GCC::TranslationUnit::Parser->parsefile($file);
  369. }
  370. print(STDERR "Parsing file $file...\n") if $dbg;
  371. print(STDERR "Searching...\n") if $dbg;
  372. $i=0;
  373. # iterate on the entire nodes array (returned by gcc), but skipping node 0
  374. SEARCH: for $node (@{$tu}[1..$#{$tu}]) {
  375. $i++;
  376. while($node) {
  377. if (
  378. @res == 0 && # parse it only once
  379. $node->isa('GCC::Node::var_decl') &&
  380. $node->type->isa('GCC::Node::array_type') # &&
  381. #(! defined $src_fname || $src_fname eq "" ||
  382. # $node->source=~"$src_fname")
  383. ){
  384. # found a var decl. that it's an array
  385. # check if it's a valid array type
  386. next if (!( $node->type->can('elements') &&
  387. defined $node->type->elements &&
  388. $node->type->elements->can('name') &&
  389. defined $node->type->elements->name &&
  390. $node->type->elements->name->can('name') &&
  391. defined $node->type->elements->name->name)
  392. );
  393. my $type_name= $node->type->elements->name->name->identifier;
  394. if ($type_name eq $var_type) {
  395. if ($node->can('initial') && defined $node->initial) {
  396. my %c1=%{$node->initial};
  397. $sel_var_name=$node->name->identifier;
  398. if (defined $c1{val}){
  399. my $c1_el;
  400. die $patch_required if (ref($c1{val}) ne "ARRAY");
  401. # iterate on array elem., level 1( top {} )
  402. # each element is a constructor.
  403. for $c1_el (@{$c1{val}}) {
  404. # finally we are a the lower {} initializer:
  405. # { prev_f, type, name, new_f, flags }
  406. my %c2=%{$c1_el};
  407. my @el=@{$c2{val}};
  408. my ($prev_f_n, $type_n, $name_n, $new_f_n,
  409. $flags_n)=@el;
  410. my ($prev_f, $type, $name, $new_f, $flags);
  411. my $str;
  412. if ($prev_f_n->isa('GCC::Node::integer_cst') &&
  413. $new_f_n->isa('GCC::Node::integer_cst') &&
  414. $prev_f_n->low==0 && $new_f_n->low==0) {
  415. last SEARCH;
  416. }
  417. $prev_f=
  418. ($prev_f_n->isa('GCC::Node::integer_cst'))?
  419. $prev_f_n->low:
  420. expr_op0($prev_f_n)->name->identifier;
  421. $type=$type_n->low;
  422. $str=${${$name_n}{val}}[0];
  423. $name=($str->isa('GCC::Node::integer_cst'))?"":
  424. expr_op0($str)->string;
  425. $new_f=
  426. ($new_f_n->isa('GCC::Node::integer_cst'))?
  427. $new_f_n->low:
  428. expr_op0($new_f_n)->name->identifier;
  429. $flags= (defined $flags_n)?$flags_n->low:0;
  430. push @res, [$prev_f, $type, $name,
  431. $new_f, $flags];
  432. }
  433. }
  434. }
  435. }
  436. }
  437. } continue {
  438. if ($node->can('chain')){
  439. $node = $node->chain;
  440. }else{
  441. last;
  442. }
  443. }
  444. }
  445. push @$sel, @res;
  446. }
  447. # main:
  448. # read command line args
  449. if ($#ARGV < 0 || ! GetOptions( 'help|h|?' => \$opt_help,
  450. 'file|f=s' => \$file,
  451. 'core|c=s' => \$core_file,
  452. 'txt|t' => \$opt_txt,
  453. 'docbook|xml' => \$opt_docbook,
  454. 'tu|T' => \$opt_is_tu,
  455. 'source|src|s=s' => \$src_fname,
  456. 'defs|d=s'=>\$c_defs,
  457. 'group|grp|g=s'=>\$opt_grp_name,
  458. 'force-group|force-grp|G=s' =>
  459. \$opt_force_grp_name,
  460. 'dbg|debug|D'=>\$dbg,
  461. 'gcc=s' => \$gcc,
  462. 'patch' => \$opt_patch) ||
  463. defined $opt_help) {
  464. do { show_patch(); exit 0; } if (defined $opt_patch);
  465. select(STDERR) if ! defined $opt_help;
  466. help();
  467. exit((defined $opt_help)?0:1);
  468. }
  469. do { show_patch(); exit 0; } if (defined $opt_patch);
  470. do { select(STDERR); help(); exit 1 } if (!defined $file);
  471. if (defined $opt_txt){
  472. $output_format_header="HEADER";
  473. $output_format_footer="FOOTER";
  474. $output_format_selline="SELLINE";
  475. }elsif (defined $opt_docbook){
  476. $output_format_header="DOCBOOK_HEADER";
  477. $output_format_footer="DOCBOOK_FOOTER";
  478. $output_format_selline="DOCBOOK_SELLINE";
  479. }
  480. process_file($file, \@sel_exports, defined $opt_is_tu);
  481. process_file($core_file, \@core_exports, 0) if (defined $core_file);
  482. print(STDERR "Done.\n") if $dbg;
  483. my ($prev, $type, $name, $next, $flags, $desc);
  484. my $extra_txt;
  485. if (@sel_exports > 0){
  486. my $s;
  487. $i=0;
  488. # dump the configuration in txt mode
  489. if (defined $opt_force_grp_name) {
  490. $sel_grp_name=output_esc($opt_force_grp_name);
  491. }elsif (!defined $sel_grp_name && defined $opt_grp_name) {
  492. $sel_grp_name=output_esc($opt_grp_name);
  493. }
  494. print(STDERR "Generating select list...\n") if $dbg;
  495. my @sels = gen_selects "0";
  496. $~ = $output_format_header; write;
  497. $~ = $output_format_selline ;
  498. for $s (@sels){
  499. $extra_txt=output_esc("");
  500. $desc=output_esc("");
  501. $name=output_esc($s);
  502. $i++;
  503. #$extra_txt.=output_esc("Returns an array.") if ($flags & 1 );
  504. # generate txt description
  505. write;
  506. }
  507. $~ = $output_format_footer; write;
  508. }else{
  509. die "no selects found in $file\n";
  510. }
  511. sub valid_grp_name
  512. {
  513. my $name=shift;
  514. return defined $name && $name ne "";
  515. }
  516. format HEADER =
  517. Selects@*
  518. (valid_grp_name $sel_grp_name) ? " for " . $sel_grp_name : ""
  519. =======@*
  520. "=" x length((valid_grp_name $sel_grp_name)?" for " . $sel_grp_name : "")
  521. @||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
  522. "[ this file is autogenerated, do not edit ]"
  523. .
  524. format FOOTER =
  525. .
  526. format SELLINE =
  527. @>>. @*
  528. $i, $name
  529. ~~ ^<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
  530. $desc
  531. ~~ ^<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
  532. $extra_txt
  533. .
  534. format DOCBOOK_HEADER =
  535. <?xml version="1.0" encoding="UTF-8"?>
  536. <!-- this file is autogenerated, do not edit! -->
  537. <!DOCTYPE section PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
  538. "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
  539. <chapter id="select_list@*">
  540. (valid_grp_name $sel_grp_name) ? "." . $sel_grp_name : ""
  541. <title>Selects@*</title>
  542. (valid_grp_name $sel_grp_name) ? " for " . $sel_grp_name : ""
  543. <orderedlist>
  544. .
  545. format DOCBOOK_FOOTER =
  546. </orderedlist>
  547. </chapter>
  548. .
  549. format DOCBOOK_SELLINE =
  550. <listitem><simpara>@*</simpara>
  551. $name
  552. ~~<para>^<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< </para>
  553. $desc
  554. ~~<para>^<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< </para>
  555. $extra_txt
  556. </listitem>
  557. .