# $Id$ $VERSION{''.__FILE__} = '$Revision$'; # # >>Title:: SDF Filters Library # # >>Copyright:: # Copyright (c) 1992-1999, Ian Clatworthy (ianc@mincom.com). # You may distribute under the terms specified in the LICENSE file. # # >>History:: # ----------------------------------------------------------------------- # Date Who Change # 23-Oct-98 ianc Add datestrings filter # 29-Jul-97 peterh Add 'noslide' parameter to topics filter. # 29-Feb-96 ianc SDF 2.000 # ----------------------------------------------------------------------- # # >>Purpose:: # This library provides the built-in filters for # [[SDF]] files. # # >>Description:: # A filter called {{xxx}} maps to: # # * a parameter table called {{_xxx_FilterParams}} # * a subroutine called {{xxx_Filter}}. # # The routine interface is: # # = &xxx_Filter(*text, %param); # # :where: # # * {{@text}} is the text (original in and result out) # * {{%param}} is the parameters. # # If the text is a table, the filter also has a data model # (i.e. validation table) called {{xxx_FilterModel}}. # For mapping tables, the convention is to name # each associative array xxx_yyy where: # # * {{xxx}} is the filter name # * {{yyy}} is the column name (all in lowercase). # # >>Limitations:: # The 'proxy' idea is only supported for phrase styles at the moment. # Should this be generalised to other configurable entities? # # The configuration filters should be rationalised to use one or two # common support routines. # # >>Resources:: # # >>Implementation:: # # Switch to the user package package SDF_USER; ##### Constants ##### ## ## >>Description:: ## {{Y:TBL_FORMAT}} is the lookup table for default table formats. ## The key is the number of columns. ## #%TBL_FORMAT = ( # 1, 9, # 2, 37, # 3, 334, # 4, 3331, # 5, 22222, #); # # >>Description:: # {{Y:DEFAULT_LANG_STYLE}} is the default lookup table for styles used to # format different parts of a language. The key is the state: # # * c - comment # * a - additional comment # * s - string # * l - literal # * kw - keywords. # # By default, comments are placed in italics, keywords in bold and # strings/literals are fixed-width. # # Note: The style must be a single letter as [A-Z]<> style phrases are used. # %DEFAULT_LANG_STYLE = ( 'c', 'I', 'a', 'I', 's', 'C', 'l', 'C', 'kw', 'B', ); # These are the lookup tables for alignment characters %_ALIGN_NAMES = ( 'L', 'Left', 'C', 'Center', 'R', 'Right', ); %_VALIGN_NAMES = ( 'T', 'Top', 'M', 'Middle', 'B', 'Bottom', 'L', 'Baseline', ); # These are the built-in table aliases %_TABLE_ALIASES = ( 'Columns', 'columns', 'Rows', 'rows', 'Grid', 'grid', 'Plain', 'plain', 'Shade', 'shade', ); ##### Variables ##### # flag for table validation $validate = 0; # tables of topic info and the jumps for a section @topics = (); @levels = (); %topic_label = (); %topic_level = (); %topic_prev = (); %topic_next = (); %topic_up = (); %jump = (); %jump_label = (); # state of formatting attributes for the sdf filter %sdf_attrs = (); @sdf_attr_stk = (); # lookup table for language styles & definitions %languages = (); %lang_aliases = (); %lang_style = %DEFAULT_LANG_STYLE; %lang_keywords = (); %lang_tokens = (); # mapping tables for document styles/variables %docstyles_name = (); %docstyles_to = (); %docstyles_vars = (); %variables_name = (); %variables_type = (); %variables_rule = (); # mapping tables for paragraph styles/attributes %parastyles_name = (); %parastyles_category = (); %parastyles_to = (); %parastyles_attrs = (); %paraattrs_name = (); %paraattrs_type = (); %paraattrs_rule = (); %paraattrs_to = (); %paraattrs_map = (); %paraattrs_attrs = (); # mapping tables for phrase styles/attributes %phrasestyles_name = (); %phrasestyles_category = (); %phrasestyles_to = (); %phrasestyles_attrs = (); %phraseattrs_name = (); %phraseattrs_type = (); %phraseattrs_rule = (); %phraseattrs_to = (); %phraseattrs_map = (); %phraseattrs_attrs = (); # mapping tables for table styles/parameters %tablestyles_name = (); %tablestyles_to = (); %tablestyles_params = (); %tableparams_name = (); %tableparams_type = (); %tableparams_rule = (); %tableparams_to = (); %tableparams_map = (); %tableparams_params = (); # mapping tables for row parameters %rowparams_name = (); %rowparams_type = (); %rowparams_rule = (); %rowparams_to = (); %rowparams_map = (); %rowparams_params = (); # mapping tables for cell parameters %cellparams_name = (); %cellparams_type = (); %cellparams_rule = (); %cellparams_to = (); %cellparams_map = (); %cellparams_params = (); ##### Initialisation ##### # # >>Description:: # {{Y:InitFilters}} initialises the global variables in this module. # sub InitFilters { @topics = (); @levels = (); %topic_label = (); %topic_level = (); %topic_prev = (); %topic_next = (); %topic_up = (); %jump = (); %jump_label = (); $validate = $::verbose; %sdf_attrs = (); @sdf_attr_stk = (); %languages = (); %lang_aliases = (); %lang_style = %DEFAULT_LANG_STYLE; %docstyles_name = (); %docstyles_to = (); %docstyles_vars = (); %variables_name = (); %variables_type = (); %variables_rule = (); %parastyles_name = (); %parastyles_to = (); %parastyles_attrs = (); %parastyles_category = (); %paraattrs_name = (); %paraattrs_type = (); %paraattrs_rule = (); %paraattrs_to = (); %paraattrs_map = (); %paraattrs_attrs = (); %phrasestyles_name = (); %phrasestyles_category = (); %phrasestyles_to = (); %phrasestyles_attrs = (); %phraseattrs_name = (); %phraseattrs_type = (); %phraseattrs_rule = (); %phraseattrs_to = (); %phraseattrs_map = (); %phraseattrs_attrs = (); %tablestyles_name = (); %tablestyles_to = (); %tablestyles_params = (); %tableparams_name = (); %tableparams_type = (); %tableparams_rule = (); %tableparams_to = (); %tableparams_map = (); %tableparams_params = (); %rowparams_name = (); %rowparams_type = (); %rowparams_rule = (); %rowparams_to = (); %rowparams_map = (); %rowparams_params = (); %cellparams_name = (); %cellparams_type = (); %cellparams_rule = (); %cellparams_to = (); %cellparams_map = (); %cellparams_params = (); } ##### Support Routines ##### # # >>_Description:: # {{Y:_FilterValidate}} validates the data for a filter which # expects a table. # sub _FilterValidate { local(*data, *model) = @_; # local(); local(@parsed_model); # It would be better not to parse this each time, but # as it only happens during validate mode, its not worth # caching the results just yet @parsed_model = &::TableParse(@model); &::TableValidate(*data, *parsed_model); } # # >>_Description:: # {{Y:_SkipHeader}} skips the top comment in a block of text where # the commenting conventions are derived from the language ({{lang}}). # The default language is {{sdf}}. The top comment must be the first # thing on the first line of the file (except for {{sdf}} files where # it may start on the second line if the first line is a {{!init}} line). # sub _SkipHeader { local(*text, $lang) = @_; # local(); # Get the comment conventions for the nominated language $lang = 'sdf' if $lang eq ''; &_SetLang($lang); my $start1 = $lang_tokens{'cb'}; my $end1 = $lang_tokens{'ce'}; my $start2 = $lang_tokens{'ab'}; my $end2 = $lang_tokens{'ae'}; # For SDF files, ignore the !init line for now, if any my $init_line = shift(@text) if $text[0] =~ /^\!init/; # Remove the line-based comment, if any if ($end1 eq "\n") { while ($text[0] =~ /^$start1/) { shift(@text); } } elsif ($end2 eq "\n") { while ($text[0] =~ /^$start2/) { shift(@text); } } # Remove the stream-based comment, if any elsif ($text[0] =~ /^$start1/) { while (@text && $text[0] !~ /$end1/) { shift(@text); } shift(@text) if @text; } elsif ($text[0] =~ /^$start2/) { while (@text && $text[0] !~ /$end2/) { shift(@text); } shift(@text) if @text; } # Restore the !init line, if any unshift(@text, $init_line) if $init_line; } # # >>_Description:: # {{Y:_Pure}} escapes special symbols within example text. # Note that it isn't necessary to escape leading characters or # patterns as the line will be explicitly tagged later. # sub _Pure { local(*text) = @_; # local(); local($line); # Escape phrase patterns for $line (@text) { $line =~ s/[<>]/$& eq '>' ? 'E' : 'E'/eg; if ($line =~ s/\[\[/E<2[>/g) { $line =~ s/\]\]/E<2]>/g; } if ($line =~ s/\{\{/E<2{>/g) { $line =~ s/\}\}/E<2}>/g; } } } # # >>_Description:: # {{Y:_SetLang}} sets the current language to be {{lang}}, i.e. it # resets the {{lang_token}} and {{lang_keyword}} lookup tables. # (Thanks to Prachin Ranavat for the initial code.) # sub _SetLang { local($lang) = @_; # local(); local($x, $name, $value); # Get the language tokens %lang_tokens = (); foreach $x (split(':',$languages{$lang_aliases{$lang}})) { ($name, $value) = split('=', $x, 2); # Covert vgrind definition metasymbols to perl metasymbols $value =~ s/\\d/\\b/g; $value =~ s/\\a/.*/g; $value =~ s/\\p/\\w+/g; $value =~ s/\\e/[^\\\\]?/g; $value =~ s/\*/\\*/g; $value = "\n" if $value eq '$'; $lang_tokens{$name} = $value; } # Get the keywords %lang_keywords = (); foreach $x (split(/\s+/, $lang_tokens{'kw'})) { $lang_keywords{$x} = 1; } } # # >>_Description:: # {{Y:_FmtLang}} formats {{@text}} for the specified language. # (Thanks to Prachin Ranavat for the initial code.) # sub _FmtLang { local(*text, $lang) = @_; # local(); local($line); local($state); local($prefix); # Set the current language &_SetLang($lang); # Process each line of code that needs to be pretty formatted. # As each line is a separate paragraph and comments, etc. can # be multi-line (but SDF phrases cannot cross paragraph boundaries), # we need to end/restart phrases accordingly. $state = ''; for $line (@text) { $prefix = $state ? "$lang_style{$state}<" : ''; $line =~ s/([^ \r\t\f\(\)\[\]\,\;\=\+\:]+)/&_WordFmt($1, *state)/eg; if ($state) { $line .= '>'; $state = '' if $lang_tokens{$state.'e'} eq "\n"; } $line =~ s/(\S)/$prefix$1/ if $prefix; } } # # >>_Description:: # {{Y:_WordFmt}} formats a word of a programming language. # This routine is used by {{Y:_FmtLang}}. # (Thanks to Prachin Ranavat for the initial code.) # sub _WordFmt { local($word, *state) = @_; local($result); local($start, $token); local($keyword); # If we are already in a state, look for the ending token if ($state) { if (($word =~ /$lang_tokens{$state.'e'}$/)) { $word .= '>'; $state = ''; } return $word; } # Check for a single word within a state return "$lang_style{'c'}<$word>" if defined $lang_tokens{'cb'} && $word =~ /^$lang_tokens{'cb'}.*$lang_tokens{'ce'}$/; return "$lang_style{'a'}<$word>" if defined $lang_tokens{'ab'} && $word =~ /^$lang_tokens{'ab'}.*$lang_tokens{'ae'}$/; return "$lang_style{'s'}<$word>" if defined $lang_tokens{'sb'} && $word =~ /^$lang_tokens{'sb'}.*$lang_tokens{'se'}$/; return "$lang_style{'l'}<$word>" if defined $lang_tokens{'lb'} && $word =~ /^$lang_tokens{'lb'}.*$lang_tokens{'le'}$/; # Look for a starting token for $start ('c', 'a', 's', 'l') { $token = $lang_tokens{$start.'b'}; if (defined $token && $word =~ /^$token/) { $state = $start; return "$lang_style{$start}<$word"; } } # Convert the word to a keyword for matching purposes $keyword = $word; $keyword =~ tr/A-Z/a-z/ if $lang_tokens{'oc'}; # Check for a keyword, potentially at the end of a line return "$lang_style{'kw'}<$word>\n" if $word =~ s/\n$// && $lang_keywords{$word}; return "$lang_style{'kw'}<$word>" if $lang_keywords{$word}; # If we reach here, we found nothing special return $word; } # # >>_Description:: # {{Y:_DefineLang}} stores a vgrind-like language definition. # (Thanks to Prachin Ranavat for the initial code.) # sub _DefineLang { local(*lang_def) = @_; # local(); local($i, $_, $virtual_line); local($langs, $rest, $main, @aliases); # Append lines to create a virtual line $virtual_line = ''; for ($i = 0; $i <= $#lang_def; $i++) { $_ = $lang_def[$i]; chop; $_ =~ s/\s*\\$//; #$_ =~ s/^\s+://; $_ =~ s/^\s+/ /; $virtual_line .= $_; } @lang_def = (); # Store the definition ($langs, $rest) = split(':', $virtual_line, 2); ($main, @aliases) = split('\|', $langs); $languages{$main} = $rest; for $_ ($main, @aliases) { $lang_aliases{$_} = $main; } } ##### Topic Filters ##### # sdf - normal sdf @_sdf_FilterParams = ('ANY'); sub sdf_Filter { local(*text, %param) = @_; # leave the text as it is, unless attributes are specified if (%param) { # Using a global variable like this seems like a hack but # it does this job nicely, provided with save and restore its value push(@sdf_attrs_stk, join("\000", %sdf_attrs)); %sdf_attrs = %param; unshift(@text, "!on paragraph ''; __sdf; &DefaultAttrs(%sdf_attrs)"); push (@text, '!off paragraph __sdf', '!script %sdf_attrs = split("\000", pop(@sdf_attrs_stk))'); } } # front - front section of (and configuration file for) a manual @_front_FilterParams = (); sub front_Filter { local(*text, %param) = @_; # leave the text as it is } # about - about section of a manual @_about_FilterParams = (); sub about_Filter { local(*text, %param) = @_; # For html, leave things as they are #return if $var{'OPT_DRIVER'} eq 'html'; # Use plain headings & exclude them from the table of contents, if necessary unshift(@text, '!on paragraph \'H\d\'; __about;' . '$style =~ s/H(\d)/"P" . ($1 + 1)/e;' . 'if ($1 == 1) { $attr{"component"} = "prechapter" }' . 'else { $attr{"notoc"} = 1 }'); push (@text, '!off paragraph __about'); } # appendix - change headings to appendix style @_appendix_FilterParams = (); sub appendix_Filter { local(*text, %param) = @_; # change headings unshift(@text, '!on paragraph \'H\d\'; __appendix; $style =~ s/H/A/'); push (@text, '!off paragraph __appendix'); } # plain - change headings to plain style @_plain_FilterParams = (); sub plain_Filter { local(*text, %param) = @_; # change headings unshift(@text, '!on paragraph \'H\d\'; __plain; $style =~ s/H/P/'); push (@text, '!off paragraph __plain'); } ##### General Filters ##### # verbatim - fixed width verbatim text @_verbatim_FilterParams = ( 'Name Type Rule', 'skipheader boolean', 'lang string', 'wide boolean', 'listitem integer', ); sub verbatim_Filter { local(*text, %param) = @_; local($style, $attrs, $line); # Preprocess the text, if necessary my $lang = $param{'lang'}; &_SkipHeader(*text, $lang) if $param{'skipheader'}; # Prefix each line with the appropriate example style $style = "V"; if ($param{'wide'} && $param{'listitem'}) { $attrs = "[wide;in=$param{'listitem'}]"; } elsif ($param{'wide'}) { $attrs = '[wide]'; } else { $attrs = $param{'listitem'} ? "[in=$param{'listitem'}]" : ':'; } for $line (@text) { $line = "$style$attrs$line"; } } # example - fixed width text @_example_FilterParams = ( 'Name Type Rule', 'skipheader boolean', 'lang string', 'wide boolean', 'listitem integer', 'pure boolean', ); sub example_Filter { local(*text, %param) = @_; local($style, $attrs, $line); # Preprocess the text, if necessary. Note that pretty printing # implies pure processing, so that special characters within the # source code are protected. my $lang = $param{'lang'}; $lang = "\L$lang"; &_SkipHeader(*text, $lang) if $param{'skipheader'}; &_Pure(*text) if $param{'pure'} || $lang ne ''; &_FmtLang(*text, $lang) if $lang ne ''; # Prefix each line with the appropriate example style $style = $param{'wide'} ? 'E80' : 'E'; $attrs = $param{'listitem'} ? "[in=$param{'listitem'}]" : ':'; for $line (@text) { $line = $line =~ /^\f/ ? "PB:$'" : "$style$attrs$line"; } } # note - a notice @_note_FilterParams = ( 'Name Type Rule', 'label string', ); sub note_Filter { local(*text, %param) = @_; # surround the note with the right stuff unshift(@text, $param{'label'} ? "NB[label='$param{'label'}']" : "NB:"); push (@text, 'NE:'); } # table - table in [[TBL]] format # Parameters are given below. # # !block table # Parameter Description # style overall look of the table # format column widths # title table caption # headings headings (if not the first row in the text) # tags colon separated list of phrase styles to apply to columns # niceheadings set to 0 to disable _ to space conversion # noheadings suppress headings in output # groups pattern of group-style rows (default is /:$/) # type same as style (provided for backwards compatibility) # wide table straddles the side head area of the page # bgcolor background colour for a table (HTML only) # cellpadding padding size for table cells (HTML only) # cellspacing spacing size between table cells (HTML only) # !endblock # @_table_FilterParams = ( 'Name Type Rule', 'align string', 'bgcolor string', 'bmargin integer', 'cellpadding integer', 'cellspacing integer', 'colaligns string', 'coltags string', 'colvaligns string', 'delete string', 'footings integer', 'format string', 'groups string', 'headings integer', 'keepindents boolean', 'landscape string', 'listitem integer', 'lmargin integer', 'narrow boolean', 'niceheadings boolean', 'nocalcs boolean', 'noheadings boolean', 'objects string', 'oncell string', 'parseline string', 'placement string', 'rmargin integer', 'select string', 'sort string', 'style string', 'tags string', 'title string', 'tmargin integer', 'type string', 'where string', 'wide boolean', 'wrap integer', ); sub table_Filter { local(*text, %param) = @_; local($style, @tbl); local($grp_search, @body_tags, $noheadings, $nocalcs, $nice_hdgs, $on_cell); local($tbl_header, @flds); local(@sort_by); local(@delete, @select); local(@col_aligns, $align); local(@col_valigns, $valign); local($bgcolor); local($body_start, $footing_count, $footing_start, %default_heading); local(@headings, @footings); local($row_type, $row_text); local($format, $tbl_cols, $fmt_cols); local(@sdf, %values, $tag, $cell, %cell); local($fld, $width, $height, $row, $col, $last_row, $last_col); local(%cover, $hidden, $rowspan, $colspan, $i, $j); local($wrap, $wrap_counter); local($actual_cols); local($keepindents); # Get the table style (type is supported for backwards compatibility) $style = $param{'style'} ne '' ? $param{'style'} : $param{'type'}; delete $param{'type'}; $style = $_TABLE_ALIASES{$style} if $_TABLE_ALIASES{$style}; $style = &Var('DEFAULT_TABLE_STYLE') if $style eq ''; $param{'style'} = $style; # Activate event processing &ReportEvents('table') if @::sdf_report_names; &ExecEventsStyleMask(*evcode_table, *evmask_table); &ReportEvents('table', 'Post') if @::sdf_report_names; # Parse the text into a table if (defined $param{'parseline'}) { unshift(@text, $param{'parseline'}); delete $param{'parseline'}; } @tbl = &::TableParse(@text); # Filter and sort the table, if requested if ($param{'where'} ne '') { @tbl = &::TableFilter(*tbl, $param{'where'}, *var); delete $param{'where'}; } if ($param{'sort'} ne '') { @sort_by = $param{'sort'} == 1 ? () : split(/,/, $param{'sort'}); @tbl = &::TableSort(*tbl, @sort_by); delete $param{'sort'}; } if ($param{'delete'} ne '') { @delete = split(/,/, $param{'delete'}); @tbl = &::TableDeleteFields(*tbl, @delete); delete $param{'delete'}; } if ($param{'select'} ne '') { @select = split(/,/, $param{'select'}); @tbl = &::TableSelectFields(*tbl, @select); delete $param{'select'}; } # The groups attribute is the pattern to match for groups if ($param{'groups'} ne '') { $grp_search = $param{'groups'}; $grp_search = ':$' if $grp_search == 1; delete $param{'groups'}; } # Get the phrase styles for the columns, if any # (tags and objects are supported for backwards compatibility) @body_tags = split(",", $param{'coltags'} || $param{'tags'} || $param{'objects'}); delete $param{'coltags'}; delete $param{'tags'}; delete $param{'objects'}; # Get the alignments for the columns if ($param{'colaligns'} =~ /^[LCR]+$/) { @col_aligns = (); for $align (split(//, $param{'colaligns'})) { push(@col_aligns, $_ALIGN_NAMES{$align}); } $param{'colaligns'} = join(',', @col_aligns); } else { @col_aligns = split(/,/, $param{'colaligns'}); } # Get the vertical alignments for the columns if ($param{'colvaligns'} =~ /^[TMBL]+$/) { @col_valigns = (); for $valign (split(//, $param{'colvaligns'})) { push(@col_valigns, $_VALIGN_NAMES{$valign}); } } else { @col_valigns = split(/,/, $param{'colvaligns'}); } delete $param{'colvaligns'}; # Get the noheadings flag $noheadings = $param{'noheadings'}; delete $param{'noheadings'}; # Get the nocalcs flag $nocalcs = $param{'nocalcs'}; delete $param{'nocalcs'}; # Get the niceheadings flag $nice_hdgs = defined($param{'niceheadings'}) ? $param{'niceheadings'} : 1; delete $param{'niceheadings'}; # Get the keepindents flag $keepindents = $param{'keepindents'}; delete $param{'keepindents'}; # Get the background colour $bgcolor = $param{'bgcolor'}; delete $param{'bgcolor'}; # Get the oncell processing code $oncell = $param{'oncell'}; delete $param{'oncell'}; # Get the number of logical rows displayed on a physical row if (defined $param{'wrap'}) { $wrap = $param{'wrap'}; $wrap_counter = $wrap; delete $param{'wrap'}; } else { $wrap = 0; } # Check the column count in the format is ok # (Note: TableFieldsCheck checks for duplicate columns) $tbl_header = shift @tbl; @flds = &::TableFieldsCheck($tbl_header); $tbl_cols = scalar(@flds); # Calculate the row number of the first body row if (defined $param{'headings'}) { $body_start = $param{'headings'}; delete $param{'headings'}; } elsif ($noheadings) { $body_start = 0; } else { # Build the default heading: # * get a copy of the data so we don't corrupt the fields array # * niceheadings => a single _ becomes a space; multiple become 1 less %default_heading = (); for $fld (@flds) { $cell = $fld; $cell =~ s/_(_*)/$& eq '_' ? ' ' : $1/eg if $nice_hdgs; $default_heading{$fld} = $cell; } unshift(@tbl, &::TableRecJoin(*flds, %default_heading)); $body_start = 1; } # If there are multiple records per rows and heading rows, we # duplicate the heading rows as one would expect if ($wrap && $body_start) { @headings = (); for $rec (@tbl[0 .. $body_start - 1]) { for $i (1 .. $wrap) { push(@headings, $rec); } } splice(@tbl, 0, $body_start, @headings); $body_start = scalar(@headings); } # Calculate the row number of the first footing row, # taking care to ignore macro rows $footing_start = scalar(@tbl); if (defined $param{'footings'}) { $footing_count = $param{'footings'}; delete $param{'footings'}; if ($footing_count >= 1) { $i = $footing_count; for ($row = $#tbl; $row > 0; $row--) { next if $tbl[$row] =~ /^!/; $i--; last if $i == 0; } } $footing_start = $row; } # If there are multiple records per rows and footing rows, we # duplicate the footing rows as one would expect if ($wrap && $footing_count) { @footings = (); for $rec (@tbl[$footing_start .. $#tbl]) { for $i (1 .. $wrap) { push(@footings, $rec); } } splice(@tbl, $footing_start, $footing_count, @footings); } # Initialise the variables exported for oncell processing $last_col = $tbl_cols - 1; $last_row = scalar(@tbl) - 1; $row = 0; $col = 0; # Build the output %cover = (); $actual_cols = $tbl_cols; $actual_cols *= $wrap if $wrap; @sdf = ("!table $actual_cols;" . &::SdfAttrJoin(*param)); for $rec (@tbl) { if ($rec =~ /^!/) { push(@sdf, $rec); next; } %values = &::TableRecSplit(*flds, $rec); cell: for ($col = 0; $col <= $#flds; $col++) { $fld = $flds[$col]; $hidden = $cover{$row,$col}; $cell = $values{$fld}; # Decide what type of row this is if ($col == 0) { if ($row < $body_start) { $row_type = 'Heading'; } elsif ($row == $footing_start) { $wrap_counter = $wrap; $row_type = 'Footing'; } elsif ($row > $footing_start) { $row_type = 'Footing'; } elsif ($grp_search ne '' && $cell =~ /$grp_search/) { $row_type = 'Group'; # group rows reset row wrapping, if any $wrap_counter = $wrap; } else { $wrap_counter = $wrap if $row_type eq 'Group'; $row_type = ''; } $row_text = $row_type ? "!row '$row_type'" : "!row"; if ($wrap) { if ($wrap_counter++ >= $wrap) { $wrap_counter = 1; } else { $row_text = ''; } } push(@sdf, $row_text) if $row_text; } # If this cell is covered by the span of another, # check that it is blank if ($hidden && $cell ne '') { &::AppMsg("warning", "ignoring hidden cell ($row,$col)='$cell'"); } # Get the custom cell attributes, if any. # Multi-line cells are implicitly 'sdf' already, # although the top line may contain other cell attributes %cell = (); if (substr($cell, 0, 1) eq "\n") { ($cell, %cell) = &::SdfParseCell(substr($cell, 1)); $cell{'sdf'} = 1; } else { ($cell, %cell) = &::SdfParseCell($cell); } # Enable spreadsheet-like access/calculations &calc_table() unless $nocalcs; # Handle hidden cells if ($hidden) { push(@sdf, "!cell hidden"); next cell; } # Add the default cell attributes unless (defined($cell{'align'})) { $cell{'align'} = $col_aligns[$col] if $col_aligns[$col]; } unless (defined($cell{'valign'})) { $cell{'valign'} = $col_valigns[$col] if $col_valigns[$col]; } unless (defined($cell{'tag'})) { $cell{'tag'} = $row_type eq '' ? $body_tags[$col] : 2; } unless (defined($cell{'bgcolor'})) { $cell{'bgcolor'} = $bgcolor if $bgcolor ne ''; } $cell{'cols'} = 1 unless defined($cell{'cols'}); $cell{'rows'} = 1 unless defined($cell{'rows'}); # Activate 'oncell' processing if ($oncell) { eval $oncell; if ($@) { &::AppMsg("error", "oncell processing failed: $@"); } } # Cover the cells hidden by this one $rowspan = $cell{'rows'}; $colspan = $cell{'cols'}; for ($i = 0; $i < $rowspan; $i++) { for ($j = 0; $j < $colspan; $j++) { $cover{$row+$i,$col+$j} = 1; } } # Convert the cell contents to SDF, if necessary if ($cell{"sdf"}) { delete $cell{"sdf"}; } else { # Replace leading whitespace with # non-breaking spaces if requested if ($keepindents) { $cell =~ s/^([ ]+)/'E' x length($1)/eg; #print STDERR "cell is $cell<\n"; } if ($cell{'tag'} ne '') { $cell = "{{" . $cell{'tag'} . ":$cell}}"; } $cell = ":$cell"; } # Add the cell delete $cell{'tag'}; push(@sdf, "!cell " . &::SdfAttrJoin(*cell), split("\n", $cell)); } # We only increment the row number for non-macro rows $row++; } push(@sdf, "!endtable"); # Update the buffer @text = scalar(@sdf) > 2 ? @sdf : (); } # ascii_graphic - format a text diagram sub ascii_graphic_Filter { local(*text, %param) = @_; @text = grep(s/^/E80:/, @text); } # abstract - format a paper abstract @_abstract_FilterParams = (); sub abstract_Filter { local(*text, %param) = @_; # indent margins (18 pts = 1/4 inch) and emphasise the text unshift(@text, '!on paragraph \'\'; __abstract; ' . '@attr{"left", "right", "first", "obj"} = (18, 18, 18, 1)'); push (@text, '!off paragraph __abstract'); # For HTML and HLP, add a heading if ($var{'OPT_TARGET'} eq 'html' || $var{'OPT_TARGET'} eq 'hlp') { unshift(@text, 'P1: Abstract'); } } # quote - format a quote sub quote_Filter { local(*text, %param) = @_; if ($var{'OPT_DRIVER'} eq 'html') { # emphasise the text unshift(@text, '!on paragraph \'\'; __quote; $attr{"obj"} = 1'); } else { # indent margins (18 pts = 1/4 inch) and emphasise the text unshift(@text, '!on paragraph \'\'; __quote; ' . '@attr{"left", "right", "first", "obj"} = (18, 18, 18, 1)'); } push(@text, '!off paragraph __quote'); } # address - format an address sub address_Filter { local(*text, %param) = @_; # local(); local($line); for $line (@text) { $line = "Addr:$line"; } } # nofill - format a set of lines sub nofill_Filter { local(*text, %param) = @_; # local(); local($para); $para = join('{{CHAR:nl}}', @text); @text = ($para); } # sections - convert text of paragraphs to sections (for HTML and help) @_sections_FilterParams = (); sub sections_Filter { local(*text, %param) = @_; if ($var{'OPT_TARGET'} eq 'html' || $var{'OPT_TARGET'} eq 'hlp') { unshift(@text, '!on paragraph \'\'; __sections; $attr{"obj"} = "SECT"'); #unshift(@text, '!on paragraph \'\'; __sections; $attr{"obj"} = "SECT";'. # '$attr{"text"} = $text; $text =~ s/\\.$//'); push (@text, '!off paragraph __sections'); } } # topics - include topics @_topics_FilterParams = ( 'Name Type Rule', 'intro string', 'noslide boolean', 'data boolean', ); @_topics_FilterModel = ( 'Field Category Rule', 'Topic mandatory', 'Label optional', 'Level optional', 'Next optional', 'Prev optional', 'Up optional', ); sub topics_Filter { local(*text, %param) = @_; local(@tbl, @flds, $rec, %values); local($topic); local($label); local($file); # Parse and validate the data @tbl = &::TableParse(@text); @text = (); &_FilterValidate(*tbl, *_topics_FilterModel) if $validate; # Get the list of fields @flds = &::TableFields(shift @tbl); # If this is topics data, store it if ($param{'data'}) { for $rec (@tbl) { %values = &::TableRecSplit(*flds, $rec); $file = $values{'Topic'}; $topic_label{$file} = $values{'Label'}; $topic_level{$file} = $values{'Level'}; $topic_next{$file} = $values{'Next'}; $topic_prev{$file} = $values{'Prev'}; $topic_up{$file} = $values{'Up'}; } return; } # When we're building the sub-topics, # display the labels as jumps to the sub-topics if ($var{'HTML_SUBTOPICS_MODE'}) { push(@text, $param{'intro'}) if $param{'intro'} ne ''; for $rec (@tbl) { %values = &::TableRecSplit(*flds, $rec); $topic = $values{'Topic'}; $file = $topic; # this next line is needed once topics mode supports files # outside the current directory #$file = &::NameAbsolute(&FindFile("$topic.sdf")); $label = $values{'Label'} ? $values{'Label'} : $topic_label{$file}; $label = $topic if $label eq ''; #push(@text, "L1[jump='$topic.html'] $label"); # Build the topic file push(@text, "!include '$topic.sdf'"); } } # Otherwise, include the topics as subsections else { for $rec (@tbl) { %values = &::TableRecSplit(*flds, $rec); $topic = $values{'Topic'}; push(@text, "!include '$topic.sdf'"); } } unless ($param{'noslide'}) { unshift(@text, "!slide_down"); push(@text, "!slide_up"); } } # jumps - include jumps data @_jumps_FilterParams = (); @_jumps_FilterModel = ( 'Field Category Rule', 'Jump key', 'Physical mandatory', ); sub jumps_Filter { local(*text, %param) = @_; local(@tbl, @flds, $rec, %values); local($jump); # Parse and validate the data @tbl = &::TableParse(@text); @text = (); &_FilterValidate(*tbl, *_jumps_FilterModel) if $validate; # Get the list of fields @flds = &::TableFields(shift @tbl); # Store the data for $rec (@tbl) { %values = &::TableRecSplit(*flds, $rec); $jump = $values{'Jump'}; $jump{$jump} = $values{'Physical'}; } } # title - title block for memos, faxes, etc. @_title_FilterParams = ( 'Name Type Rule', 'type string', 'toc integer', 'format string', ); sub title_Filter { local(*text, %param) = @_; # local(); local($format); local($params); local($target); local($title); local($logo); # Turn the data into a name-values block $format = defined($param{'format'}) ? $param{'format'} : '28'; $params = "format=$format"; @text = ("!block namevalues; $params", @text, '!endblock'); # Prepend a title box, if necessary $target = $var{'OPT_TARGET'}; $title = $param{'type'} || $var{'DOC_TYPE'}; if ($title ne '') { if ($target eq 'html') { unshift(@text, 'P2[notoc]' . $title); } else { $title = '{{B[family="Helvetica";size=14]' . $title . '}}'; unshift(@text, '!block box; narrow; wide', $title, '!endblock', 'E:'); } } # Prepend a logo, if necessary $logo = $var{'DOC_ADMIN_LOGO'} || $var{'DOC_LOGO'}; $base = $var{'DOC_ADMIN_LOGO_BASE'} || $var{'DOC_LOGO_BASE'}; if ($logo ne '') { unshift(@text, "!import '$logo'; wide; wrap_text; align=Right; base='$base'"); } # Add a table of contents, if requested if ($param{'toc'}) { push(@text, "!default DOC_TOC $param{'toc'}"); if ($target eq 'html') { push(@text, "!block inline; target='html'", "", "", "!endblock"); } } # Output a line after the title block push(@text, "Line[wide]"); } # box - put a box around text @_box_FilterParams = ( 'Name Type Rule', 'align string', 'listitem integer', 'narrow boolean', 'wide boolean', 'lines boolean', 'bgcolor string', 'fill integer', ); sub box_Filter { local(*text, %param) = @_; # local(); local($line); local($params); # Set up the default parameters for formatting the table $params = 'noheadings; nocalcs; style="columns"; tmargin=6'; # If requested, turn the lines into a paragraph if ($param{'lines'}) { for ($line = 0; $line < $#text; $line++) { $text[$line] .= "[[nl]]"; } delete $param{'lines'}; $params .= '; colaligns="C"'; } # Turn the data into a single cell table if ($param{'fill'}) { unshift(@text, '[fill=' . $param{'fill'} . ']'); delete $param{'fill'}; } if (%param) { $params .= '; ' . &::SdfAttrJoin(*param); } @text = ("!block table; $params", 'Dummy', '<<', @text, '>>', '!endblock'); } # namevalues - table of names & values @_namevalues_FilterParams = ( 'Name Type Rule', 'format string', ); @_namevalues_FilterModel = ( 'Field Category Rule', 'Name optional', 'Value optional', #'Name2 optional', #'Value2 optional', #'Name3 optional', #'Value3 optional', ); sub namevalues_Filter { local(*text, %param) = @_; local(@tbl, @flds, $rec, %values); local($target); local($format); local($width); # Parse and validate the data @tbl = &::TableParse(@text); &_FilterValidate(*tbl, *_namevalues_FilterModel) if $validate; # Decide on the format if ($param{'format'}) { $format = $param{'format'}; } else { # The first column must be at least 90 points in size and # is sized so that the second column is aligned with either # normal paragraphs or progressively indented list paragraphs $width = $var{'OPT_SIDEHEAD_WIDTH'} + $var{'OPT_SIDEHEAD_GAP'} - 2; if ($var{'OPT_LIST_INDENT'} > 0) { $width += $var{'OPT_LIST_INDENT'} while $width <= 90; } $format = $width . 'pt'; } # Process the data $target = $var{'OPT_TARGET'}; if ($target eq 'html' || $target eq 'hlp' || $target eq 'txt' || $target eq 'pod') { unshift(@text, "!block table; noheadings; style='plain'; tags='B,,B,,B'; wide; format='$format'"); push(@text, "!endblock"); } else { @text = (); (@flds) = &::TableFields(shift @tbl); for $rec (@tbl) { %values = &::TableRecSplit(*flds, $rec); push(@text, "NV[label='$values{'Name'}\\t']$values{'Value'}"); } } } # define - variable definitions @_define_FilterParams = ( 'Name Type Rule', 'family string <\w+>', 'export boolean', ); @_define_FilterModel = ( 'Field Category Rule', 'Name key <\w+>', 'Value optional', ); sub define_Filter { local(*text, %param) = @_; local(@tbl, @flds, $rec, %values); local($family, $export, $name); # Parse and validate the data @tbl = &::TableParse(@text); @text = (); &_FilterValidate(*tbl, *_define_FilterModel) if $validate; # Process the data $family = $param{'family'}; $family .= '_' if $family ne ''; $export = $param{'export'}; (@flds) = &::TableFields(shift @tbl); for $rec (@tbl) { %values = &::TableRecSplit(*flds, $rec); $name = $family . $values{'Name'}; push(@text, "!define $name $values{'Value'}"); push(@text, "!export $name") if $export; } } # default - variable defaulting @_default_FilterParams = ( 'Name Type Rule', 'family string <\w+>', 'export boolean', ); @_default_FilterModel = ( 'Field Category Rule', 'Name key <\w+>', 'Value optional', ); sub default_Filter { local(*text, %param) = @_; local(@tbl, @flds, $rec, %values); local($family, $export, $name); # Parse and validate the data @tbl = &::TableParse(@text); @text = (); &_FilterValidate(*tbl, *_default_FilterModel) if $validate; # Process the data $family = $param{'family'}; $family .= '_' if $family ne ''; $export = $param{'export'}; (@flds) = &::TableFields(shift @tbl); for $rec (@tbl) { %values = &::TableRecSplit(*flds, $rec); $name = $family . $values{'Name'}; push(@text, "!default $name $values{'Value'}"); push(@text, "!export $name") if $export && !$export{$name}; } } # script - embedded Perl @_script_FilterParams = (); sub script_Filter { local(*text, %param) = @_; # execute the code eval join("\n", @text); if ($@) { &::AppMsg("error", "script failed: $@"); } @text = (); } # pod - embedded Pod @_pod_FilterParams = ( 'Name Type Rule', 'main boolean', ); sub pod_Filter { local(*text, %param) = @_; use Pod::Sdf; @text = pod2sdf(\@text, \%param); } # get - get embedded documentation (sdfget format) @_get_FilterParams = ( 'Name Type Rule', 'report string', 'rule string', 'filename string', 'inverse boolean', ); sub get_Filter { local(*text, %param) = @_; # Guess the filename. We need to do this because sdfget is getting # it's input from stdin $param{'filename'} ||= $var{'FILE_PATH'}; # Build the options my $opts = "-g$param{'rule'} -f$param{'filename'} "; $opts .= $param{'inverse'} ? "-i" : "-r$param{'report'}"; # Get the documentation &CommandFilter(*text, "sdfget $opts -", "get"); } # inline - embedded target code @_inline_FilterParams = ( 'Name Type Rule', 'target string <\w+>', 'expand boolean', ); sub inline_Filter { local(*text, %param) = @_; local($line); # The default target is 'html' $target = $param{'target'} || 'html'; # Prefix each line with the appropriate style for $line (@text) { if ($param{'expand'}) { next if $line =~ /^\s*\!/; $line = &main::_SdfParaExpand($line); } $line = "__inline[target='$target']$line"; } } # end - prepend finalisation text @_end_FilterParams = (); sub end_Filter { local(*text, %param) = @_; # We prepend a blank line so that multiple end sections remain separate. unshift(@::sdf_end, '', @text); @text = (); } # comment - ignore text @_comment_FilterParams = ('ANY'); sub comment_Filter { local(*text, %param) = @_; @text = (); } # toc_html - format the table of contents for html @_toc_html_FilterParams = (); sub toc_html_Filter { local(*text, %param) = @_; # local(); local($toc_text, $toc_tag); # Prepend a section separator and a title $toc_text = $var{'DOC_TOC_TITLE'}; $toc_text = 'Table of Contents' unless $toc_text; $toc_tag = $parastyles_to{'TOCT'}; $toc_tag = "P2" unless $toc_tag; unshift(@text, "!HTML_PRE_SECTION", $toc_tag . "[noevents;notoc]$toc_text"); } # offices - format a set of office locations @_offices_FilterParams = ( 'Name Type Rule', 'style string', ); sub offices_Filter { local(*text, %param) = @_; local(@result); local($line, $cell_count); # Init things $style = $param{'style'} || 'plain'; @result = ("!table 2; format=55; wide; tmargin=12; bmargin=4; style='$style'"); # Place each address into a cell, 2 cells per row $cell_count = 0; for $line (@text) { $line =~ s/\s+$//; # Skip blank lines and comments next if $line eq ''; next if $line =~ /^\s*#/; # Handle group entries if ($line =~ /^GROUP:\s*/) { $cell_count = 0; push(@result, "!row 'Group'", "!cell cols=2; tmargin=14; bmargin=6; truling=Thin; bruling=Thin", "N[size='11pt';bold]$'"); } # Handle office titles elsif ($line =~ /:$/) { push(@result, "!row") if $cell_count++ % 2 == 0; push(@result, "!cell"); push(@result, "N[size='10pt']{{B:$line}}"); } # Handle phone and fax numbers elsif ($line =~ /^Ph:\s*/) { push(@result, "N[size='10pt';tabs='36pt']Ph:[[tab]]$'"); } elsif ($line =~ /^Fax:\s*/) { push(@result, "[[nl]]Fax:[[tab]]$'"); } # Handle address lines else { push(@result, "[[nl]]$line"); } } # Finalise things @text = (@result, "!endtable"); } ##### Help Filters ##### # hlp_header - table of jumps in the non-scrolling region @_hlp_header_FilterParams = (); @_hlp_header_FilterModel = ( 'Field Category Rule', 'Text key', 'Kind optional ' ); sub hlp_header_Filter { local(*text, %param) = @_; local(@tbl, @flds, $rec, %values); local($item, @items,$thetext); # Parse and validate the data @tbl = &::TableParse(@text); @text = (); &_FilterValidate(*tbl, *_hlp_header_FilterModel) if $validate; # Unless we're generating help, skip the rest return unless $var{'OPT_TARGET'} eq 'hlp'; # Process the data (@flds) = &::TableFields(shift @tbl); for $rec (@tbl) { %values = &::TableRecSplit(*flds, $rec); $thetext=$values{'Text'}; if ($values{'Kind'} eq 'jump') { $item = "{{N[jump='#jump_${topic}_$thetext';size='8 pt'] $thetext}}"; } else { $item = "{{N[hlp.popup='#jump_${topic}_$thetext';size='8 pt'] $thetext}}"; } push(@items, $item); } # Format the output @text = ("[hlp.header]" . join("{{E: }}", @items)); } # hlp_window - contents of a popup window (by TJH) @_hlp_window_FilterParams = (); sub hlp_window_Filter { local(*text, %param) = @_; unshift(@text,'!on paragraph ; __window_filter; $attr{"hlp.window"} = "1"'); push(@text,"!off paragraph __window_filter","[hlp.endwindow=1]"); } ##### Configuration Filters ##### # langdefs - load a set of (vgrind-like) language definitions @_langdefs_FilterParams = (); sub langdefs_Filter { local(*text, %param) = @_; # local(); local($_, @language_definition); # append lines until each language definition is complete & # then process definition @language_definition = (); for $_ (@text) { next if (/^#/); push(@language_definition, $_); #print "line:$_<\n"; &_DefineLang(*language_definition) unless /\\$/; } @text = (); } # docstyles - document style definitions @_docstyles_FilterParams = (); @_docstyles_FilterModel = ( 'Field Category Rule', 'Name key <\w+>', 'To optional', 'Variables optional', ); sub docstyles_Filter { local(*text, %param) = @_; local(@tbl, @flds, $rec, %values); local($name); local($to); # Parse and validate the data @tbl = &::TableParse(@text); @text = (); &_FilterValidate(*tbl, *_docstyles_FilterModel) if $validate; # Process the data (@flds) = &::TableFields(shift @tbl); for $rec (@tbl) { %values = &::TableRecSplit(*flds, $rec); # Update the declaration, if necessary $name = $values{'Name'}; $docstyles_name{$name} = 1; # Update the definition, if any $to = $values{'To'}; if ($to ne '') { $docstyles_to{$name} = $to; $docstyles_vars{$name} = $values{'Variables'}; } } } # variables - variable declarations @_variables_FilterParams = ( 'Name Type Rule', 'family string <[\w,]+>', 'export boolean', ); @_variables_FilterModel = ( 'Field Category Rule', 'Name key <\w+>', 'Type mandatory <\w+>', 'Rule optional', ); sub variables_Filter { local(*text, %param) = @_; local(@tbl, @flds, $rec, %values); local(@family, $export); local($name); local($type, $rule); local($family, @fullname); # Parse and validate the data @tbl = &::TableParse(@text); @text = (); &_FilterValidate(*tbl, *_variables_FilterModel) if $validate; # Process the data @family = split(/,/, $param{'family'}); $export = $param{'export'}; (@flds) = &::TableFields(shift @tbl); for $rec (@tbl) { %values = &::TableRecSplit(*flds, $rec); # Update the declaration $name = $values{'Name'}; $type = $values{'Type'}; $rule = $values{'Rule'}; if (@family) { for $family (@family) { $fullname = "${family}_$name"; $variables_name{$fullname} = 1; $variables_type{$fullname} = $type; $variables_rule{$fullname} = $rule if $rule ne ''; # Mark the variable as exported, if requested push(@text, "!export $fullname") if $export; } } else { $variables_name{$name} = 1; $variables_type{$name} = $type; $variables_rule{$name} = $rule if $rule ne ''; # Mark the variable as exported, if requested push(@text, "!export $name") if $export; } } } # parastyles - paragraph style definitions @_parastyles_FilterParams = (); @_parastyles_FilterModel = ( 'Field Category Rule', 'Name key <[A-Z_0-9]\w*>', 'Category optional ', 'To optional <[\w ]+>', 'Attributes optional', ); sub parastyles_Filter { local(*text, %param) = @_; local(@tbl, @flds, $rec, %values); local($name, $category); local($to); # Parse and validate the data @tbl = &::TableParse(@text); @text = (); &_FilterValidate(*tbl, *_parastyles_FilterModel) if $validate; # Process the data (@flds) = &::TableFields(shift @tbl); for $rec (@tbl) { %values = &::TableRecSplit(*flds, $rec); # Update the declaration, if necessary $name = $values{'Name'}; $category = $values{'Category'}; $parastyles_name{$name} = 1; $parastyles_category{$name} = $category if $category ne ''; # Update the definition, if any $to = $values{'To'}; if ($to ne '') { $parastyles_to{$name} = $to; $parastyles_attrs{$name} = $values{'Attributes'}; } } } # paraattrs - paragraph attribute definitions @_paraattrs_FilterParams = ( 'Name Type Rule', 'family string <\w+>', ); @_paraattrs_FilterModel = ( 'Field Category Rule', 'Name key <\w+>', 'To optional <\w+>', 'Map optional <[\%\&]\w+>', 'Attributes optional', 'Type optional <\w+>', 'Rule optional', ); sub paraattrs_Filter { local(*text, %param) = @_; local(@tbl, @flds, $rec, %values); local($family); local($name); local($type, $rule); local($to, $map, $attrs); # Parse and validate the data @tbl = &::TableParse(@text); @text = (); &_FilterValidate(*tbl, *_paraattrs_FilterModel) if $validate; # Process the data $family = $param{'family'} ne '' ? "$param{'family'}." : ''; (@flds) = &::TableFields(shift @tbl); for $rec (@tbl) { %values = &::TableRecSplit(*flds, $rec); # Update the declaration, if necessary $name = $family . $values{'Name'}; $type = $values{'Type'}; $rule = $values{'Rule'}; $paraattrs_name{$name} = 1; $paraattrs_type{$name} = $type if $type ne ''; $paraattrs_rule{$name} = $rule if $rule ne ''; # Update the definition, if any $to = $values{'To'}; $map = $values{'Map'}; $attrs = $values{'Attributes'}; if ($to || $map || $attrs) { $paraattrs_to{$name} = $to; $paraattrs_map{$name} = $map; $paraattrs_attrs{$name} = $attrs; } } } # phrasestyles - phrase style definitions @_phrasestyles_FilterParams = (); @_phrasestyles_FilterModel = ( 'Field Category Rule', 'Name key <[A-Z_0-9]\w*>', 'Category optional ', 'To optional <\=?\w+>', 'Attributes optional', ); sub phrasestyles_Filter { local(*text, %param) = @_; local(@tbl, @flds, $rec, %values); local($name, $category); local($to); local($proxy); # Parse and validate the data @tbl = &::TableParse(@text); @text = (); &_FilterValidate(*tbl, *_phrasestyles_FilterModel) if $validate; # Process the data (@flds) = &::TableFields(shift @tbl); for $rec (@tbl) { %values = &::TableRecSplit(*flds, $rec); # Update the declaration, if necessary $name = $values{'Name'}; $category = $values{'Category'}; $phrasestyles_name{$name} = 1; $phrasestyles_category{$name} = $category if $category ne ''; # Update the definition, if any $to = $values{'To'}; if ($to ne '') { if ($to =~ /^\=/) { $proxy = $'; if ($phrasestyles_name{$proxy}) { $to = $phrasestyles_to{$proxy}; } else { &::AppMsg("warning", "unknown phrase style $proxy '$proxy"); $to = $proxy; } } $phrasestyles_to{$name} = $to; $phrasestyles_attrs{$name} = $values{'Attributes'}; } } } # phraseattrs - phrase attribute definitions @_phraseattrs_FilterParams = ( 'Name Type Rule', 'family string <\w+>', 'para string', ); @_phraseattrs_FilterModel = ( 'Field Category Rule', 'Name key <\w+>', 'To optional <\w+>', 'Map optional <[\%\&]\w+>', 'Attributes optional', 'Type optional <\w+>', 'Rule optional', ); sub phraseattrs_Filter { local(*text, %param) = @_; local(@tbl, @flds, $rec, %values); local($family, $para); local($name, $paraname); local($type, $rule); local($to, $map, $attrs); # Parse and validate the data @tbl = &::TableParse(@text); @text = (); &_FilterValidate(*tbl, *_phraseattrs_FilterModel) if $validate; # Process the data $family = $param{'family'} ne '' ? "$param{'family'}." : ''; $para = $param{'para'}; (@flds) = &::TableFields(shift @tbl); for $rec (@tbl) { %values = &::TableRecSplit(*flds, $rec); # Update the declaration, if necessary $name = $family . $values{'Name'}; $paraname = $family . $para . $values{'Name'}; $type = $values{'Type'}; $rule = $values{'Rule'}; $phraseattrs_name{$name} = 1; $phraseattrs_type{$name} = $type if $type ne ''; $phraseattrs_rule{$name} = $rule if $rule ne ''; $paraattrs_name{$paraname} = 1; $paraattrs_type{$paraname} = $type if $type ne ''; $paraattrs_rule{$paraname} = $rule if $rule ne ''; # Update the definition, if any $to = $values{'To'}; $map = $values{'Map'}; $attrs = $values{'Attributes'}; if ($to || $map || $attrs) { $phraseattrs_to{$name} = $to; $phraseattrs_map{$name} = $map; $phraseattrs_attrs{$name} = $attrs; $paraattrs_to{$paraname} = $to; $paraattrs_map{$paraname} = $map; $paraattrs_attrs{$paraname} = $attrs; } } } # tablestyles - table style definitions @_tablestyles_FilterParams = (); @_tablestyles_FilterModel = ( 'Field Category Rule', 'Name key <\w+>', 'To optional <\w+>', 'Parameters optional', ); sub tablestyles_Filter { local(*text, %param) = @_; local(@tbl, @flds, $rec, %values); local($name); local($to); # Parse and validate the data @tbl = &::TableParse(@text); @text = (); &_FilterValidate(*tbl, *_tablestyles_FilterModel) if $validate; # Process the data (@flds) = &::TableFields(shift @tbl); for $rec (@tbl) { %values = &::TableRecSplit(*flds, $rec); # Update the declaration, if necessary $name = $values{'Name'}; $tablestyles_name{$name} = 1; # Update the definition, if any $to = $values{'To'}; if ($to ne '') { $tablestyles_to{$name} = $to; $tablestyles_params{$name} = $values{'Parameters'}; } } } # tableparams - table parameter definitions @_tableparams_FilterParams = ( 'Name Type Rule', 'family string <\w+>', ); @_tableparams_FilterModel = ( 'Field Category Rule', 'Name key <\w+>', 'To optional <\w+>', 'Map optional <[\%\&]\w+>', 'Parameters optional', 'Type optional <\w+>', 'Rule optional', ); sub tableparams_Filter { local(*text, %param) = @_; local(@tbl, @flds, $rec, %values); local($family); local($name); local($type, $rule); local($to, $map, $params); # Parse and validate the data @tbl = &::TableParse(@text); @text = (); &_FilterValidate(*tbl, *_tableparams_FilterModel) if $validate; # Process the data $family = $param{'family'} ne '' ? "$param{'family'}." : ''; (@flds) = &::TableFields(shift @tbl); for $rec (@tbl) { %values = &::TableRecSplit(*flds, $rec); # Update the declaration, if necessary $name = $family . $values{'Name'}; $type = $values{'Type'}; $rule = $values{'Rule'}; $tableparams_name{$name} = 1; $tableparams_type{$name} = $type if $type ne ''; $tableparams_rule{$name} = $rule if $rule ne ''; # Update the definition, if any $to = $values{'To'}; $map = $values{'Map'}; $params = $values{'Parameters'}; if ($to || $map || $params) { $tableparams_to{$name} = $to; $tableparams_map{$name} = $map; $tableparams_params{$name} = $params; } } } # rowparams - row parameter definitions @_rowparams_FilterParams = ( 'Name Type Rule', 'family string <\w+>', ); @_rowparams_FilterModel = ( 'Field Category Rule', 'Name key <\w+>', 'To optional <\w+>', 'Map optional <[\%\&]\w+>', 'Parameters optional', 'Type optional <\w+>', 'Rule optional', ); sub rowparams_Filter { local(*text, %param) = @_; local(@tbl, @flds, $rec, %values); local($family); local($name); local($type, $rule); local($to, $map, $params); # Parse and validate the data @tbl = &::TableParse(@text); @text = (); &_FilterValidate(*tbl, *_rowparams_FilterModel) if $validate; # Process the data $family = $param{'family'} ne '' ? "$param{'family'}." : ''; (@flds) = &::TableFields(shift @tbl); for $rec (@tbl) { %values = &::TableRecSplit(*flds, $rec); # Update the declaration, if necessary $name = $family . $values{'Name'}; $type = $values{'Type'}; $rule = $values{'Rule'}; $rowparams_name{$name} = 1; $rowparams_type{$name} = $type if $type ne ''; $rowparams_rule{$name} = $rule if $rule ne ''; # Export the definition, if any $to = $values{'To'}; $map = $values{'Map'}; $params = $values{'Parameters'}; if ($to || $map || $params) { $rowparams_to{$name} = $to; $rowparams_map{$name} = $map; $rowparams_params{$name} = $params; } } } # cellparams - cell parameter definitions @_cellparams_FilterParams = ( 'Name Type Rule', 'family string <\w+>', ); @_cellparams_FilterModel = ( 'Field Category Rule', 'Name key <\w+>', 'To optional <\w+>', 'Map optional <[\%\&]\w+>', 'Parameters optional', 'Type optional <\w+>', 'Rule optional', ); sub cellparams_Filter { local(*text, %param) = @_; local(@tbl, @flds, $rec, %values); local($family); local($name); local($type, $rule); local($to, $map, $params); # Parse and validate the data @tbl = &::TableParse(@text); @text = (); &_FilterValidate(*tbl, *_cellparams_FilterModel) if $validate; # Process the data $family = $param{'family'} ne '' ? "$param{'family'}." : ''; (@flds) = &::TableFields(shift @tbl); for $rec (@tbl) { %values = &::TableRecSplit(*flds, $rec); # Update the declaration, if necessary $name = $family . $values{'Name'}; $type = $values{'Type'}; $rule = $values{'Rule'}; $cellparams_name{$name} = 1; $cellparams_type{$name} = $type if $type ne ''; $cellparams_rule{$name} = $rule if $rule ne ''; # Update the definition, if any $to = $values{'To'}; $map = $values{'Map'}; $params = $values{'Parameters'}; if ($to || $map || $params) { $cellparams_to{$name} = $to; $cellparams_map{$name} = $map; $cellparams_params{$name} = $params; } } } # targetobjects - define target objects @_targetobjects_FilterParams = ( 'Name Type Rule', 'type string <\w+>', ); @_targetobjects_FilterModel = ( 'Field Category Rule', 'Name key', 'Parent optional', 'Attributes optional', ); sub targetobjects_Filter { local(*text, %param) = @_; local(@tbl, @flds, $rec, %values); local($type); local(%attrs); # Parse and validate the data @tbl = &::TableParse(@text); @text = (); &_FilterValidate(*tbl, *_targetobjects_FilterModel) if $validate; # Process the data $type = $param{'type'}; (@flds) = &::TableFields(shift @tbl); for $rec (@tbl) { %values = &::TableRecSplit(*flds, $rec); %attrs = &::SdfAttrSplit($values{'Attributes'}); $attrs{'Name'} = $values{'Name'}; $attrs{'Parent'} = $values{'Parent'}; push(@text, &::SdfJoin("__object", $type, %attrs)); } } # datestrings - date strings for month, weekday, etc. @_datestrings_FilterParams = ( ); @_datestrings_FilterModel = ( 'Field Category Rule', 'Symbol key ', 'Values optional', ); sub datestrings_Filter { local(*text, %param) = @_; local(@tbl, @flds, $rec, %values); local($strings); # Parse and validate the data @tbl = &::TableParse(@text); @text = (); &_FilterValidate(*tbl, *_datestrings_FilterModel) if $validate; # Process the data (@flds) = &::TableFields(shift @tbl); for $rec (@tbl) { %values = &::TableRecSplit(*flds, $rec); $strings = [ split(/\s+/, eval $values{'Values'}) ]; $main::misc_date_strings{$values{'Symbol'}} = $strings; } } # meta - meta information about a document @_meta_FilterParams = ( ); @_meta_FilterModel = ( 'Field Category Rule', 'Name key', 'Content optional', ); sub meta_Filter { local(*text, %param) = @_; local(@tbl, @flds, $rec, %values); # Parse and validate the data @tbl = &::TableParse(@text); @text = (); &_FilterValidate(*tbl, *_meta_FilterModel) if $validate; # Process the data (@flds) = &::TableFields(shift @tbl); for $rec (@tbl) { %values = &::TableRecSplit(*flds, $rec); push(@text, &::SdfJoin("__object", "meta", %values)); } } # links - links for a document @_links_FilterParams = ( ); @_links_FilterModel = ( 'Field Category Rule', 'Jump key', 'Type optional', 'Relationship optional', ); sub links_Filter { local(*text, %param) = @_; local(@tbl, @flds, $rec, %values); # Parse and validate the data @tbl = &::TableParse(@text); @text = (); &_FilterValidate(*tbl, *_links_FilterModel) if $validate; # Process the data (@flds) = &::TableFields(shift @tbl); for $rec (@tbl) { %values = &::TableRecSplit(*flds, $rec); $values{'Type'} = 'text/css' if $values{'Type'} eq ''; $values{'Relationship'} = 'stylesheet' if $values{'Relationship'} eq ''; push(@text, &::SdfJoin("__object", "link", %values)); } } # stylesheet - stylesheet rules for a document @_stylesheet_FilterParams = ( ); @_stylesheet_FilterModel = ( ); sub stylesheet_Filter { local(*text, %param) = @_; # Process the data @text = (&::SdfJoin("__stylesheet", join("\n", @text), %param)); } # package return value 1;