/*

/ Program      : npcttab.sas
/ Version      : 10.29
/ Author       : Roland Rashleigh-Berry
/ Date         : 30-Dec-2011
/ Purpose      : Clinical reporting macro to produce tables showing "n", the
/                percentage and optionally, the number of events.
/ SubMacros    : %quotelst %words %varlen %zerogrid %npctpvals %attrn %qdequote
/                %varfmt %sysfmtlist %verifyb %commas %sortedby %match %nodup
/                %mvarlist %mvarvalues %varlistn %nlobs %attrc %removew
/                %hasvarsc %varnum %splitvar (assumes %popfmt already run)
/ Notes        : Observations for the total of all treatment arms will be
/                generated inside this macro so do not set this up in the
/                input dataset.
/
/                If you do not specify ordering variables then the levels will
/                be displayed in descending subject frequency count for the
/                total column (even if total column is not displayed).
/
/ Usage        : See tutorial with demonstrations on the Spectre web site. After
/                completing the tutorial you will be able to learn more about the
/                capabilities of this macro by reading this header.
/
/===============================================================================
/ PARAMETERS:
/-------name------- -------------------------description------------------------
/ dsin              Name of input dataset
/ msglevel=X        Message level to use inside this macro for notes and 
/                   information written to the log. By default, both Notes and
/                   Information are suppressed. Use msglevel=N or I for more
/                   information.
/ dsall             Optional dataset for displaying all levels present. The 
/                   level variables in this dataset must be identical to those
/                   in the dsin= dataset. If the high level or mid level
/                   variables are missing from this dataset then they will be 
/                   added using what is found in the input dataset so you can
/                   just specify a dataset containing low level terms if you
/                   wish.
/ alllowlvl=no      This takes effect so long as dsall= is null and if set to
/                   "yes" (no quotes) will create a dsall= dataset with just the
/                   lower level terms in it as found in the input data. If you
/                   are sure that all the lower level terms are in the data then
/                   using this parameter is easier than setting up a dsall=  
/                   dataset containing the lower terms.
/ style=1           This is the default layout style to use but you should
/                   always set this value as the default value may change from
/                   time to time.
/                     Style=1 is for indented terms and is most suitable for
/                             ascii output. It is the original style and the 
/                             most reliable. It should handle three-level
/                             reports well.
/                     Style=2 is for each level term to appear in its own column
/                             but because these terms can be long it can cause
/                             data values to be "pushed down" where these terms
/                             flow. This option is only good for ODS output and
/                             should only be used for sas v9.2 or later with
/                             the parameter spanrows=yes set for this macro.
/                             This style is NOT IMPLEMENTED YET and if you
/                             specify it then style=1 is used instead.
/                     Style=3 is for combining all the levels in the same column
/                             and the levels will be indented with spaces (non-
/                             breaking spaces for ODS output) as per the indent=
/                             parameter setting. Terms that flow onto following
/                             lines can have a "hanging indent" according to the
/                             hindent= parameter setting. Note that you need to
/                             specify the column label to the style3lbl= as it
/                             will not use the normal labels. This style uses
/                             the %splitvar macro that currently only works for
/                             Western character sets such that one letter takes
/                             up one byte. See the %splitvar macro header for
/                             more information.
/ usecolon=yes      By default, use a colon as an indentation marker when
/                   splitting text.
/ double=no         By default, do not throw blank lines between rows.
/ midskip=yes       By default, throw a blank line between mid-level terms
/ highskip=no       By default, do not throw a blank line between high-level
/                   terms.
/ filtercode        SAS code you specify to drop observations and do minor
/                   reformatting before printing is done. If this code
/                   contains commas then enclose in quotes (the quotes will be
/                   dropped from the start and end before the code is executed).
/                   You can have multiple lines of sas code if you end each line
/                   with a semicolon. For serious editing you should do this in
/                   a macro defined to extmacro= .
/ extmacro          External macro to call (no % sign) and will typically be
/                   used to include extra stats values to add to the report.
/ pageline=no       Default is not to show a line under the report on each page
/ pageline1-15      Additional lines (in quotes) to show at the bottom of each
/                   page.
/ pagemacro         Name of macro (no starting "%") to assign the page line
/                   values instead of setting them manually. This macro will be
/                   called in a "proc report" compute block so it must not
/                   contain any data step or procedure boundaries in any code
/                   it calls and must resolve to something that is syntactically
/                   correct for compute block processing. You can have 
/                   parameter settings in the macro call.
/ endline=no        Default is not to show a line under the end of the report.
/                   This is the best setting for ODS RTF tables and the like.
/ endline1-15       Additional lines (in quotes) to show at end of report
/ endmacro          Name of macro (no starting "%") to assign the end line
/                   values instead of setting them manually. This macro will be
/                   called in a "proc report" compute block so it must not
/                   contain any data step or procedure boundaries in any code
/                   it calls and must resolve to something that is syntactically
/                   correct for compute block processing. You can have 
/                   parameter settings in the macro call.
/ dspop=_popfmt     Name of population dataset used for calculating percentages
/ uniqueid          List of variables that uniquely identify a subject
/                   (defaults to &_uniqueid_ set in %popfmt).
/ trtvar            Treatment group variable (defaults to &_trtvar_ set in
/                   %popfmt) which must be a coded numeric variable or a short
/                   coded character variable (typically one or two bytes with no
/                   spaces).
/ trtfmt            (optional) format to apply to the treatment variable
/ trtalign=c        Default alignment of treatment column headers is centred
/ odsescapechar="°" ODS escape character (quoted)
/ trtlabel          (quoted) Treatment arm label. Default is not to show a
/                   treatment label.
/ odstrtlabel       ODS non-listing output label for treatment arms (defaults to
/                   value of trtlabel= ). Note that column underlines defined
/                   using "--" that work for ascii output do not work for ODS
/                   output. Assuming "^" is the ODS escape character then to
/                   achieve a spanning underline for ODS output then use the
/                   following method where both the ascii form and ODS form are
/                   shown as examples (works for SAS 9.2 or later)
/              trtlabel="Treatment Arm" "__"
/           odstrtlabel='^{style [borderbottomwidth=1 borderbottomcolor=black]
/                        Treatment Arm}'
/ topline=yes       Default is to show a line at the top of the report for ascii
/                   output. Non-listing ODS output will not show this line.
/ toplabel          Default is not to have a label at the top of the table but
/                   should be quoted if required.
/ spacing=2         Default spacing is 2 between the lowlvl terms and treatments
/ trtspace=4        Default spacing is 4 between treatment arms
/ byvars            (optional) by variables
/ highlvl           High level variable (optional)
/ highlvlord        (optional) variable to order high level terms
/ pageon            Values of the highest level variable to force page throws.
/                   These should be separated by spaces. Strings should be
/                   enclosed in quotes. The in: function is used internally 
/                   so string values need only be start values.
/                   example:    "Muscul" "Hepa"
/ midlvl            Mid level variable (optional)
/ midlvlord         (optional) variable to order mid level terms
/ lowlvl            Low level variable
/ lowlvlord         (optional) variable to order low level terms
/ lowlvlw=0         Column width for lowlvl items. If 0 then it is automatically
/                   calculated to fit the page.
/ style3w=0         Column width for the style 3 report where all level terms
/                   are in the same column. If 0 then it is automatically
/                   calculated to fit the page.
/ nlen=3            Default digit length for the "n" count is 3. 
/ trtw1-19          You can specify individual column widths for the treatment
/                   arms. If left blank then this will be calculated for you.
/ trtsp1-19         You can specify individual column spacing for the treatment
/                   arms. If left blank then this will be filled in from the
/                   spacing= parameter setting (for the first treatment arm)
/                   and the trtspace= setting for the others.
/ indent=3          Default indentation of the level terms is 3 characters
/ hindent=0         The number of columns to leave blank for style=3 reports 
/                   when a term flows onto following lines (a "hanging indent").
/                   For already indented terms then this value will be added to
/                   the existing indent.
/ highlvllbl        Label for the high level terms (must be a single line quoted
/                   or unquoted for style=1 reports but for style=2 reports it 
/                   can have several parts and each part must be quoted).
/ midlvllbl         Label for the mid level terms (must be a single line quoted
/                   or unquoted for style=1 reports but for style=2 reports it 
/                   can have several parts and each part must be quoted).
/ lowlvllbl         Label for the low level terms (must be a single line quoted
/                   or unquoted for style=1 reports but for style=2 reports it 
/                   can have several parts and each part must be quoted).
/ style3lbl         This is the column label to use for style 3, where all the
/                   information shares the same column. It can have several 
/                   parts and each part should be quoted. If this is left blank
/                   then the macro will generate it from any existing labels
/                   supplied and failing that it will use the variable labels.
/ trtord            If lowlvlord not specified then this is the treatment group
/                   value used for ordering lowlvl items which defaults to
/                   whata is defined to _trttotstr_ .
/ trttotval         Extra observations are created for the total of all 
/                   treatment groups and the default value is _trttotstr_.
/ total=yes         Default is to show the total for all treatment arms
/ anylowlvl="ANY AE"       Default displayed string for combined low level
/                   terms (required and must be quoted). If not wanted then it
/                   must still be specified but dropped in the droplowlvl= list.
/ toplowlvl         Defaults to whatever anylowlvl= is set to. This causes the
/                   lowlvl term to be displayed first in the midlvl group.
/                   Only set this if you are setting up your own effective
/                   anylowlvl= term in the input data.
/ anywhen=before    This affects how the "any lowlvl" is calculated. If
/ (anywhen=after)   "before" (default) then it will be calculated before any
/                   minimum percentage is applied to the display. If "after"
/                   then first the lowlvl terms to display are determined and
/                   then the "any" counts and percentages are based on just
/                   those lowlvl terms that will be displayed. You should make
/                   sure that the label supplied to anylowlvl= makes it clear
/                   what method is used. If no minimum percentage or minimum
/                   count is supplied then "anywhen=before" will override
/                   whatever is specified to this parameter.
/ droplowlvl        Low level value(s) to drop from table (quoted and separate
/                   with spaces if more than one). You can use this to create
/                   a total with your data by repeating each observation with
/                   the higher level terms set to say what you want such as
/                   "Total of subjects with adverse events" and set your low
/                   level term to "XXX", for example, and then specify to drop
/                   "XXX" to this parameter.
/ split=@           Split character for proc report (no quotes)
/ headskip=yes      Default is to skip a line at the top if proc report output
/ spantotal=yes     Default is for the treament label to span the total column
/ minpct            Minimum percentage value (in trtord= arm) to display
/ mincount          Minimum patient count (in trtord= arm) to display
/ minpctany         Minimum percentage value in any treatment arm to display
/ mincountany       Minimum patient count in any treatment arm to display
/ print=yes         By default, allow this macro to print the report
/ events=no         By default, do not display the number of events
/ nevlen            Digit length for the number of events. Defaults to the
/                   nlen= setting if not used.
/ dsout=_npcttab    Output dataset name if not printing (no modifiers)
/ trtvarlist        You should not need to use this except in special situations
/                   where you want to specify labels to go with the transposed 
/                   treatment variables in the proc report call at the end of
/                   this macro such as in this example:
/              trtvarlist=("__ DRUG 1 __" TRT1 TRT2) ("__ DRUG 2 __" TRT3 TRT4)
/                   You need to know what treatment variables there are and
/                   this will be put in the log by the %popfmt macro. If you
/                   are specifying column widths (usually not required) then if
/                   you change the natural order of these TRT variables then the
/                   trtw(n)= value that acts on a variable is the one that you
/                   would use if the natural order had not been changed.
/ pcttrt            If set to a list of values (separated by spaces, not commas)
/                   will calculate the percentage only for those treatment arm
/                   values. Will be ignored if event counts were requested.
/ pvalues=no        By default, do not calculate p-values
/ usetest           Set to C (Chi-square) or F (Fisher's exact) to override
/                   decision of macro to select Chi-square or Fisher's exact
/                   test based on expected cell counts. Can also be set to T2,
/                   T1L or T1R for the two-sided, one-sided-left or 
/                   one-sided-right Cochran-Armitage Trend test.
/ pvalkeep          Only keep p-values that satisfy this condition (e.g. <0.05)
/ pvalfmt=p63val.   Default format to use for p-values (uses 6.3 or <0.001)
/ pvalvar=TRT9999   Name for p-value variable
/ pvallbl="p-value" Label for p-value variable
/ pvaltrtlist       List of treatment arm values (separated by spaces) used for
/                   p-value calculation (defaults to not using the value
/                   assigned to trttotval= ).
/ fisherid=^        Symbol to suffix formatted p-values for the Fisher exact
/                   test.
/ chisqid=~         Symbol to suffix formatted p-values for the Chi-square test
/ trendid           Symbol to use to suffix p-values for the Cochran-Armitage
/                   Trend Test.
/ nodata=nodata     Name of macro to call to produce a report if there is no
/                   data. This would normally put out a "NO DATA" message.
/ pgbrkpos          Page breaking position (no quotes - defaults to "before")
/ pctsign=no        By default, do not show the percent sign for percentages
/ pctfmt=5.1        Default format for the percentage
/ pctcompress=no    Whether to compress the percentage
/ npctord=npct      By default display N before PCT values. Set to pctn to
/                   reverse this order.
/ pctwarn=yes       By default, put out a warning message if a percentage is
/                   greater than 100.01
/ odsrtf            Give the "file='filename' style=style" (unquoted) to create
/                   rtf output. "ods rtf   ;" and "ods rtf close;" will be
/                   automatically generated. If you want to suppress the plain
/                   text output then use "ods listing close;" before the call to
/                   the macro and "ods listing;" afterwards to reinstate listing
/                   output. Use of topline=no is advised with ods options.
/ odshtml           Works the same way as odsrtf
/ odshtmlcss        Works the same way as odsrtf
/ odscsv            Works the same way as odsrtf
/ odspdf            Works the same way as odsrtf
/ odslisting        Allows you to specify a file= statement
/ odsother          Works the same way as odsrtf but you have to supply the
/                   destination word as the first word.
/ tfmacro           Name of macro (no % sign) containing titles and footnotes
/                   code to be enacted before any output is produced, if set.
/                   You will be able to use the "repwidth" macro variable which
/                   is the width of the report in columns if you use this
/                   parameter and reference "repwidth" in your macro code.
/ odstfmacro        Name of macro (no % sign) containing titles and footnotes
/                   code to be enacted before any ODS non-listing output is
/                   produced, if set. If not used then the macro defined to
/                   tfmacro= will be in effect.
/ spanrows=yes      Applies to sas v9.2 and later. Default is to enable the
/                   "spanrows" option for "proc report". You should leave this
/                   set to "yes" (no quotes) unless you have a clear need.
/ eventsort=yes     By default, use the event count as a secondary sort key.
/ debug=no          By default, do not keep the work datasets created by this
/                   macro and activate mprint.
/ keepmidlvlmin=no  By default, do not keep the mid-level term where the 
/                   percentage or count meets the minimum criteria but there are
/                   no low level terms that meet the criteria.
/ dsdenom           Dataset to use for the denominator for calculating
/                   percentages. Setting this overrides the pctcalc= process.
/                   The denominator value must be held in the variable _total.
/                   The sort order of this dataset will be used for the merge
/                   but if no sort order is known then a matching of variable 
/                   names will be done. Note that if you have "by" variables
/                   then these should normally be in this dataset as well.
/ denomshow=yes     By default, show the denominator value (ddd) held in the
/                   dsdenom dataset as part of the display string. The digits
/                   will be trimmed and will have one space on each side. A 
/                   slash will precede it as shown below.
/                   nnn / ddd (pct)
/ byrowvar          This is for when you want to show a "by" variable as a row
/                   following the variable you are analyzing.
/ byroword          Ordering variable for byrowvar if needed
/ byrowlabel        Label for byrowvar
/ byrowalign        Alignment of byrow (left/center/right)
/ byrowfmt          Format for byrow variable
/ byroww            Width for byrowvar
/                   ============================================================
/                   Note that giving incorrect parameter values to fonts or any
/                   other values used in a style() statement in proc report can
/                   result in extremely confusing log error messages, usually
/                   shown as a missing round bracket sign as shown below.
/                     ERROR 79-322: Expecting a (.
/                     ERROR 76-322: Syntax error, statement will be ignored.
/                   http://support.sas.com/kb/2/457.html
/                   There is nothing wrong with this macro if this happens. You 
/                   just need to get your values right. Whatever style()
/                   statement causes an error message is the one that contains
/                   an incorrect value. Check in the SAS documentation for the
/                   values you are allowed to use. It won't be font weight or
/                   style causing the problem as correct values for these are
/                   enforced by this macro.
/ font_face_stats=times   Font to use for ODS output of the calculated stats
/                   values. For correct alignment of the decimal point for non-
/                   paired stats for MS Office then this should be set to
/                   "Courier" (no quotes). If your font face contains more than
/                   one word then use quotes (e.g. "courier new"). Also use
/                   quotes if you are specifying a search order for fonts such
/                   as "arial, helvetica".
/ font_weight_stats=medium    Weight of font to use for stats columns 
/                   (choice of light, medium or bold enforced by this macro).
/ font_weight_other=bold    Weight of font to use for columns other than 
/                   the calculated stats.
/ font_face_other=times     Font to use for ODS output for columns other
/                   than the calculated stats.
/ font_style_other=roman    Font style (italic, roman or slant) to use for ODS 
/                   output for columns other than the calculated stats.
/ font_weight_other=bold    Weight of font to use for columns other than 
/                   the calculated stats (choice of light, medium or bold).
/ report_border=no  Whether to draw a border around the report
/ header_border=no  Whether to draw a border around the column headers
/ column_border=no  Whether to draw a border around the cells in the report
/ lines_border=no   Whether to draw a border around the computed lines in the 
/                   report.
/ rules=none        Ruler lines to use in the report must be set to one of the
/                   following: none, all, cols, rows, groups.
/ cellspacing=0     Spacing between cells
/ cellpadding=0     Padding between cells
/ outputwidthpct=calc    Integer percentage of the ODS output width to use. 
/                   Default is "calc" (no quotes) for the %unistats macro to 
/                   calculate a suitable value itself (ReportWidth*2/3).
/                   Set to null (nothing) for ODS to do automatic sizing. This
/                   won't look nice as there will be insufficient space between
/                   columns with the default cellspacing=0. Set to whatever
/                   integer percentage you like, otherwise. If this is set to
/                   anything other than null then the column widths for ascii
/                   reports are used to calculate percentages for individual
/                   columns (if these are available).
/ compskip=yes      (no quotes) By default, throw a blank line after a mid
/                   level term for ODS reports. For ascii output, compskip=no
/                   will always be in effect.
/ compskippos=after (no quotes) By default, throw the skip line after the mid
/                   level term.
/                   ============================================================
/                   Colors for the different parts of the table
/ background_header=white
/ foreground_header=black
/ background_stats=white
/ foreground_stats=black
/ background_other=white
/ foreground_other=black
/                   ============================================================
/                   Whether to draw underlines (_ul) or overlines (_ol) for
/                   different parts of the report.
/ header_ul=yes
/ report_ul=yes
/ report_ol=yes
/ linepx=1          Width of drawn line in pixels
/ linecol=black     Color of drawn line
/ compskip_ul=no    Default is not to underline the compskip extra lines
/ compskippx=1      Width in pixels of the compskip underlines
/ compskipcol=black   Color of the compskip underlines
/                   ============================================================
/                   Note that you can insert code for the following two
/                   parameters that will apply immediately before and
/                   immediately after the "proc report" step. ENCLOSE YOUR CODE
/                   IN SINGLE QUOTES. Enclosed SINGLE quotes will be removed by
/                   the macro before code execution. Note that you are
/                   responsible for including all semicolons and "run"
/                   statements required for correct code syntax.
/ odsprerepcode     Code to execute immediately before the "proc report" step
/                   for ODS non-listing output (enclose in single quotes).
/ odspostrepcode    Code to execute immediately after the "proc report" step
/                   for ODS non-listing output (enclose in single quotes).
/ dsparam           Name of parameter dataset. This can EITHER be a "flat"
/                   dataset with variable names matching parameter names OR a
/                   Name-Value pair "tall" dataset (both Name and Value must be
/                   character variables and be called "Name" and "Value" in the
/                   input dataset) with the contents of Name matching a parameter
/                   name and Value its value. "Tall" datasets are suited to the
/                   metadata-driven use of this macro. In both cases, variables
/                   should be character variables. Numeric values can be used
/                   but they must be supplied as characters. Note that parameter
/                   values that are normally supplied in quotes such as 'Courier'
/                   must be enclosed in extra quotes such as Value="'Courier'"
/                   when building the parameter dataset.
/
/                   You can use dataset modifiers when specifying the input
/                   dataset and these modifiers will be applied to create the
/                   internal work dataset "_dsparam". Do not call your parameter
/                   dataset "_dsparam" as this is reserved for use inside this
/                   macro and will be deleted.
/
/ listallparams=no  By default, do not list all the parameters and their values
/                   that this macro has put into effect. Parameter dataset
/                   settings are always listed. Note that parameter values are
/                   often abbreviated to upper case first character inside this
/                   macro such as "no" becoming "N" and "yes" becoming "Y".
/===============================================================================
/ AMENDMENT HISTORY:
/ init --date-- mod-id ----------------------description------------------------
/ rrb  12Mar06         Version 4.0 with three level reporting and level
/                      parameter name changes.
/ rrb  08May06         Set _str=" " when total population and _count are 0
/ rrb  14Jul06         Header tidy
/ rrb  13Feb07         "macro called" message added
/ rrb  08Mar07         Use %qdequote for parameter quote checks for v4.1
/ rrb  09Mar07         Allow use of formatted level variables for v5.0
/ rrb  10Mar07         dsall= parameter and processing added for v6.0
/ rrb  11Mar07         80 column limit strictly enforced on header
/ rrb  21Mar07         Added support for the "nocenter" option
/ rrb  30Mar07         Bug fixed with trtlabel and toplabel
/ rrb  03May07         pctsign=no parameter added to allow users to force the
/                      display of the percent sign if required.
/ rrb  16May07         pctfmt=5.1 parameter added to allow users to change the
/                      percent format.
/ rrb  17May07         Further checking of pctfmt= value added (v6.5)
/ rrb  30jul07         Header tidy
/ rrb  01Sep07         pctwarn=yes parameter added
/ rrb  18Sep07         odsrtf= parameter added
/ rrb  19Sep07         odshtml= and odspdf= parameters added
/ rrb  21Sep07         odsother= parameter added
/ rrb  23Sep07         Header tidy
/ rrb  30Sep07         topline=no is now the default which is better suited to
/                      ODS output.
/ rrb  10Nov07         Bug fixed to allow $ and $CHAR formats
/ rrb  27Jan08         odshtmlcss= and odscsv= parameters added and logic
/                      changed for when print=no so that other ods output is
/                      still produced.
/ rrb  15Mar08         odslisting= parameter added
/ rrb  16Mar08         "ods listing" statement used to free ods listing file
/ rrb  26Mar08         Bug in handling $ and $CHAR formats fixed
/ rrb  10Apr08         spanrows= parameter added
/ rrb  26Apr08         style=3 , style3w= , style3lbl= and eventsort= 
/                      parameters added for this major upgrade to v7.0
/ rrb  26Apr08         debug=no and keepmidlvlmin=yes parameters added
/ rrb  26Apr08         Duplicate style 3 label parameter removed
/ rrb  30Apr08         dsdenom= and denomshow= parameters added
/ rrb  30Apr08         pctcompress= and byrow= parameters added for v8.0
/ rrb  08May08         style(COLUMN)={HTMLSTYLE="mso-number-format:'\@'"} added
/                      to proc report call so that MS Office treats the cells as
/                      text-formatted cells. The same can be achieved using
/                      headtext=""
/                      in the ODS statement but it is hard to remember.
/ rrb  09May08         font_face_stats=, font_face_other= and font_weight_other=
/                      parameters added.
/ rrb  11Sep08         font_weight_stats= parameter added and problems resolving
/                      these parameters fixed.
/ rrb  16Nov08         _combtext length increased to 256 as well as length of
/                      some variables used for spanning the report increased
/                      from 200 to 256. Default style changed back from 3 to 1.
/                      These changes implemented in version 8.3.
/ rrb  10Feb09         Some problems with sort order of main results dataset
/                      fixed in v8.4
/ rrb  11May09         prerepcode= and postrepcode= parameters added plus
/                      fisherid= and chisqid= made consistent with %unistats
/                      for v8.5
/ rrb  18Jun09         tfmacro= parameter added for v8.6
/ rrb  10Jul09         Ascii output created seperately from ODS non-listing
/                      output. Parameters prerepcode= and postrepcode= renamed
/                      to odsprerepcode= and odspostrepcode= . Parameter
/                      odstrtlabel= added. Default changed to topline=yes but
/                      this will only be shown for ascii output (v9.0)
/ rrb  11Jul09         odstfmacro= parameter added (v9.1)
/ rrb  12Jul09         dsparam= parameter added (v9.2)
/ rrb  13Jul09         listallparams= parameter added plus parameter dataset
/                      handling changed slightly (v9.3)
/ rrb  30Aug09         odsescapechar= parameter added plus header tidy (v9.4)
/ rrb  10Sep09         %unquote() used with %qdequote() (v9.5)
/ rrb  13Sep09         asis=on put into effect for columns (v9.6)
/ rrb  12Oct09         Calls to %dequote changed to calls to %qdequote due to
/                      macro renaming (v9.7)
/ rrb  08Nov09         New parameter pageon= added (v9.8)
/ rrb  15Nov09         Warning message about BY variable length now avoided
/                      plus length limit equal to report width on displayed
/                      higher term now limited to non-ODS output only (v9.9)
/ rrb  23Jan10         More ODS parameters used in %unistats added (v10.0)
/ rrb  24Jan10         Variable _combtext2 added for better style=3 ODS
/                      indentation of the combined categories when proportional
/                      text is used (v10.1)
/ rrb  24Jan10         PDF output generated separately due to problems with
/                      column widths (v10.2)
/ rrb  24Jan10         compskip= and associated parameters added from %unicatrep
/                      to throw a line after a mid level term (v10.3)
/ rrb  18Nov10         Added npctord= parameter to allow change of order for
/                      displaying N and PCT values (v10.4)
/ rrb  06Feb11         Bug with unresolved pvalue macro variable fixed (v10.5)
/ rrb  08May11         Code tidy
/ rrb  17May11         Name-Value pair datasets allowed as parameter datasets
/                      in addition to "flat" parameter datasets (v10.6)
/ rrb  24May11         filtercode=, extmacro= and msglevel= parameter
/                      processing added (v10.7)
/ rrb  26May11         Keep event counts in output dataset and added double=
/                      parameter for throwing blank lines between items (v10.8)
/ rrb  27May11         pageline= and endline= parameters added (v10.9)
/ rrb  31May11         Bug with pageline handling fixed (v10.10)
/ rrb  05Jun11         pagevar= parameter removed as not used and superseded by
/                      the pageon= parameter (v10.11)
/ rrb  23Jun11         Bug in anywhen=after processing fixed (v10.12)
/ rrb  12Jul11         trtalign= parameter added (v10.13)
/ rrb  17Jul11         Added hindent= parameter and better flow for ascii text
/                      for style=3 reports (v10.14)
/ rrb  17Jul11         Allow three-level reporting for style=3 (v10.15)
/ rrb  22Jul11         Improved dsall processing to add high level or mid level
/                      variables if missing (v10.16)
/ rrb  22Jul11         alllowlvl= and dsallonly= processing added (v10.17)
/ rrb  23Jul11         Bug in alllowlvl= processing fixed (v10.18)
/ rrb  19Aug11         Changed call to %splitvar for renamed parameter (v10.19)
/ rrb  06Oct11         midskip= and highskip= parameters added (v10.20)
/ rrb  13Oct11         Now odsescapechar="°" the same as %unistats (v10.21)
/ rrb  24Oct11         Minor bug positioning compute block line fixed (v10.22)
/ rrb  28Oct11         hindent= default value changed from 1 to 0 (v10.23)
/ rrb  30Oct11         style3lbl now automatically generated if blank (v10.24)
/ rrb  31Oct11         Incorrect (pcpttab) changed to (npcttab) and bug with
/                      style3 "repeat" use fixed (v10.25)
/ rrb  03Nov11         "repeat" use bug refix (v10.26)
/ rrb  03Nov11         99 defaults for trtord= and trttotval= changed so that it
/                      uses what is defined to _trttotstr_ as this was causing a
/                      problem for character treatment arms. nlen= and nevlen=
/                      values will now be corrected if too small (v10.27)
/ rrb  12Dec11         Bug with setting style 3 label fixed (v10.28)
/ rrb  26Dec11         Header updated to explain that style=3 currently only
/                      works correctly for Western character sets.
/ rrb  30Dec11         msglevel= processing changed (v10.29)
/===============================================================================
/ Copyright (C) 2011, Roland Rashleigh-Berry. Use of this software is by license
/ only commencing 01 Jan 2012 although permission is granted to use these macros
/ for educational or demonstration purposes and by drug regulatory agencies.
/
/ Users should ensure this software is suitable for the purpose to which it is 
/ put and to provide adequate checks on the accuracy of any values produced as
/ no guarantee can be given that the results displayed by this software are as
/ expected and no liability is accepted for any damage caused through use of any
/ incorrect output produced. Only use this software if you agree to these terms.
/=============================================================================*/

%put MACRO CALLED: npcttab v10.29;

%macro npcttab(dsin=,
           msglevel=X,
             double=no,
            midskip=yes,
           highskip=no,
              dsall=,
          dsallonly=no,
          alllowlvl=no,
              dspop=_popfmt,
           uniqueid=,
             trtvar=,
             trtfmt=,
         filtercode=,
           extmacro=,
      odsescapechar="°",
           trtlabel=,
        odstrtlabel=,
            topline=yes,
           toplabel=,
            spacing=2,
           trtspace=4,
             byvars=,
            highlvl=,
         highlvlord=,
             pageon=,
             midlvl=,
          midlvlord=,
             lowlvl=,
          lowlvlord=,
            lowlvlw=0,
            style3w=0,
               nlen=3,
              style=1,
           usecolon=yes,
           pageline=no,
          pageline1=,
          pageline2=,
          pageline3=,
          pageline4=,
          pageline5=,
          pageline6=,
          pageline7=,
          pageline8=,
          pageline9=,
         pageline10=,
         pageline11=,
         pageline12=,
         pageline13=,
         pageline14=,
         pageline15=,
          pagemacro=,
            endline=no,
           endline1=,
           endline2=,
           endline3=,
           endline4=,
           endline5=,
           endline6=,
           endline7=,
           endline8=,
           endline9=,
          endline10=,
          endline11=,
          endline12=,
          endline13=,
          endline14=,
          endline15=,
           endmacro=,
              trtw1=,
              trtw2=,
              trtw3=,
              trtw4=,
              trtw5=,
              trtw6=,
              trtw7=,
              trtw8=,
              trtw9=,
             trtw10=,
             trtw11=,
             trtw12=,
             trtw13=,
             trtw14=,
             trtw15=,
             trtw16=,
             trtw17=,
             trtw18=,
             trtw19=,
             trtsp1=,
             trtsp2=,
             trtsp3=,
             trtsp4=,
             trtsp5=,
             trtsp6=,
             trtsp7=,
             trtsp8=,
             trtsp9=,
            trtsp10=,
            trtsp11=,
            trtsp12=,
            trtsp13=,
            trtsp14=,
            trtsp15=,
            trtsp16=,
            trtsp17=,
            trtsp18=,
            trtsp19=,
           trtalign=c,
             indent=3,
            hindent=0,
         highlvllbl=,
          midlvllbl=,
          lowlvllbl=,
          style3lbl=,
             trtord=,
          trttotval=,
              total=yes,
          anylowlvl="ANY AE",
          toplowlvl=,
            anywhen=before,
         droplowlvl=,
              split=@,
           headskip=yes,
          spantotal=yes,
             minpct=,
           mincount=,
          minpctany=,
        mincountany=,
              print=yes,
             events=no,
             nevlen=,
              dsout=_npcttab,
         trtvarlist=,
             pcttrt=,
            pvalues=no,
           pvalcolw=8,
            usetest=,
           pvalkeep=,
            pvalvar=_pvalue,
            pvalfmt=p63val.,
            pvalstr=TRT9999,
            pvallbl="p-value",
        pvaltrtlist=,
           fisherid=^,
            chisqid=~,
            trendid=,
             nodata=nodata,
           pgbrkpos=,
            pctsign=no,
            pctwarn=yes,
             pctfmt=5.1,
        pctcompress=no,
            npctord=npct,
             odsrtf=,
            odshtml=,
         odshtmlcss=,
             odscsv=,
             odspdf=,
         odslisting=,
           odsother=,
            tfmacro=,
         odstfmacro=,
          eventsort=yes,
           debug=no,
      keepmidlvlmin=no,
            dsdenom=,
          denomshow=yes,
            byrowvar=,
            byroword=,
          byrowlabel=" ",
          byrowalign=,
            byrowfmt=,
              byroww=,
           byrow2var=,
           byrow2ord=,
         byrow2label=" ",
         byrow2align=,
           byrow2fmt=,
             byrow2w=,
            spanrows=yes,
     font_face_stats=times,
   font_weight_stats=medium,
     font_face_other=times,
    font_style_other=roman,
   font_weight_other=bold,
       report_border=no,
       header_border=no,
       column_border=no,
        lines_border=no,
               rules=none,
         cellspacing=0,
         cellpadding=0,
      outputwidthpct=calc,
   background_header=white,
   foreground_header=black,
    background_stats=white,
    foreground_stats=black,
    background_other=white,
    foreground_other=black,
           header_ul=yes,
           report_ul=yes,
           report_ol=yes,
              linepx=1,
             linecol=black,
            compskip=yes,
         compskippos=after,
         compskip_ul=no,
          compskippx=1,
         compskipcol=black,
       odsprerepcode=,
      odspostrepcode=,
             dsparam=,
       listallparams=no,
                asis=off
           );


  %local parmlist;


  %*- get a list of parameters for this macro -;
  %let parmlist=%mvarlist(npcttab);

  %*- remove the macro variable name "parmlist" from this list -;
  %let parmlist=%removew(&parmlist,parmlist);


  %local errflag err wrn ls i totvar strlen numtrt trtwidth startcol
         repwidth rest trtinlist val var pvalds pvalvar cwidth cwidths
         fwidth fwidths pcttrtlist newmidlvl newhighlvl newlowlvl
         indentmid indentlow highfmt midfmt lowfmt center pctnfmt
         denomsortvars pctaction varlist2 badvars suppvars savopts
         pagevar pagevar2 dsallmiss maxcnt maxev;

  %let err=ERR%str(OR);
  %let wrn=WAR%str(NING);
  %let errflag=0;

  %if not %length(&msglevel) %then %let msglevel=X;
  %let msglevel=%upcase(%substr(&msglevel,1,1));
  %if "&msglevel" NE "N" and "&msglevel" NE "I" %then %let msglevel=X;

  %let savopts=%sysfunc(getoption(msglevel,keyword)) %sysfunc(getoption(notes,keyword));
  %if "&msglevel" EQ "N" or "&msglevel" EQ "I" %then %do;
    options msglevel=&msglevel;
  %end;
  %else %do;
    options nonotes;
  %end;



             /*-----------------------------------------*
                      Parameter dataset handling
              *-----------------------------------------*/


  %if %length(&dsparam) %then %do;

    *-- handle possible dataset modifiers --;
    data _dsparam;
      set &dsparam;
    run;
 
    %if %hasvarsc(_dsparam,name value) %then %do;
      *-- we have a Name-Value pair dataset so transpose it to a "flat" dataset --;
      proc transpose data=_dsparam(keep=name value) out=_dsparam(drop=_name_);
        var value;
        id name;
      run;
 
      %if %varnum(_dsparam,_label_) %then %do;
        *-- drop the _label_ --;
        data _dsparam,
          set _dsparam:
          drop _label_;
        run;
      %end;
    %end;

    %if %nlobs(_dsparam) NE 1 %then %do;
      %let errflag=1;
      %put &err: (npcttab) The parameter dataset dsparam=&dsparam should have one;
      %put &err: (npcttab) observation but this dataset has %nlobs(_dsparam) observations.;
      %put &err: (npcttab) Checking of this dataset will continue but it can not be used.;
      %put;
    %end;

    %let varlist2=%varlistn(_dsparam);
    %if %length(&varlist2) %then %do;
      %let errflag=1;
      %put &err: (npcttab) Numeric variables are not allowed in the parameter dataset ;
      %put &err: (npcttab) dsparam=&dsparam but the following numeric variables exist:;
      %put &err: (npcttab) &varlist2;
      %put;
    %end;

    %if %varnum(_dsparam,dsparam) %then %do;
      %let errflag=1;
      %put &err: (npcttab) The variable DSPARAM is present in the parameter dataset;
      %put &err: (npcttab) dsparam=&dsparam but use of this variable inside a;
      %put &err: (npcttab) parameter dataset is not allowed.;
      %put;
    %end;

    proc contents noprint data=_dsparam out=_unicont(keep=name);
    run;

    data _null_;
      length badvars $ 2000;
      retain badvars ;
      set _unicont end=last;
      name=upcase(name);
      if name not in (%quotelst(&parmlist)) then badvars=trim(badvars)||" "||name;
      if last then call symput('badvars',trim(left(badvars)));
    run;

    %if %length(&badvars) %then %do;
      %let errflag=1;
      %put &err: (npcttab) The following list of variables in dsparam=&dsparam;
      %put &err: (npcttab) do not match any of the macro parameter names so the;
      %put &err: (npcttab) parameter dataset will not be used:;
      %put &err: (npcttab) &badvars;
      %put;
    %end;

    proc datasets nolist;
      delete _unicont;
    run;
    quit;

    %if &errflag %then %goto exit;

    *- the parameter dataset is good so call symput all the variables -;
    data _null_;
      set _dsparam;
      array _char {*} _character_;
      length __y $ 32;
      do __i=1 to dim(_char);
        __y=vname(_char(__i));
        call symput(__y,trim(left(_char(__i))));
      end;
    run;

    %let varlist2=%varlist(_dsparam);

    proc datasets nolist;
      delete _dsparam;
    run;
    quit;

    %put MSG: (npcttab) The following macro parameters and their values were;
    %put MSG: (npcttab) set as the result of use of the dsparam=&dsparam;
    %put MSG: (npcttab) parameter dataset:;
    %mvarvalues(&varlist2);
    %put;

  %end;



      /*-----------------------------*
           check parameter settings
       *-----------------------------*/ 


  %global _strlen_;

  %if not %length(&trtord) %then %let trtord=&_trttotstr_;
  %if not %length(&trttotval) %then %let trttotval=&_trttotstr_;

  %if not %length(&midskip) %then %let midskip=yes;
  %let midskip=%upcase(%substr(&midskip,1,1));

  %if not %length(&highskip) %then %let highskip=no;
  %let highskip=%upcase(%substr(&highskip,1,1));

  %if not %length(&dsallonly) %then %let dsallonly=no;
  %let dsallonly=%upcase(%substr(&dsallonly,1,1));

  %if not %length(&alllowlvl) %then %let alllowlvl=no;
  %let alllowlvl=%upcase(%substr(&alllowlvl,1,1));

  %if not %length(&indent) %then %let indent=3;
  %if not %length(&hindent) %then %let hindent=0;

  %if %length(%sysfunc(compress(&indent,1234567890))) %then %do;
    %put &err: (npcttab) Expecting a positive interger for indent=&indent;
    %let errflag=1;
  %end;
  %if %length(%sysfunc(compress(&hindent,1234567890))) %then %do;
    %put &err: (npcttab) Expecting a positive interger for hindent=&hindent;
    %let errflag=1;
  %end;

  %if not %length(&trtalign) %then %let trtalign=c;
  %let trtalign=%upcase(%substr(&trtalign,1,1));
  %if &trtalign=C %then %let trtalign=center;
  %else %if &trtalign=R %then %let trtalign=right;
  %else %let trtalign=left;

  %if not %length(&pageline) %then %let pageline=no;
  %let pageline=%upcase(%substr(&pageline,1,1));
 
  %if not %length(&endline) %then %let endline=no;
  %let endline=%upcase(%substr(&endline,1,1));

  %if not %length(&double) %then %let double=no;
  %let double=%upcase(%substr(&double,1,1));

  %if not %length(&npctord) %then %let npctord=npct;
  %let npctord=%upcase(%substr(&npctord,1,1));

  %if not %length(&compskip) %then %let compskip=yes;
  %let compskip=%upcase(%substr(&compskip,1,1));

  %if not %length(&compskippos) %then %let compskippos=after;

  %if not %length(&asis) %then %let asis=on;

  %if not %length(&pvalcolw) %then %let pvalcolw=8;

  %if not %length(&listallparams) %then %let listallparams=no;
  %let listallparams=%upcase(%substr(&listallparams,1,1));

  %let suppvars=&highlvl &midlvl &lowlvl;

  %let pagevar=;  %*- this parameter disabled with v10.11 -;
  %if NOT %length(&pagevar) %then %do;
    %if NOT %length(&pageon) %then %do;
      %let pagevar2=_PAGE_;
    %end;
    %else %do;
      %let pagevar=_page;
      %let pagevar2=_page;
    %end;
  %end;
  %else %let pagevar2=&pagevar;

  %if not %length(&odstrtlabel) %then %let odstrtlabel=&trtlabel;

  %if not %length(&font_face_stats) %then %let font_face_stats=times;
  %if not %length(&font_weight_stats) %then %let font_weight_stats=medium;
  %if not %length(&font_face_other) %then %let font_face_other=times;
  %if not %length(&font_style_other) %then %let font_style_other=roman;
  %if not %length(&font_weight_other) %then %let font_weight_other=bold;

  %if not %sysfunc(indexw(LIGHT MEDIUM BOLD,%upcase(&font_weight_stats))) %then %do;
    %let errflag=1;
    %put &err: (unicatrep) Font weights can only be light, medium or bold. You put font_weight_stats=&font_weight_stats;
  %end;

  %if not %sysfunc(indexw(LIGHT MEDIUM BOLD,%upcase(&font_weight_other))) %then %do;
    %let errflag=1;
    %put &err: (unicatrep) Font weights can only be light, medium or bold. You put font_weight_other=&font_weight_other;
  %end;

  %if not %sysfunc(indexw(ITALIC ROMAN SLANT,%upcase(&font_style_other))) %then %do;
    %let errflag=1;
    %put &err: (unicatrep) Font styles can only be italic, roman or slant. You put font_style_other=&font_style_other;
  %end;


  %if not %length(&byvars) %then %let byvars=&byroword &byrowvar &byrow2ord &byrow2var;

  %if not %length(&byrowalign) %then %let byrowalign=left;
  %else %if %upcase(%substr(&byrowalign,1,1)) EQ L %then %let byrowalign=left;
  %else %if %upcase(%substr(&byrowalign,1,1)) EQ C %then %let byrowalign=center;
  %else %if %upcase(%substr(&byrowalign,1,1)) EQ R %then %let byrowalign=right;
  %else %let byrowalign=left;

  %if not %length(&byrow2align) %then %let byrow2align=left;
  %else %if %upcase(%substr(&byrow2align,1,1)) EQ L %then %let byrow2align=left;
  %else %if %upcase(%substr(&byrow2align,1,1)) EQ C %then %let byrow2align=center;
  %else %if %upcase(%substr(&byrow2align,1,1)) EQ R %then %let byrow2align=right;
  %else %let byrow2align=left;

  %if not %length(&byrowlabel) %then %let byrowlabel=" ";
  %if not %length(&byrow2label) %then %let byrow2label=" ";

  %if not %length(&style) %then %let style=1;

  %if not (&style=1 or &style=2 or &style=3) %then %do;
    %let errflag=1;
    %put &err: (npcttab) Style must be 1, 2 or 3 (unquoted) but you have style=&style;
  %end;


  %if %length(&dsdenom) %then %let denomsortvars=%sortedby(&dsdenom);

  %if not %length(&pctcompress) %then %let pctcompress=no;
  %let pctcompress=%upcase(%substr(&pctcompress,1,1));

  %if not %length(&denomshow) %then %let denomshow=yes;
  %let denomshow=%upcase(%substr(&denomshow,1,1));

  %if not %length(&keepmidlvlmin) %then %let keepmidlvlmin=yes;
  %let keepmidlvlmin=%upcase(%substr(&keepmidlvlmin,1,1));

  %if not %length(&debug) %then %let debug=no;
  %let debug=%upcase(%substr(&debug,1,1));
  %if "&debug" EQ "Y" %then %do;
    options mprint;
  %end;

  %if not %length(&eventsort) %then %let eventsort=no;
  %let eventsort=%upcase(%substr(&eventsort,1,1));

  %if not %length(&pctsign) %then %let pctsign=no;
  %let pctsign=%upcase(%substr(&pctsign,1,1));

  %if not %length(&pctwarn) %then %let pctwarn=yes;
  %let pctwarn=%upcase(%substr(&pctwarn,1,1));

  %if "&pctwarn" EQ "N" %then %do;
    %put &wrn: (npcttab) Percent > 100.01 checks disabled;
  %end;

  %*- if a high level variable specified but no mid one -;
  %*- then shift high level values down to mid ones.    -;
  %if %length(&highlvl) and not %length(&midlvl) %then %do;
    %let midlvl=&highlvl;
    %let midlvlord=&highlvlord;
    %let midlvllbl=&highlvllbl;
    %let highlvl=;
    %let highlvlord=;
    %let highlvllbl=;
  %end;


  %*- set indentation values -;
  %if %length(&midlvl) %then %let indentlow=&indent;
  %else %let indentlow=0;
  %if %length(&highlvl) %then %let indentmid=&indent;
  %else %let indentmid=0;


  %if not %length(&pctfmt) %then %let pctfmt=5.1;
  %let pctnfmt=%substr(&pctfmt,%verifyb(&pctfmt,0123456789.)+1);

  %if not %length(&pctnfmt) or not %index(&pctnfmt,.) %then %do;
    %let errflag=1;
    %put &err: (npcttab) Format supplied to pctfmt=&pctfmt not valid;
  %end;
  %else %if "%substr(&pctnfmt,1,1)" EQ "." %then %do;
    %let errflag=1;
    %put &err: (npcttab) No format length supplied to pctfmt=&pctfmt;
  %end;

  %if not %length(&dsin) %then %do;
    %let errflag=1;
    %put &err: (npcttab) No input dataset name specified for dsin=;
  %end;

  %if not %length(&dspop) %then %do;
    %let errflag=1;
    %put &err: (npcttab) No population dataset specified for dspop=;
  %end;

  %if not %length(&lowlvl) %then %do;
    %let errflag=1;
    %put &err: (npcttab) No low level variable specified for lowlvl=;
  %end;

  %if not %length(&trtord) %then %do;
    %let errflag=1;
    %put &err: (npcttab) No treatment arm ordering value specified for trtord=;
  %end;

  %if not %length(&trttotval) %then %do;
    %let errflag=1;
    %put &err: (npcttab) No treatment arm total value specified for trttotval=;
  %end;

  %if not %length(&anylowlvl) %then %do;
    %let errflag=1;
    %put &err: (npcttab) No label specified for the combination of all low level terms for anylowlvl=;
  %end;

  %if not %length(&minpct.&mincount.&minpctany.&mincountany) %then %let anywhen=before;
  %if not %length(&anywhen) %then %let anywhen=before;
  %let anywhen=%upcase(%substr(&anywhen,1,1));

  %if ("&anywhen" NE "B" and "&anywhen" NE "A") %then %do;
    %let errflag=1;
    %put &err: (npcttab) You must set anywhen=before or anywhen=after;
  %end;

  %if not %length(&pvaltrtlist) %then %let pvaltrtlist=ne &trttotval;
  %else %let pvaltrtlist=in (&pvaltrtlist);

  %if not %length(&rules) %then %let rules=none;
  %if not %sysfunc(indexw(NONE ALL COLS ROWS GROUPS,%upcase(&rules))) %then %do;
    %let errflag=1;
    %put &err: (npcttab) Rules parameter value can only be none, all, cols, rows, groups. You put rules=&rules;
  %end;

  %if &errflag %then %goto exit;



      /*-----------------------------*
           check parameter quoting
       *-----------------------------*/ 

  %*- make sure the following is quoted -;


  %let anylowlvl="%unquote(%qdequote(&anylowlvl))";


  %*- make sure these are NOT quoted -;

  %if %length(&highlvllbl) %then %let highlvllbl=%unquote(%qdequote(&highlvllbl));

  %if %length(&midlvllbl) %then %let midlvllbl=%unquote(%qdequote(&midlvllbl));

  %if %length(&lowlvllbl) %then %let lowlvllbl=%unquote(%qdequote(&lowlvllbl));


  %if &style EQ 3 and not %length(&style3lbl) %then %do;
    %let style3lbl=;
    %if %length(&highlvl) %then %do;
      %if %length(&highlvllbl) %then %let style3lbl="&highlvllbl";
      %else %let style3lbl="%varlabel(%scan(&dsin,1,%str(%()),&highlvl)";
    %end;
    %if %length(&midlvl) %then %do;
      %if %length(&midlvllbl) %then %let style3lbl=&style3lbl "&midlvllbl";
      %else %do;
        %if &indentmid EQ 0 %then %let style3lbl=&style3lbl
"%varlabel(%scan(&dsin,1,%str(%()),&midlvl)";
        %else %let style3lbl=&style3lbl
"%sysfunc(repeat(%str( ),%eval(&indentmid-1)))%varlabel(%scan(&dsin,1,%str(%()),&midlvl)";
      %end;
    %end;
    %if %length(&lowlvl) %then %do;
      %if %length(&lowlvllbl) %then %let style3lbl=&style3lbl "&lowlvllbl";
      %else %do;
        %if %eval(&indentmid+&indentlow) EQ 0 %then %let style3lbl=&style3lbl
"%varlabel(%scan(&dsin,1,%str(%()),&lowlvl)";
        %else %let style3lbl=&style3lbl
"%sysfunc(repeat(%str( ),%eval(&indentmid+&indentlow-1)))%varlabel(%scan(&dsin,1,%str(%()),&lowlvl)";
      %end;
    %end;
  %end;


        /*-----------------------------*
            assign parameter defaults
         *-----------------------------*/

  %let ls=%sysfunc(getoption(linesize));
  %let center=%sysfunc(getoption(center));

  %if not %length(&cellspacing) %then %let cellspacing=0;


  %if not %length(&report_border) %then %let report_border=no;
  %if not %length(&header_border) %then %let header_border=no;
  %if not %length(&column_border) %then %let column_border=no;
  %if not %length(&lines_border) %then %let lines_border=no;

  %let report_border=%upcase(%substr(&report_border,1,1));
  %let header_border=%upcase(%substr(&header_border,1,1));
  %let column_border=%upcase(%substr(&column_border,1,1));
  %let lines_border=%upcase(%substr(&lines_border,1,1));

  %if not %length(&uniqueid) %then %do;
    %let uniqueid=&_uniqueid_;
    %put NOTE: (npcttab) Defaulting to uniqueid=&uniqueid;
    %put;
  %end;

  %if not %length(&trtvar) %then %do;
    %let trtvar=&_trtvar_;
    %put NOTE: (npcttab) Defaulting to trtvar=&trtvar;
    %put;
  %end;

  %if not %length(&dsout) %then %let dsout=_npcttab;

  %if not %length(&toplowlvl) %then %let toplowlvl=&anylowlvl;

  %if not %length(&trtfmt) %then %do;
    %if %length(&pcttrt) %then %let trtfmt=&_poptfmt_;
    %else %let trtfmt=&_popfmt_;
  %end;

  %if not %length(&spacing) %then %let spacing=2;

  %if not %length(&trtspace) %then %let trtspace=4;

  %if not %length(&lowlvlw) %then %let lowlvlw=0;

  %if not %length(&events) %then %let events=no;
  %let events=%upcase(%substr(&events,1,1));


  %if not %length(&spanrows) %then %let spanrows=yes;
  %let spanrows=%upcase(%substr(&spanrows,1,1));
  %if "&spanrows" EQ "Y" and 
    %sysevalf( %scan(&sysver,1,.).%scan(&sysver,2,.) GE 9.2 ) 
    %then %let spanrows=spanrows;
  %else %let spanrows=;

  %if not %length(&linepx) %then %let linepx=1;

  %if not %length(&compskip_ul) %then %let compskip_ul=no;
  %if not %length(&header_ul) %then %let header_ul=yes;
  %if not %length(&report_ul) %then %let report_ul=yes;
  %if not %length(&report_ol) %then %let report_ol=yes;

  %let compskip_ul=%upcase(%substr(&compskip_ul,1,1));
  %let header_ul=%upcase(%substr(&header_ul,1,1));
  %let report_ul=%upcase(%substr(&report_ul,1,1));
  %let report_ol=%upcase(%substr(&report_ol,1,1));


  %let cwidths=&_trtcwidths_;
  %if "&total" EQ "Y" %then %let cwidths=&cwidths &_trttotcwidth_;

  %let fwidths=&_trtfwidths_;
  %if "&total" EQ "Y" %then %let fwidths=&fwidths &_trttotfwidth_;

  %*- if percentages shown for certain trt only then make a list of the vars -;
  %if %length(&pcttrt) %then %do;
    %do i=1 %to %words(&pcttrt);
      %let pcttrtlist=&pcttrtlist &_trtpref_%sysfunc(compress(%scan(&pcttrt,&i,%str( )),%str(%'%")));
    %end;
  %end;



  %if not %length(&topline) %then %let topline=yes;
  %let topline=%substr(%upcase(&topline),1,1);

  %if not %length(&split) %then %let split=@;


  %if not %length(&headskip) %then %let headskip=yes;
  %let headskip=%upcase(%substr(&headskip,1,1));
  %if "&headskip" EQ "Y" %then %let headskip=headskip;
  %else %let headskip=;


  %if not %length(&total) %then %let total=yes;
  %let total=%substr(%upcase(&total),1,1);

  %if not %length(&spantotal) %then %let spantotal=yes;
  %let spantotal=%upcase(%substr(&spantotal,1,1));

  %if not %length(&print) %then %let print=yes;
  %let print=%upcase(%substr(&print,1,1));

  %if %length(&highlvllbl) %then %let highlvllbl=%unquote(%qdequote(&highlvllbl));
  %if %length(&midlvllbl) %then %let midlvllbl=%unquote(%qdequote(&midlvllbl));
  %if %length(&lowlvllbl) %then %let lowlvllbl=%unquote(%qdequote(&lowlvllbl));

  %if not %length(&pvalues) %then %let pvalues=no;
  %let pvalues=%upcase(%substr(&pvalues,1,1));

  %if not %length(&pgbrkpos) %then %let pgbrkpos=before;


  %if "&listallparams" EQ "Y" %then %do;
    %put MSG: (npcttab) The complete list of macro parameters and their values;
    %put MSG: (npcttab) that this macro has put into effect is as follows:;
    %mvarvalues(&parmlist);
    %put;
  %end;


      /*-----------------------------*
          dsall initial processing
       *-----------------------------*/

  %if %length(&dsall) or "&alllowlvl" EQ "Y" %then %do;

    *-- Find out if we are missing the high or mid level variables in the      --;
    *-- dsall dataset and add them from the input dataset if that is the case. --;

    %let dsallmiss=;

    %if %length(&dsall) %then %do;
      data _npctdsall;
        set &dsall;
      run;
    %end;
    %else %do;
      %let dsall=_npctdsall;
      proc summary nway missing data=&dsin;
        class &lowlvl;
        output out=_npctdsall(drop=_type_ _freq_);
      run;
    %end;

    %if %length(&highlvl) %then %do;
      %if not %varnum(_npctdsall,&highlvl) and not %varnum(_npctdsall,&midlvl) %then %let dsallmiss=&highlvl &midlvl;
      %else %if not %varnum(_npctdsall,&highlvl) %then %let dsallmiss=&highlvl;
    %end;
    %else %if %length(&midlvl) %then %do;
      %if not %varnum(_npctdsall,&midlvl) %then %let dsallmiss=&midlvl;
    %end;

    %if %length(&dsallmiss) %then %do;
      proc summary nway missing data=&dsin;
        class &dsallmiss;
        output out=_npctmiss(drop=_type_ _freq_);
      run;

      data _npctdsall;
        set _npctmiss;
        do __i=1 to __nobs;
          set _npctdsall point=__i nobs=__nobs;
          output;
        end;
      run;

      proc sort data=_npctdsall;
        by &highlvl &midlvl &lowlvl;
      run;


      %if "&debug" EQ "Y" %then %do;
      %end;
      %else %do;
        proc datasets nolist;
          delete _npctmiss;
        run;
        quit;
      %end;
    %end;

  %end;



        /*-----------------------------*
              set up new variables
         *-----------------------------*/

  *- This version of npcttab allows you to specify formatted variables  -;
  *- which it will automatically resolve into its uncoded form in a new -;
  *- variable and this is where the processing is done. -;

  *- keep only the needed variables -;
  data _npctdsin;
    set &dsin;
    keep &byvars &trtvar &uniqueid 
         &highlvlord &highlvl &midlvlord &midlvl &lowlvlord &lowlvl;
  run;

  proc sort data=_npctdsin;
    by &highlvl &midlvl &lowlvl;
  run;

  %if %length(&dsall) and "&dsallonly" EQ "Y" %then %do;
    data _npctdsin;
      merge _npctdsall(in=_all) _npctdsin(in=_data);
      by &highlvl &midlvl &lowlvl;
      if _all and _data;
    run;
  %end;

  %*- default the new variable names to the old ones -;
  %let newhighlvl=&highlvl;
  %let newmidlvl=&midlvl;
  %let newlowlvl=&lowlvl;


  %*- find out if a user format is applied to these variables -;
  %if %length(&highlvl) %then %do;
    %let highfmt=%varfmt(_npctdsin,&highlvl);
    %if not %index("" %sysfmtlist, "%sysfunc(compress(&highfmt,1234567890.))") 
      %then %let newhighlvl=__highlvl;
  %end;
  %if %length(&midlvl) %then %do;
    %let midfmt=%varfmt(_npctdsin,&midlvl);
    %if not %index("" %sysfmtlist, "%sysfunc(compress(&midfmt,1234567890.))")
      %then %let newmidlvl=__midlvl;
  %end;
  %if %length(&lowlvl) %then %do;
    %let lowfmt=%varfmt(_npctdsin,&lowlvl);
    %if not %index("" %sysfmtlist, "%sysfunc(compress(&lowfmt,1234567890.))")
      %then %let newlowlvl=__lowlvl;
  %end;




  *- do for input data -;
  data _npctdsin;
    %*- decode any of these variables if need be -;
    %if ("&newhighlvl" NE "&highlvl") or ("&newmidlvl" NE "&midlvl") 
     or ("&newlowlvl" NE "&lowlvl") %then %do;
      length
      %if ("&newhighlvl" NE "&highlvl") %then &newhighlvl ;
      %if ("&newmidlvl" NE "&midlvl") %then &newmidlvl ;
      %if ("&newlowlvl" NE "&lowlvl") %then &newlowlvl ;
      $ 256; *-was 200 -;
    %end;
    set _npctdsin;
    %if ("&newhighlvl" NE "&highlvl") %then %do;
      &newhighlvl=put(&highlvl,&highfmt.);
    %end;
    %if ("&newmidlvl" NE "&midlvl") %then %do;
      &newmidlvl=put(&midlvl,&midfmt.);
    %end;
    %if ("&newlowlvl" NE "&lowlvl") %then %do;
      &newlowlvl=put(&lowlvl,&lowfmt.);
    %end;
    keep &byvars &trtvar &uniqueid 
         &highlvlord &newhighlvl &midlvlord &newmidlvl &lowlvlord &newlowlvl;
    *- cancel all formats except for by variables -;
    format &trtvar &uniqueid &highlvlord &newhighlvl &midlvlord &newmidlvl
           &lowlvlord &newlowlvl;
  run;



  %if %length(&dsall) %then %do;
    *- do for dsall data -;
    data _npctdsall;
      %*- decode any of these variables if need be -;
      %if ("&newhighlvl" NE "&highlvl") or ("&newmidlvl" NE "&midlvl") 
       or ("&newlowlvl" NE "&lowlvl") %then %do;
        length
        %if ("&newhighlvl" NE "&highlvl") %then &newhighlvl ;
        %if ("&newmidlvl" NE "&midlvl") %then &newmidlvl ;
        %if ("&newlowlvl" NE "&lowlvl") %then &newlowlvl ;
        $ 256; *-was 200 -;
      %end;
      set _npctdsall;
      %if ("&newhighlvl" NE "&highlvl") %then %do;
        &newhighlvl=put(&highlvl,&highfmt.);
      %end;
      %if ("&newmidlvl" NE "&midlvl") %then %do;
        &newmidlvl=put(&midlvl,&midfmt.);
      %end;
      %if ("&newlowlvl" NE "&lowlvl") %then %do;
        &newlowlvl=put(&lowlvl,&lowfmt.);
      %end;
      *- cancel all the formats -;
      format &highlvlord &newhighlvl &midlvlord &newmidlvl 
             &lowlvlord &newlowlvl;
    run;
  %end;




      /*-----------------------------*
            add dummy variables
       *-----------------------------*/

  *- if all level variables not present then set up dummy variables -;

  %if not %length(&highlvl) or not %length(&midlvl) %then %do;
    %if not %length(&highlvl) %then %let newhighlvl=_dummyhigh;
    %if not %length(&midlvl) %then %let newmidlvl=_dummymid;

    data _npctdsin;
      %if not %length(&highlvl) %then %do;
        retain &newhighlvl "DUMMY";
      %end;
      %if not %length(&midlvl) %then %do;
        retain &newmidlvl "DUMMY";
      %end;
      set _npctdsin;
    run;

    %if %length(&dsall) %then %do;
      data _npctdsall;
        %if not %length(&highlvl) %then %do;
          retain &newhighlvl "DUMMY";
        %end;
        %if not %length(&midlvl) %then %do;
          retain &newmidlvl "DUMMY";
        %end;
        set _npctdsall;
      run;
    %end;

  %end;



      /*-----------------------------*
          add total treatment group
       *-----------------------------*/

  *- This is calculated even if not going to be displayed since it is  -;
  *- possibly to be used as the treatment arm to base the ordering by. -;
  data _npctdsin;
    set _npctdsin;
    output;
    &trtvar=&trttotval;
    output;
  run;



      /*-----------------------------*
          in case there is no data
       *-----------------------------*/

  %if %attrn(_npctdsin,nobs) EQ 0 and %length(&nodata) %then %do;
    %&nodata
    %goto skip;
  %end;



      /*-----------------------------*
         sort ready for filter step
       *-----------------------------*/

  *- sort ready for the filter step if needed -;
  proc sort data=_npctdsin;
    by &byvars &newhighlvl &newmidlvl &newlowlvl &trtvar;
  run;

  %goto skipfilter;



      /*-----------------------------*
          filter for anywhen=after
       *-----------------------------*/

  %filter:

  *- If anywhen=after is set then only keep those terms that will -;
  *- be displayed and repeat all the steps to calculate the count -;
  *- and percentage for anylowlvl plus the default ordering variables. -;
  data _npctdsin;
    merge _npctfilter(in=_filter) _npctdsin(in=_data);
    by &byvars &newhighlvl &newmidlvl &newlowlvl;
    if _filter and _data;
  run;



  %skipfilter:



      /*-----------------------------*
            summarise for events
       *-----------------------------*/


  *- low level events -;
  proc summary nway missing data=_npctdsin;
    by &byvars;
    class &highlvlord &newhighlvl &midlvlord &newmidlvl &lowlvlord &newlowlvl &trtvar;
    output out=_npctevlowlvl(drop=_type_ rename=(_freq_=_events));
  run;

  data _npctevlowlvl;
    set _npctevlowlvl;
    _lowlvlevord=1/_events;
  run;

  
  *- mid level events -;
  proc summary nway missing data=_npctdsin;
    by &byvars;
    class &highlvlord &newhighlvl &midlvlord &newmidlvl &trtvar;
    output out=_npctevmidlvl(drop=_type_ rename=(_freq_=_events));
  run;

  proc sql noprint;
    select max(_events) into: maxev separated by " " from _npctevmidlvl;
  quit;
  %if &nevlen LT %length(&maxev) %then %let nevlen=%length(&maxev);
 
  data _npctevmidlvl;
    set _npctevmidlvl;
    _midlvlevord=1/_events;
  run;


  *- high level events -;
  proc summary nway missing data=_npctdsin;
    by &byvars;
    class &highlvlord &newhighlvl &trtvar;
    output out=_npctevhighlvl(drop=_type_ rename=(_freq_=_events));
  run;

  data _npctevhighlvl;
    set _npctevhighlvl;
    _highlvlevord=1/_events;
  run;





      /*-----------------------------*
            get rid of duplicates
       *-----------------------------*/

  *- This part gets ready for the unique subject counts by dropping -;
  *- duplicate records for the subjects. -;

  proc sort nodupkey data=_npctdsin
                      out=_npctlowlvl;
    by &byvars &trtvar &uniqueid 
       &highlvlord &newhighlvl &midlvlord &newmidlvl &lowlvlord &newlowlvl;
  run;

  proc sort nodupkey data=_npctdsin(drop=&lowlvlord &newlowlvl)
                      out=_npctmidlvl;
    by &byvars &trtvar &uniqueid 
       &highlvlord &newhighlvl &midlvlord &newmidlvl;
  run;

  proc sort nodupkey data=_npctdsin(drop=&midlvlord &newmidlvl &lowlvlord &newlowlvl)
                      out=_npcthighlvl;
    by &byvars &trtvar &uniqueid 
       &highlvlord &newhighlvl;
  run;



      /*-----------------------------*
        summarise for unique subjects
       *-----------------------------*/

  *- Summarise for unique subjects and put the total in _count. -;
  *- Merge in the event counts. -;

  *- Low Level -;
  proc summary nway missing data=_npctlowlvl;
    by &byvars;
    class &highlvlord &newhighlvl &midlvlord &newmidlvl &lowlvlord &newlowlvl &trtvar;
    output out=_npctlowlvl(drop=_type_ rename=(_freq_=_count));
  run;

  data _npctlowlvl;
    merge _npctevlowlvl _npctlowlvl;
    by &byvars &highlvlord &newhighlvl &midlvlord &newmidlvl &lowlvlord &newlowlvl &trtvar;
  run;



  *- Mid Level -;
  proc summary nway missing data=_npctmidlvl;
    by &byvars;
    class &highlvlord &newhighlvl &midlvlord &newmidlvl &trtvar;
    output out=_npctmidlvl(drop=_type_ rename=(_freq_=_count));
  run;

  proc sql noprint;
    select max(_count) into: maxcnt separated by " " from _npctmidlvl;
  quit;
  %if &nlen LT %length(&maxcnt) %then %let nlen=%length(&maxcnt);

  data _npctmidlvl;
    merge _npctevmidlvl _npctmidlvl;
    by &byvars &highlvlord &newhighlvl &midlvlord &newmidlvl &trtvar;
  run;



  *- High Level -;
  proc summary nway missing data=_npcthighlvl;
    by &byvars;
    class &highlvlord &newhighlvl &trtvar;
    output out=_npcthighlvl(drop=_type_ rename=(_freq_=_count));
  run;

  data _npcthighlvl;
    merge _npctevhighlvl _npcthighlvl;
    by &byvars &highlvlord &newhighlvl &trtvar;
  run;



  data _npcthighlvl;
    set _npcthighlvl;
    _highlvlord=1/_count;
  run;


  *- Set up the anylowlvl term for later inclusion -;
  *- in with the rest of low level terms -;
  data _npctmidlvl;
    length &newlowlvl %varlen(_npctlowlvl,&newlowlvl);
    retain &newlowlvl &anylowlvl;
    set _npctmidlvl;
    _midlvlord=1/_count;
  run;



      /*-----------------------------*
               Add in anylowlvl
       *-----------------------------*/

  *- Note that the anylowlvl term gets added to the _npctlowlvl -;
  *- (and dsall) dataset here. -;

  data _npctlowlvl;
    set _npctmidlvl(drop=_midlvlord _midlvlevord)
        _npctlowlvl;
    _lowlvlord=1/_count;
  run;

  %if %length(&dsall) %then %do;
    data _npctdsall;
      set _npctmidlvl(drop=_midlvlord _midlvlevord)
      _npctdsall;
    run;
  %end;


  *- sort ready for a merge with zero values -;
  proc sort data=_npctlowlvl;
    by &trtvar &byvars &highlvlord &newhighlvl 
       &midlvlord &newmidlvl &lowlvlord &newlowlvl;
  run;



      /*-----------------------------*
             Create a zero grid
       *-----------------------------*/

  %if %length(&dsall) %then %do;
    %zerogrid(zerovar=_count,dsout=_zerogrid,
    var1=&trtvar,ds1=&dspop,
    %if %length(&byvars) %then %do;
      var2=&byvars,ds2=_npctlowlvl,
    %end;
    var3=&highlvlord &newhighlvl &midlvlord &newmidlvl &lowlvlord &newlowlvl,
    ds3=_npctdsall)
  %end;
  %else %do;
    %zerogrid(zerovar=_count,dsout=_zerogrid,
    var1=&trtvar,ds1=&dspop,
    var2=&byvars &highlvlord &newhighlvl &midlvlord &newmidlvl &lowlvlord &newlowlvl,
    ds2=_npctlowlvl)
  %end;

  *- add extra required variables -;
  data _zerogrid;
    retain _events 0 _lowlvlord _lowlvlevord 99;
    set _zerogrid;
  run;



      /*-----------------------------*
         Merge on top of zero values
       *-----------------------------*/

  data _npctlowlvl;
    merge _zerogrid _npctlowlvl;
    by &trtvar &byvars &highlvlord &newhighlvl 
       &midlvlord &newmidlvl &lowlvlord &newlowlvl;
  run;



      /*-----------------------------*
            Work out some lengths
       *-----------------------------*/

  %if not %length(&nlen) %then %let nlen=3;
  %if not %length(&nevlen) %then %let nevlen=&nlen;
  %*- add 3 for the space and round brackets -;
  %let strlen=%eval(&nlen+3);
  %*- add the length of the percent format -;
  %let strlen=%eval(&strlen+%scan(&pctnfmt,1,.));
  %*- add for a space and the event count if that is specified -;
  %if "&events" EQ "Y" %then %let strlen=%eval(&strlen+1+&nevlen);
  %*- if displaying percent sign then add 1 to the length -;
  %if "&pctsign" EQ "Y" %then %let strlen=%eval(&strlen+1);
  %*- if displaying denominator then add more -;
  %if %length(&dsdenom) and "&denomshow" EQ "Y" 
    %then %let strlen=%eval(&strlen+&nlen+3);
  %let _strlen_=&strlen;

  %*- set default lengths for widths -;
  %do i=1 %to 19;
    %if not %length(&&trtw&i) %then %do;
      %let var=%scan(&_trtvarlist_,&i,%str( ));
      %let cwidth=%scan(&cwidths,&i,%str( ));
      %let fwidth=%scan(&fwidths,&i,%str( ));
      %let trtw&i=&strlen;
      %if &cwidth GT &&trtw&i %then %let trtw&i=&cwidth;
      %if %length(&pcttrt) and not %index(%quotelst(%upcase(&pcttrtlist)),"%upcase(&var)") %then %do;
        %let trtw&i=&nlen;
        %if &fwidth GT &&trtw&i %then %let trtw&i=&fwidth;
      %end;
    %end;
  %end;


       /*-----------------------------*
             calculate percentages
        *-----------------------------*/


  %if %length(&dsdenom) %then %do;
    %if not %length(&denomsortvars) %then %do;
      %let denomsortvars=%match(%varlist(_npctlowlvl),%varlist(&dsdenom));
      proc sort data=&dsdenom out=_npctdenom;
        by &denomsortvars;
      run;
    %end;
    %else %do;
      data _npctdenom;
        set &dsdenom;
      run;
    %end;
    proc sort data=_npctlowlvl;
      by &denomsortvars;
    run;
  %end;


  %if "&pctcompress" EQ "Y" %then %let pctaction=compress(put(_pct,&pctfmt));
  %else %let pctaction=put(_pct,&pctfmt);


  *- Merge with the population or the denominator dataset -;
  data _npctlowlvl;
    length _str $ &strlen _idlabel $ 120;
    %if %length(&dsdenom) %then %do;
      merge _npctdenom _npctlowlvl(in=_npct);
      by &denomsortvars;
    %end;
    %else %do;
      merge &dspop _npctlowlvl(in=_npct);
      by &trtvar;
    %end;
    if _npct;
    if _total in (.,0) and _count EQ 0 then do;
      _pct=0;
      _str=" ";
    end;
    else do;
      _pct=100*_count/_total;
      %if "&pctwarn" NE "N" %then %do;
      if _pct GT 100.01 then 
        put "WAR" "NING: (npcttab) _pct GT 100.01 " (_all_) (=);
      %end;
      %if "&events" EQ "Y" %then %do;
        %if "&pctsign" EQ "Y" %then %do;
          %if %length(&dsdenom) and "&denomshow" EQ "Y" %then %do;
            %if "&npctord" EQ "P" %then %do;
              _str=&pctaction||"% ("||put(_count,&nlen..)||' / '||trim(left(put(_total,8.)))||") "||put(_events,&nevlen..);
            %end;
            %else %do;
              _str=put(_count,&nlen..)||' / '||trim(left(put(_total,8.)))||" ("||&pctaction||"%) "||put(_events,&nevlen..);
            %end;
          %end;
          %else %do;
            %if "&npctord" EQ "P" %then %do;
              _str=&pctaction||"% ("||put(_count,&nlen..)||") "||put(_events,&nevlen..);
            %end;
            %else %do;
              _str=put(_count,&nlen..)||" ("||&pctaction||"%) "||put(_events,&nevlen..);
            %end;
          %end;
        %end;
        %else %do;
          %if %length(&dsdenom) and "&denomshow" EQ "Y" %then %do;
            %if "&npctord" EQ "P" %then %do;
              _str=&pctaction||" ("||put(_count,&nlen..)||' / '||trim(left(put(_total,8.)))||") "||put(_events,&nevlen..);
            %end;
            %else %do;
              _str=put(_count,&nlen..)||' / '||trim(left(put(_total,8.)))||" ("||&pctaction||") "||put(_events,&nevlen..);
            %end;
          %end;
          %else %do;
            %if "&npctord" EQ "P" %then %do;
              _str=&pctaction||" ("||put(_count,&nlen..)||") "||put(_events,&nevlen..);
            %end;
            %else %do;
              _str=put(_count,&nlen..)||" ("||&pctaction||") "||put(_events,&nevlen..);
            %end;
          %end;
        %end;
      %end;
      %else %do;
        %if %length(&pcttrt) %then %do;
          %if "&pctsign" EQ "Y" %then %do;
            %if %length(&dsdenom) and "&denomshow" EQ "Y" %then %do;
              %if "&npctord" EQ "P" %then %do;
                if &trtvar in (&pcttrt) then _str=&pctaction||" ("||put(_count,&nlen..)||' / '||trim(left(put(_total,8.)))||")";
              %end;
              %else %do;
                if &trtvar in (&pcttrt) then _str=put(_count,&nlen..)||' / '||trim(left(put(_total,8.)))||" ("||&pctaction||"%)";
              %end;
            %end;
            %else %do;
              %if "&npctord" EQ "P" %then %do;
                if &trtvar in (&pcttrt) then _str=&pctaction||" ("||put(_count,&nlen..)||")";
              %end;
              %else %do;
                if &trtvar in (&pcttrt) then _str=put(_count,&nlen..)||"("||&pctaction||"%)";
              %end;
            %end;
            else _str=put(_count,&nlen..);
          %end;
          %else %do;
            %if %length(&dsdenom) and "&denomshow" EQ "Y" %then %do;
              %if "&npctord" EQ "P" %then %do;
                if &trtvar in (&pcttrt) then _str=&pctaction||" ("||put(_count,&nlen..)||' / '||trim(left(put(_total,8.)))||")";
              %end;
              %else %do;
                if &trtvar in (&pcttrt) then _str=put(_count,&nlen..)||' / '||trim(left(put(_total,8.)))||" ("||&pctaction||")";
              %end;
            %end;
            %else %do;
              %if "&npctord" EQ "P" %then %do;
                if &trtvar in (&pcttrt) then _str=&pctaction||" ("||put(_count,&nlen..)||")";
              %end;
              %else %do;
                if &trtvar in (&pcttrt) then _str=put(_count,&nlen..)||"("||&pctaction||")";
              %end;
            %end;
            else _str=put(_count,&nlen..);
          %end;
        %end;
        %else %do;
          %if "&pctsign" EQ "Y" %then %do;
            %if %length(&dsdenom) and "&denomshow" EQ "Y" %then %do;
              %if "&npctord" EQ "P" %then %do;
                _str=&pctaction||"% ("||put(_count,&nlen..)||' / '||trim(left(put(_total,8.)))||")";
              %end;
              %else %do;
                _str=put(_count,&nlen..)||' / '||trim(left(put(_total,8.)))||" ("||&pctaction||"%)";
              %end;
            %end;
            %else %do;
              %if "&npctord" EQ "P" %then %do;
                _str=&pctaction||"% ("||put(_count,&nlen..)||")";
              %end;
              %else %do;
                _str=put(_count,&nlen..)||" ("||&pctaction||"%)";
              %end;
            %end;
          %end;
          %else %do;
            %if %length(&dsdenom) and "&denomshow" EQ "Y" %then %do;
              %if "&npctord" EQ "P" %then %do;
                _str=&pctaction||" ("||put(_count,&nlen..)||' / '||trim(left(put(_total,8.)))||")";
              %end;
              %else %do;
                _str=put(_count,&nlen..)||' / '||trim(left(put(_total,8.)))||" ("||&pctaction||")";
              %end;
            %end;
            %else %do;
              %if "&npctord" EQ "P" %then %do;
                _str=&pctaction||" ("||put(_count,&nlen..)||")";
              %end;
              %else %do;
                _str=put(_count,&nlen..)||" ("||&pctaction||")";
              %end;
            %end;
          %end;
        %end;
      %end;
    end;
    _idlabel=put(&trtvar,&trtfmt);
    *- cancel possible treatment variable format picked up from dspop dataset -;
    format &trtvar;
  run;


 
      /*-----------------------------*
             calculate p-values
       *-----------------------------*/

  %if "&pvalues" EQ "Y" %then %do;

    data _forpvals;
      set _npctlowlvl(where=(&trtvar &pvaltrtlist));
      _response=1;
      output;
      _response=0;
      _count=_total-_count;
      output;
    run;

    proc sort data=_forpvals;
      by &byvars &highlvlord &newhighlvl 
         &midlvlord &newmidlvl &lowlvlord &newlowlvl &trtvar;
    run;


    %if %attrn(_forpvals,nobs) GT 0 %then %do;
      %let pvalds=_pvalues;
      %npctpvals(dsin=_forpvals,
               byvars=&byvars &highlvlord &newhighlvl &midlvlord &newmidlvl &lowlvlord &newlowlvl,
               trtvar=&trtvar,respvar=_response,countvar=_count,usetest=&usetest,
               pvalvar=&pvalvar,pvallbl=&pvallbl,pvalstr=&pvalstr,pvalfmt=&pvalfmt,
               pvalkeep=&pvalkeep,chisqid=&chisqid,fisherid=&fisherid,trendid=&trendid)
    %end;
    %else %let pvalds=;
  
    proc datasets nolist;
      delete _forpvals;
    run;
    quit;
    
  %end;
  %else %do;

    %*- setting this to null makes the "proc report" logic easier -;
    %let pvalstr=;

  %end;



      /*-----------------------------*
                 Transpose
       *-----------------------------*/
  
  proc sort data=_npctlowlvl;
    by &byvars &highlvlord &newhighlvl &midlvlord 
       &newmidlvl &lowlvlord &newlowlvl &trtvar;
  run;

  *- transpose _str -;
  proc transpose prefix=&_trtpref_ data=_npctlowlvl out=_npctstr(drop=_name_);
    by &byvars &highlvlord &newhighlvl &midlvlord 
       &newmidlvl &lowlvlord &newlowlvl ;
    var _str;
    id &trtvar;
    idlabel _idlabel;
  run;

  *- transpose _count -;
  proc transpose prefix=_CNT data=_npctlowlvl out=_npctcnt(drop=_name_);
    by &byvars &highlvlord &newhighlvl &midlvlord 
       &newmidlvl &lowlvlord &newlowlvl ;
    var _count;
    id &trtvar;
  run;

  *- transpose _pct -;
  proc transpose prefix=_PCT data=_npctlowlvl out=_npctpct(drop=_name_);
    by &byvars &highlvlord &newhighlvl &midlvlord 
       &newmidlvl &lowlvlord &newlowlvl ;
    var _pct;
    id &trtvar;
  run;


  *- transpose _events -;
  proc transpose prefix=_EVE data=_npctlowlvl out=_npcteve(drop=_name_);
    by &byvars &highlvlord &newhighlvl &midlvlord 
       &newmidlvl &lowlvlord &newlowlvl ;
    var _events;
    id &trtvar;
  run;

  *- Merge the transposed datasets back together -;
  *- and add the pvalue dataset (if created).    -;
  data &dsout;
    merge _npctstr _npctcnt _npctpct _npcteve &pvalds;
    by &byvars &highlvlord &newhighlvl &midlvlord 
       &newmidlvl &lowlvlord &newlowlvl ;
  run;



      /*-----------------------------*
            minpct= and mincount=
       *-----------------------------*/

  %if %length(&minpct) or %length(&mincount) %then %do;

    *- minpct= and mincount= only apply to the treatment arm value -;
    *- specified to trtord= (by default the total of all treatment -;
    *- groups) so select on these. -;
    data &dsout;
      set &dsout;
      %if %length(&mincount) %then %do;
        if _cnt%sysfunc(compress(&trtord,%str(%'%"))) >= &mincount;
      %end;
      %if %length(&minpct) %then %do;
        if _pct%sysfunc(compress(&trtord,%str(%'%"))) >= &minpct;
      %end;   
    run;

  %end;



      /*-----------------------------*
         minpctany= and mincountany=
       *-----------------------------*/

  %if %length(&minpctany) or %length(&mincountany) %then %do;

    %*- Get a list of all the treatment values -;
    %let trtinlist=&_trtinlist_;
    %if "&total" EQ "Y" %then %let trtinlist=&trtinlist &_trttotstr_;

    *- Find the maximum _count and _pct for any displayed treatment -;
    *- arm, select of this and drop at the end. -;
    data &dsout;
      set &dsout;
       _count=max(0
      %do i=1 %to %words(&trtinlist);
        %let val=%sysfunc(compress(%scan(&trtinlist,&i,%str( )),%str(%'%")));
        , _cnt&val
      %end;
      );
      _pct=max(0
      %do i=1 %to %words(&trtinlist);
        %let val=%sysfunc(compress(%scan(&trtinlist,&i,%str( )),%str(%'%")));
        , _pct&val
      %end;
      );    
      %if %length(&mincountany) %then %do;
        if _count >= &mincountany;
      %end;
      %if %length(&minpctany) %then %do;
        if _pct >= &minpctany;
      %end;   
      drop _pct _count;
    run;

  %end;



      /*-----------------------------*
               Keepmidlvlmin
       *-----------------------------*/

  %if "&keepmidlvlmin" EQ "N" %then %do;
    proc summary nway missing data=&dsout;
      class &byvars &newhighlvl &newmidlvl;
      output out=_npctkeepmid(drop=_type_);
    run;

    data _npctkeepmid;
      set _npctkeepmid;
      if _freq_=1 then delete;
      drop _freq_;
    run;

    proc sort data=&dsout;
      by &byvars &newhighlvl &newmidlvl;
    run;

    data &dsout;
      merge _npctkeepmid(in=_mid) &dsout;
      by &byvars &newhighlvl &newmidlvl;
      if _mid;
    run;

  %end;



      /*-----------------------------*
           anywhen=after processing
       *-----------------------------*/

  %if "&anywhen" EQ "A" %then %do;

    *- If anywhen=after then the anylvl3 count and percentage -;
    *- are based on only the terms being displayed so at this -;
    *- point find out what those terms are and branch back to -;
    *- near the start of the macro and select only those terms. -;
    proc sort nodupkey data=&dsout(keep=&byvars &newhighlvl &newmidlvl &newlowlvl)
               out=_npctfilter;
      by &byvars &newhighlvl &newmidlvl &newlowlvl;
    run;
   
    %*- reset so that we do not do this a second time -;
    %let anywhen=B;
    %goto filter;

  %end;



      /*-----------------------------------*
         Add _highlvlord ordering variable
       *-----------------------------------*/

  %if not %length(&highlvlord) %then %do;

    data &dsout;
      merge _npcthighlvl(in=_highlvl keep=&byvars &trtvar
                           &newhighlvl _highlvlord _highlvlevord
                     where=(&trtvar=&trtord))
            &dsout(in=_tran);
      by &byvars &newhighlvl;
      if _tran;
      if not _highlvl then do;
        _highlvlord=9999;
        _highlvlevord=9999;
      end;
      drop &trtvar;
    run;

  %end;



      /*----------------------------------*
         Add _midlvlord ordering variable
       *----------------------------------*/

  %if not %length(&midlvlord) %then %do;

    proc sort data=&dsout;
      by &byvars &highlvlord &newhighlvl &newmidlvl;
    run;

    data &dsout;
      merge _npctmidlvl(in=_midlvl keep=&byvars &highlvlord &newhighlvl 
                           &newmidlvl _midlvlord _midlvlevord &trtvar
                     where=(&trtvar=&trtord))
            &dsout(in=_tran);
      by &byvars &highlvlord &newhighlvl &newmidlvl;
      if _tran;
      if not _midlvl then do;
        _midlvlord=9999;
        _midlvlevord=9999;
      end;
      drop &trtvar;
    run;

  %end;



      /*----------------------------------*
         Add _lowlvlord ordering variable
       *----------------------------------*/

  %if not %length(&lowlvlord) %then %do;

    proc sort data=&dsout;
      by &byvars &highlvlord &newhighlvl &midlvlord &newmidlvl &newlowlvl;
    run;

    data &dsout;
      merge _npctlowlvl(in=_lowlvl 
                  keep=&byvars &highlvlord &newhighlvl &trtvar
                       &midlvlord &newmidlvl &newlowlvl _lowlvlord _lowlvlevord
                 where=(&trtvar=&trtord))
          &dsout(in=_tran);
      by &byvars &highlvlord &newhighlvl &midlvlord &newmidlvl &newlowlvl;
      if _tran;
      if not _lowlvl then do;
        _lowlvlord=9999;
        _lowlvlevord=9999;
      end;
      drop &trtvar;
    run;

  %end;



       /*----------------------------------*
           Set default ordering variables
        *----------------------------------*/

  %if not %length(&highlvlord) %then %let highlvlord=_highlvlord;
  %if not %length(&midlvlord)  %then %let midlvlord=_midlvlord;
  %if not %length(&lowlvlord)  %then %let lowlvlord=_lowlvlord;



     /*-----------------------------------------*
              Manipulate output dataset 
      *-----------------------------------------*/

    %*-- apply filter code if any --;
    %if %length(&filtercode) %then %do;
      data &dsout;
        set &dsout;
        %unquote(%qdequote(&filtercode));
      run;
    %end;


    %*- call external data manipulation macro if set -;
    %if %length(&extmacro) %then %do;
      %&extmacro;
    %end;



     /*-----------------------------------------*
            Call titles and footnotes macro
      *-----------------------------------------*/


  %*- call titles and footnotes macro if set -;
  %if %length(&tfmacro) %then %do;
    %&tfmacro;
  %end;



       /*-----------------------------*
             Calculate report width
        *-----------------------------*/

  %*- This is done here so the report width can be applied -;
  %*- to the midlvl and highlvl variable in a length statement -;
  %*- to make sure it is not too long for the report. -;

  %let totvar=;
  %if "&total" EQ "Y" %then %let totvar=&_trttotvar_;

  %if not %length(&byroww) %then %let byroww=12;
  %if not %length(&byrow2w) %then %let byrow2w=12;


  %let numtrt=%words(&_trtvarlist_ &totvar);
  %do i=1 %to &numtrt;
    %if &i EQ 1 and not %length(&trtsp1) %then %let trtsp1=&spacing;
    %else %if not %length(&&&trtsp&i) %then %let trtsp&i=&trtspace;
  %end;
  %let trtwidth=0;
  %do i=1 %to &numtrt;
    %let trtwidth=%eval(&trtwidth+&&trtsp&i+&&trtw&i);
  %end;

  %if "&pvalues" EQ "Y" %then %let trtwidth=%eval(&trtwidth+&trtspace+8);

  %if &style NE 3 %then %let rest=%eval(&ls-&trtwidth-&indentlow-&indentmid);
  %else %let rest=%eval(&ls-&trtwidth);

  %if %length(&byrowvar) %then %let rest=%eval(&rest-&byroww-2);
  %if %length(&byrow2var) %then %let rest=%eval(&rest-&byrow2w-2);

  %if &lowlvlw=0 %then %let lowlvlw=&rest;
  %else %if &rest LT &lowlvlw %then %let lowlvlw=&rest;

  %if &style3w=0 %then %let style3w=&rest;
  %else %if &rest LT &style3w %then %let style3w=&rest;


  %*- report width below is only meaningful and correct for style=1 and style=3 output -;
  %if &style NE 3 %then %let repwidth=%eval(&lowlvlw+&trtwidth+&indentlow+&indentmid);
  %else %let repwidth=%eval(&trtwidth+&style3w);


  %if ¢er EQ NOCENTER %then %let startcol=1;
  %else %let startcol=%eval((&ls-&repwidth)/2 + 1);

  %put NOTE: (npcttab) ls=&ls repwidth=&repwidth startcol=&startcol;
  %put;

  %if %length(&outputwidthpct) %then %do;
    %if %upcase(%substr(&outputwidthpct,1,1)) EQ C 
       %then %let outputwidthpct=%eval(&repwidth*1/1);
  %end;

  %if %sysevalf(&outputwidthpct GT 100) %then %let outputwidthpct=100;

  %put NOTE: (npcttab) outputwidthpct=&outputwidthpct% of available width;



          /*---------------------------------*
                Set some variable lengths
           *---------------------------------*/

  proc sort data=&dsout;
    by &byvars &highlvlord &newhighlvl &midlvlord &newmidlvl &lowlvlord &newlowlvl;
  run;


  *- midlvl variable can not be longer than the report width -;
  data &dsout;
    retain _page 1;
    %if &style EQ 3 %then %do;
      length _combtxt _combtext2 $ 256; *-was %eval(&ls-&trtwidth) -;
    %end;
    length &_trtvarlist_ 
      %if "&total" EQ "Y" %then %do;
        &_trttotvar_ 
      %end;
      $ &strlen
      %if "&pvalues" EQ "Y" %then %do;
        &pvalstr $ 8
      %end;
    ;
    retain _indent '        ';
    set &dsout;
    by &byvars &highlvlord &newhighlvl &midlvlord &newmidlvl &lowlvlord &newlowlvl;
    %if &style EQ 3 %then %do;
      if &newlowlvl=&anylowlvl then do;
        _combtxt=&newmidlvl;
        _combtext2=_combtxt;
      end;
      else do;
        _combtxt =repeat(" ",&indentlow-1)||&newlowlvl;
        _combtext2=repeat("A0"x,&indentlow*2.5-1)||&newlowlvl;
      end;
      %if %length(&highlvl) %then %do;
        _combtxt=repeat(" ",&indentlow-1)||_combtxt;
        _combtext2=repeat("A0"x,&indentlow*2.5-1)||_combtext2;
      %end;
      %splitvar(_combtxt,_combtext,&style3w,split=&split,hindent=&hindent,usecolon=&usecolon);
    %end;
    %if %length(&droplowlvl) %then %do;
      if &newlowlvl in (&droplowlvl) 
      %if &style=3 %then %do;
        and &newlowlvl NE &anylowlvl
      %end;
      then delete;
    %end;
    if &newlowlvl=&toplowlvl then _primord=0;
    else _primord=1;
    %if %length(&pageon) %then %do;
      if first.%scan(&suppvars,1) and %scan(&suppvars,1) in: (&pageon) then _page=_page+1;
    %end;
  run;

  %if &style EQ 3 and %length(&highlvl) %then %do;

    data &dsout;
      set &dsout;
      by &byvars &highlvlord &newhighlvl &midlvlord &newmidlvl &lowlvlord &newlowlvl;
      array _trt &_trtpref_:;
      if first.&newhighlvl then do;
        output;
        _combtxt=&newhighlvl;
        _combtext2=&newhighlvl;
        link splitvar;
        _primord=_primord-0.001;
        do over _trt;
          _trt=" ";
        end;
        output;
      end;
      else output;
      return;
      splitvar:
        %splitvar(_combtxt,_combtext,&style3w,split=&split,hindent=&hindent,usecolon=&usecolon);
      return;
    run;
  %end;


             /*-----------------------------------------*
                       Define "proc report" macro
              *-----------------------------------------*/

  %macro _npctrep(topline=,trtlabel=,combtext=_combtext,PDF=N,compskip=);

    proc report nowd missing headline &headskip split="&split" spacing=&spacing &spanrows data=&dsout

      style(REPORT)=[cellspacing=&cellspacing cellpadding=&cellpadding rules=&rules 
                 background=white foreground=black 
                 %if &report_ul EQ Y and &report_ol EQ Y %then %do;
                   HTMLSTYLE="border-left:none;border-right:none;border-bottom:&linepx.px solid &linecol;border-top:&linepx.px solid &linecol"
                 %end;
                 %else %if &report_ul EQ Y %then %do;
                   HTMLSTYLE="border-left:none;border-right:none;border-bottom:&linepx.px solid &linecol;border-top:none"
                 %end;
                 %else %if &report_ol EQ Y %then %do;
                   HTMLSTYLE="border-left:none;border-right:none;border-top:&linepx.px solid &linecol;border-bottom:none"
                 %end;
                 %else %if &report_border NE Y %then %do;
                   HTMLSTYLE="border:none"
                 %end;
                 %if %length(&outputwidthpct) %then %do;
                   outputwidth=&outputwidthpct%
                 %end;
                 ]

      style(COLUMN)={asis=&asis
                 %if &column_border NE Y %then %do;
                   HTMLSTYLE="border:none%str(;) mso-number-format:'\@'"
                 %end;
                 %else %do;
                   HTMLSTYLE="mso-number-format:'\@'"
                 %end;
                 background=&background_stats foreground=&foreground_stats
                 font_face=&font_face_stats font_weight=&font_weight_stats}

      style(HEADER)=[background=&background_header foreground=&foreground_header
                 %if "&header_ul" EQ "Y" %then %do;
                   HTMLSTYLE="border-bottom:&linepx.px solid &linecol;border-left:none;border-right:none;border-top:none; mso-number-format:'\@'"
                 %end;
                 %else %if "&header_border" NE "Y" %then %do;
                   HTMLSTYLE="border:none; mso-number-format:'\@'"
                 %end;
                 %else %do;
                   HTMLSTYLE="mso-number-format:'\@'"
                 %end;
                 ]

       style(LINES)=[background=&background_other foreground=&foreground_other
                font_face=&font_face_other font_style=&font_style_other font_weight=&font_weight_other 
                %if &lines_border NE Y %then %do;
                  %if &compskip_ul EQ Y %then %do;
                    HTMLSTYLE="border-bottom:&compskippx.px solid &compskipcol;border-left:none;border-right:none;border-top:none; mso-number-format:'\@'"
                  %end;
                  %else %do;
                    HTMLSTYLE="border:none; mso-number-format:'\@'"
                  %end;
                %end;
                %else %do;
                  HTMLSTYLE="mso-number-format:'\@'"
                %end;
                ]
               
    ;

      %if %length(&byvars) %then %do;
        by &byvars;
      %end; 
 
      columns
      %if "&topline" EQ "Y" %then %do;
        ( "___" " "
      %end;

      &pagevar &byroword &byrowvar &byrow2ord &byrow2var

           &highlvlord
 
           %if "&eventsort" EQ "Y" %then %do;
             _highlvlevord
           %end;

           &newhighlvl 
           &midlvlord 

           %if "&eventsort" EQ "Y" %then %do;
             _midlvlevord
           %end;

           &newmidlvl 
           _primord
           &lowlvlord 

           %if "&eventsort" EQ "Y" %then %do;
             _lowlvlevord
           %end;

      %if &style=3 %then %do;
        &combtext
      %end;

      %else %do;
        %if &indentlow GT 0 %then %do;
          _indent 
        %end;
  
        &newlowlvl 
      %end;
  
      (&toplabel 
 
     %if %length(&trtlabel) %then %do;
        %if "&spantotal" EQ "Y" %then %do;
          ( &trtlabel 
            %if %length(&trtvarlist) %then %do;
              &trtvarlist )
            %end;
            %else %do;
              &_trtvarlist_ &totvar ) &pvalstr
            %end;
        %end;
        %else %do;
          ( &trtlabel
            %if %length(&trtvarlist) %then %do;
              &trtvarlist )
            %end;
            %else %do; 
              &_trtvarlist_ ) &totvar &pvalstr
            %end;
        %end;
      %end;
      %else %do;
         %if %length(&trtvarlist) %then %do;
           &trtvarlist
         %end;
         %else %do;
           &_trtvarlist_ &totvar &pvalstr
         %end;
      %end;
        )

      %if "&topline" EQ "Y" %then %do;
        )
      %end;
      ;

      %if %length(&pagevar) %then %do;
        define &pagevar / order order=internal noprint;
      %end;

      %if %length(&byroword) %then %do;
        define &byroword / order order=internal noprint;
      %end;

      %if %length(&byrowvar) %then %do;
        define &byrowvar / order order=internal &byrowlabel width=&byroww &byrowalign flow spacing=0
        %if %length(&byrowfmt) %then f=&byrowfmt ;
        style(COLUMN)={font_face=&font_face_other font_weight=&font_weight_other}   
        ;
      %end;


      %if %length(&byrow2ord) %then %do;
        define &byrow2ord / order order=internal noprint;
      %end;

      %if %length(&byrow2var) %then %do;
        define &byrow2var / order order=internal &byrow2label width=&byrow2w &byrow2align flow spacing=2
        %if %length(&byrow2fmt) %then f=&byrow2fmt ;   
        style(COLUMN)={font_face=&font_face_other font_weight=&font_weight_other}
        ;
      %end;

      define &highlvlord / order order=internal noprint;

      %if "&eventsort" EQ "Y" %then %do;
        define _highlvlevord / order=internal noprint;
      %end;

      define &newhighlvl / order noprint;

      define &midlvlord / order order=internal noprint;

      %if "&eventsort" EQ "Y" %then %do;
        define _midlvlevord / order=internal noprint;
      %end;

      define &newmidlvl / order noprint;

      %if &indentlow GT 0 and &style NE 3 %then %do;
        define _indent / order spacing=0 width=%eval(&indentlow+&indentmid)

        %if %length(&highlvllbl) %then %do;
          %if %length(&highlvllbl) LE %eval(&indentlow+&indentmid) %then %do;
            "&highlvllbl" 
          %end;
          %else %do;
            "%substr(&highlvllbl,1,%eval(&indentmid+&indentlow))" 
          %end;
          %if %length(&midlvllbl) %then %do;
            %if %length(&midlvllbl) LE &indentlow %then %do;
              "%sysfunc(repeat(%str( ),%eval(&indentmid-1)))&midlvllbl" 
            %end;
            %else %do;
              "%sysfunc(repeat(%str( ),%eval(&indentmid-1)))%substr(&midlvllbl,1,&indent)" 
            %end;
            %if %length(&lowlvllbl) %then %do;
              " "
            %end;
          %end;
          %else %do;
            " " 
          %end;
        %end;

        %else %if %length(&midlvllbl) %then %do;
          %if %length(&midlvllbl) LE &indentlow %then %do;
            "&midlvllbl" 
          %end;
          %else %do;
            "%substr(&midlvllbl,1,&indentlow)" 
          %end;
          %if %length(&lowlvllbl) %then %do;
            " "
          %end;
        %end;

        %else %do;
          " " 
        %end;

        style(COLUMN)=[cellwidth=%eval(52*(&indentlow+&indentmid)/&repwidth)% ]
       ;

      %end;

      define _primord / order order=internal noprint;

      define &lowlvlord / order order=internal noprint;

      %if "&eventsort" EQ "Y" %then %do;
        define _lowlvlevord / order=internal noprint;
      %end;


      %if &style NE 3 %then %do;

        define &newlowlvl / order spacing=0 width=&lowlvlw flow

        %if &indentlow GT 0 %then %do;
          %if %length(&highlvllbl) GT %eval(&indentlow+&indentmid) %then %do;
            "%substr(&highlvllbl,&indentlow+&indentmid+1)"
            %if %length(&midlvllbl) GT &indentlow %then %do;
              "%substr(&midlvllbl,&indentlow+1)"
            %end;
            %else %do;
             " "
            %end;
            %if %length(&lowlvllbl) %then %do;
              "&lowlvllbl"
            %end;
          %end;
          %else %if %length(&midlvllbl) GT &indentlow %then %do;
            "%substr(&midlvllbl,&indentlow+1)"
            %if %length(&lowlvllbl) %then %do;
              "&lowlvllbl"
            %end;
          %end;
          %else %do;
            %if %length(&lowlvllbl) %then %do;
              "&lowlvllbl"
            %end;
            %else %do;
              " "
            %end;
          %end;
        %end;

        %else %do;
          %if %length(&lowlvllbl) %then %do;
            "&lowlvllbl"
          %end;
          %else %do;
            " "
          %end;
        %end;

     /**   style(COLUMN)=[cellwidth=%eval(100*(&lowlvlw)/&repwidth)% ]   **/

      ;

      %end;

      %else %do;

        define &combtext / order
        %if not %length(&byrowvar) %then %do;
          spacing=0
        %end;
        %else %do;
          spacing=2
        %end;
    
        width=&style3w flow

        %if %length(&style3lbl) %then %do;
          &style3lbl
        %end;
        %else %do;
          " "
        %end;
        style(COLUMN)={asis=&asis font_face=&font_face_other font_weight=&font_weight_other}
        ;

      %end;

  
      %do i=1 %to %words(&_trtvarlist_);
        define %scan(&_trtvarlist_,&i,%str( )) / width=&&trtw&i center display spacing=&&trtsp&i &trtalign
        %if %length(&outputwidthpct) %then %do;
          %if &PDF NE Y %then %do;
            style(COLUMN)=[cellwidth=%eval(45*(&&trtw&i+&trtspace)/&repwidth)% ]
          %end;
        %end;
        ;
      %end;
      %if %length(&totvar) %then %do;
        define &totvar / width=&&trtw&i center display spacing=&trtspace &trtalign
        %if %length(&outputwidthpct) %then %do;
          %if &PDF NE Y %then %do;
            style(COLUMN)=[cellwidth=%eval(45*(&&trtw&i+&trtspace)/&repwidth)% ]
          %end;
        %end;
        ;
      %end;
      %if %length(&pvalstr) %then %do;
        define &pvalstr / &pvallbl width=&pvalcolw center display spacing=&trtspace
        %if %length(&outputwidthpct) %then %do;
          %if &PDF NE Y %then %do;
            style(COLUMN)=[cellwidth=%eval(45*(&pvalcolw+&trtspace)/&repwidth)% ]
          %end;
        %end;
        ;
      %end;

      %if &indentmid gt 0 and &style ne 3 %then %do;
        compute before &newhighlvl;
          line @&startcol &highlvl $char400.;
        endcomp;
      %end;

      %if &indentlow gt 0 and &style ne 3 %then %do;
        compute before &newmidlvl;
          line @%eval(&startcol+&indentmid) &midlvl $char400.;
        endcomp;
      %end;

      %if &midskip EQ Y %then %do;
        %if &compskip EQ Y %then %do;
          compute &compskippos &newmidlvl;
            line " ";
          endcomp;
        %end;
        %else %do;
          break after &newmidlvl / skip;
        %end;
      %end;

      %if &highskip EQ Y %then %do;
        %if &compskip EQ Y %then %do;
          compute after &newhighlvl;
            line " ";
          endcomp;
        %end;
        %else %do;
          break after &newhighlvl / skip;
        %end;
      %end;

      %if "&double" EQ "Y" %then %do;
        %if &compskip EQ Y %then %do;
          compute after &newlowlvl;
            line " ";
          endcomp;
        %end;
        %else %do;
          break after &newlowlvl / skip;
        %end;
      %end;

      %if %length(&pagevar) %then %do;
        break &pgbrkpos &pagevar / page;
      %end;

      %if "&pageline" EQ "Y" or %length(&pageline1.&pageline2.&pageline3.&pageline4) 
        or %length(&pagemacro) %then %do;
        compute after &pagevar2;
          %if "&pageline" EQ "Y" %then %do;
            line @&startcol &repwidth*'_';
          %end;
          %if %length(&pagemacro) %then %do;
            %&pagemacro;
          %end;
          %else %do;
            %do i=1 %to 15;
              %if %length(&&pageline&i) %then %do;
                line @&startcol &&pageline&i;
              %end;
            %end;
          %end;
        endcomp;
      %end;

      %if "&endline" EQ "Y" or %length(&endline1.&endline2.&endline3.&endline4)
        or %length(&endmacro) %then %do;
        compute after;
          %if "&endline" EQ "Y" %then %do;
            line @&startcol &repwidth*'_';
          %end;
          %if %length(&endmacro) %then %do;
            %&endmacro;
          %end;
          %else %do;
            %do i=1 %to 15;
              %if %length(&&endline&i) %then %do;
                line @&startcol &&endline&i;
              %end;
            %end;
          %end;
        endcomp;
      %end;

    run;

  %mend _npctrep;



    /*-----------------------------*
            Produce the report
     *-----------------------------*/


  %*- make sure byroword and byrowvar are not in the by variables list -;
  %if %length(&byvars) and %length(&byrowvar) %then
    %let byvars=%removew(&byvars,&byrowvar);

  %if %length(&byvars) and %length(&byroword) %then
    %let byvars=%removew(&byvars,&byroword);

  %*- make sure byrow2ord and byrow2var are not in the by variables list -;
  %if %length(&byvars) and %length(&byrow2var) %then
    %let byvars=%removew(&byvars,&byrow2var);

  %if %length(&byvars) and %length(&byrow2ord) %then
    %let byvars=%removew(&byvars,&byrow2ord);


  %if "&print" EQ "N" %then %do;
    ods listing close;
  %end;
  %else %do;
    ods listing &odslisting;
  %end;

  %if "&print" EQ "Y" %then %do;
    %_npctrep(topline=&topline,trtlabel=&trtlabel,compskip=N);
    ods listing close;
  %end;


  %if %length(&odsrtf) or %length(&odshtml) or %length(&odshtmlcss)
   or %length(&odscsv) or %length(&odspdf) or %length(&odsother) %then %do;


    %*- call ODS titles and footnotes macro if set -;
    %if %length(&odstfmacro) %then %do;
      %&odstfmacro;
    %end;

    %if %length(&odsescapechar) %then %do;
      ODS escapechar=&odsescapechar;
    %end;

    %*- strip start and trailing single quotes of prerepcode if present -;
    %if %length(&odsprerepcode) %then %do;
      %if %qsubstr(&odsprerepcode,1,1) EQ %str(%') 
       and %qsubstr(&odsprerepcode,%length(&odsprerepcode),1) EQ %str(%')
        %then %do;
%unquote(%qsubstr(&odsprerepcode,2,%length(&odsprerepcode)-2))
        %end;
      %else %do;
&odsprerepcode
      %end;
    %end;


    %if %length(&odspdf) %then %do;
      ods pdf &odspdf;
      %_npctrep(topline=N,trtlabel=&odstrtlabel,combtext=_combtext2,PDF=Y,compskip=&compskip);
      ods pdf close;
    %end;


    %if %length(&odsrtf) %then %do;
      ods rtf &odsrtf ;
    %end;

    %if %length(&odshtml) %then %do;
      ods html &odshtml ;
    %end;

    %if %length(&odshtmlcss) %then %do;
      ods htmlcss &odshtmlcss ;
    %end;

    %if %length(&odscsv) %then %do;
      ods csv &odscsv ;
    %end;

    %if %length(&odsother) %then %do;
      ods &odsother ;
    %end;


    %_npctrep(topline=N,trtlabel=&odstrtlabel,combtext=_combtext2,compskip=&compskip);


    %*- strip start and trailing single quotes of postrepcode if present -;
    %if %length(&odspostrepcode) %then %do;
      %if %qsubstr(&odspostrepcode,1,1) EQ %str(%') 
       and %qsubstr(&odspostrepcode,%length(&odspostrepcode),1) EQ %str(%')
        %then %do;
%unquote(%qsubstr(&odspostrepcode,2,%length(&odspostrepcode)-2))
        %end;
      %else %do;
&odspostrepcode
      %end;
    %end;



    %if %length(&odsrtf) %then %do;
      ods rtf close;
    %end;

    %if %length(&odshtml) %then %do;
      ods html close;
    %end;

    %if %length(&odshtmlcss) %then %do;
      ods htmlcss close;
    %end;

    %if %length(&odscsv) %then %do;
      ods csv close;
    %end;

    %if %length(&odsother) %then %do;
      ods %scan(&odsother,1,%str( )) close;
    %end;

  %end;



    /*-----------------------------*
             Tidy up and exit
     *-----------------------------*/

  ods listing;

  %if "&debug" EQ "Y" %then %do;
  %end;
  %else %do;
    proc datasets nolist;
      delete _npctdsin _npcthighlvl _npctmidlvl _npctlowlvl 
             _npctstr _npctcnt _npctpct _npcteve &pvalds
        _npctevhighlvl _npctevmidlvl _npctevlowlvl _zerogrid

      %if %length(&keepmidlvlmin) %then %do;
        _npctkeepmid
      %end;

      %if %length(&dsall) %then %do;
        _npctdsall
      %end;
      %if "&print" EQ "Y" %then %do;
        &dsout
      %end;
      ;
    run;
    quit;
  %end;

  %goto skip;
  %exit: %put &err: (npcttab) Leaving macro due to problem(s) listed;
  %skip:

  options &savopts;

%mend npcttab;