*! texsave 1.6.1 22may2023 by Julian Reif * 1.6.1: added rowstretch, rowheight, colwidth, and tablelines options. added headerlines2() option. added align options 'R' and 'L' * 1.6.0: added "@{}" to header alignment. Changed footnote to use \parbox. * 1.5.1: added dataonly and valuelabels options. endash option, when there is more than one negative number in the cell, now changes all negatives (up to 10) rather than just the first one * 1.4.6: added label option (replaces marker function, which is now deprecated) * 1.4.5: added new endash option (enabled by default) * 1.4.4: added headersep() option * 1.4.3: width default changed from \textwidth to \linewidth, to improve landscape tables. Added addlinespace() as footnote suboption. Changed default to \addlinespace[\belowrulesep] * 1.4.2: added preamble option. * 1.4.1: added footnote width option. * 1.4: geometry package now required. footnote parameters edited. landscape and geometry options added. * 1.3.3: -fix- option now also corrects "_" in column names * 1.3.2: make -autonumber- option stack with, rather than replace, column names. * 1.3.1: hlines now allows negative numbers * 1.3: booktabs and tabularx packages now required. 'H' allowed as location option (for subfig package). Headerlines option now enabled when nonames option is requested. Fixed hlines bug when numlist was unsorted. SW options turned off by default. * 1.2.2: added headerlines option * 1.2.1: added extra space after \hline to make tables look nicer. Added -rowsep- option to set row height spacing. * 1.2: added booktab package and formatting options. * 1.1.1: changed default align option to 8cm (so SWP will no longer error on this). * 1.1: new hlines, autonumber options, and footnotes suboptions. Added '&' to badchar list. Align option now allows any table spec argument. * 1.0.3: integer values now allowed for size option * 1.0.2: added size option * 1.0.1: added location option program define texsave, nclass version 10 syntax [varlist] using/ [if] [in] [, noNAMES SW noFIX noENDASH title(string) DELIMITer(string) footnote(string asis) preamble(string asis) headlines(string asis) tablelines(string asis) headerlines(string asis) headerlines2(string asis) footlines(string asis) frag align(string) LOCation(string) size(string) width(string) marker(string) label(string) bold(string) italics(string) underline(string) slanted(string) smallcaps(string) sansserif(string) monospace(string) emphasis(string) VARLABels VALUELABels hlines(numlist) autonumber rowstretch(numlist max=1 missingok) rowheight(string) rowsep(string) colwidth(string) headersep(string) LANDscape GEOmetry(string) DECIMALalign dataonly replace] ******************************************************************************************** ***** Parsing and QC'ing of command options ******************************************************************************************** * Check if appendfile is installed cap appendfile if _rc==199 { di as error "appendfile not installed. Type -ssc install appendfile-" exit _rc } * nonames cannot be specified together with varlabels if "`varlabels'"!="" & "`names'"!="" { di as error "option varlabels not allowed with option nonames" exit 198 } * headerlines and headerlines2 generally not specified together if `"`headerlines'"'!="" & `"`headerlines2'"'!="" { di as error "Note: headerlines() and headerlines2() both specified" } * By default, value labels are not written out if "`valuelabels'"=="" local nolabel nolabel * Error check hlines if "`hlines'"!="" { numlist "`hlines'", integer sort local hlines "`r(numlist)'" local num_hlines : word count `hlines' local max : word `num_hlines' of `hlines' local min : word 1 of `hlines' qui count `if' `in' local num_rows = `r(N)' if `num_rows' < abs(`max') | `num_rows' < abs(`min') { di as error "hlines() cannot cannot include values larger than `r(N)', the size of the table" exit 198 } * Any negative numbers are interpreted as coming from the bottom of the table local tmp_hlines foreach v of local hlines { local tmp = `v' if `tmp' < 0 local tmp =`num_rows'+`tmp' local tmp_hlines "`tmp_hlines' `tmp'" } local hlines "`tmp_hlines'" numlist "`hlines'", integer sort local hlines "`r(numlist)'" } * Define a horizontal line local horiz_line "\midrule" * Define row and header separation spacing if applicable. (rowsep uses "\BS" since it is implemented via filefilter) if `"`rowsep'"'!="" local rowsep `" \BSaddlinespace[`rowsep']"' if "`rowstretch'" == "" local rowstretch = . if `"`headersep'"'!="" local headersep `" \addlinespace[`headersep']"' else local headersep `" \addlinespace[\belowrulesep]"' * Append .tex extension if no extension present if strpos(`"`using'"',".") == 0 local using `"`using'.tex"' * Get number of vars/columns local num_vars : word count `varlist' * Label option overloads the marker option if !mi(`"`label'"') { if !mi(`"`marker'"') { di as error "Cannot specify both the label and marker options" exit 198 } local marker `"`label'"' } * Error check the location option. Set default to "tbp" if `"`location'"'!="" { * Only allowed chracters are 'H', 'h', 't', 'b', 'p' and ' ' (blank) local location: subinstr local location " " "", all forval x = 1/`=length(`"`location'"')' { local char = substr(`"`location'"',`x',1) if !inlist(`"`char'"', "h", "t", "b", "p", "H") { di as error "location() option contains invalid characters" exit 198 } } } else local location "tbp" * Set table width if "`width'"=="" local width "\linewidth" * Set default values for delimiter. Determine what the end-of-line character is for the machine (needed for filefilter command below) if `"`delimiter'"'=="" local delimiter "&" if "`c(os)'" == "MacOSX" { if "`c(eolchar)'"=="mac" local eol_char = "\M" else local eol_char = "\U" } else if "`c(os)'" == "Windows" local eol_char = "\W" else local eol_char = "\U" ***************************** ***** FOOTNOTE OPTIONS **** ***************************** * Pull out footnotesize and footnote width options if specified. Save the options that apply to the table so they don't get overwritten here. foreach v in using size width varlist if in { local hold_`v' `"``v''"' } local 0 `"`footnote'"' gettoken footnote 0 : 0, parse(,) cap syntax, [size(string) width(string) addlinespace(string)] if _rc!=0 { di as error "Invalid syntax for footnote() option" exit 198 } local footnotesize `"`size'"' local footnotewidth `"`width'"' foreach v in using size width varlist if in { local `v' `"`hold_`v''"' } * Footnote spacing option. Default (no footnote) is blank. Default (footnote, user did not specify spacing) is \belowrulesep (suggestion by booktabs package) * http://mirror.utexas.edu/ctan/macros/latex/contrib/booktabs/booktabs.pdf if `"`footnote'"'!="" local footnotespace "\addlinespace[\belowrulesep]" if `"`footnote'"'!="" & `"`addlinespace'"'!="" local footnotespace `"\addlinespace[`addlinespace']"' * Footnote width option. Default is \linewidth (consistent with table -width- option) if `"`footnotewidth'"'=="" & `"`width'"'!="" local footnotewidth `"`width'"' else if `"`footnotewidth'"'=="" local footnotewidth "\linewidth" * Error check the size and footnotesize options. Set default for footnotesize. if `"`footnotesize'"'=="" local footnotesize "footnotesize" foreach opt in "size" "footnotesize" { if "``opt''"!="" { * If size number is given... cap confirm integer number ``opt'' if _rc == 0 { if ``opt''>10 | ``opt''<1 { di as error "`opt'() value must be between 1 and 10" exit 198 } if ``opt''==1 local size2 "tiny" if ``opt''==2 local size2 "scriptsize" if ``opt''==3 local size2 "footnotesize" if ``opt''==4 local size2 "small" if ``opt''==5 local size2 "normalsize" if ``opt''==6 local size2 "large" if ``opt''==7 local size2 "Large" if ``opt''==8 local size2 "LARGE" if ``opt''==9 local size2 "huge" if ``opt''==10 local size2 "Huge" assert "`size2'"!="" local `opt' "`size2'" } * Else user specified string else { if !inlist(`"``opt''"', "tiny","scriptsize","footnotesize","small","normalsize","large","Large","LARGE","huge") & "`size'"!="Huge" { di as error "``opt'' is an invalid option for `opt'()" exit 198 } } } } ***************************** ** Table column alignment ** ***************************** * Default is to have first column left-justified and the rest centered. if `"`align'"'=="" { local align "@{}l" forval x = 2/`num_vars' { if "`decimalalign'"!="" local align "`align'S" else local align "`align'C" } local align "`align'@{}" } ***************************** ** HEADER ** ***************************** * Headerlines (user-specified code): by default, includes "\tabularnewline" after each string if `"`headerlines'"' != "" { tokenize `"`headerlines'"' while `"`1'"' != "" { local header_headerlines `"`header_headerlines'`1' \tabularnewline "' macro shift } } * alternative: headerlines2: does not include "\tabularnewline" if `"`headerlines2'"' != "" { tokenize `"`headerlines2'"' while `"`1'"' != "" { local header_headerlines `"`header_headerlines'`1' "' macro shift } } * Autonumber - no number in the first column if "`autonumber'"!="" { local run_no = 1 foreach v of local varlist { if `run_no'>1 local header_autonumber `"`header_autonumber'`delimiter'{(`=`run_no'-1')}"' local run_no = `run_no'+1 } local header_autonumber `"`header_autonumber' \tabularnewline"' * If variable names are also being written out, add an additional horizontal line if "`names'"=="" local header_autonumber `"`header_autonumber' `horiz_line'"' } * Column names (either varlabels or Stata column names) - don't write these out if user specifies -nonames- if "`names'"=="" { foreach v of local varlist { if "`varlabels'"!="" { local lbl : variable label `v' local header_colnames `"`header_colnames'`delimiter'{`lbl'}"' } else local header_colnames `"`header_colnames'`delimiter'{`v'}"' } * Strip out the first delimiter local header_colnames : subinstr local header_colnames `"`delimiter'"' "" local header_colnames `"`header_colnames' \tabularnewline"' } *** * -fix- option: correct chars in header, footnote, and title that cause problems in LaTeX; add bold, italics, underline etc. tags as necessary *** * Header, title, and footer corrections if "`fix'"=="" { foreach str in "header_colnames" "footnote" "title" { * Note: $ substitution here does not work foreach symbol in _ % # $ & ~ { if "`str'"=="header_colnames" & "`symbol'"=="&" continue // Allow &'s in headers since they are delimiters local `str' : subinstr local `str' `"`symbol'"' `"\\`symbol'"', all } * We have braces in the header sometimes so skip that one if "`str'"!="header_colnames" local `str' : subinstr local `str' "{" `"\{"', all if "`str'"!="header_colnames" local `str' : subinstr local `str' "}" "\}", all * '^' is handled specially local `str' : subinstr local `str' `"^"' `"\^{}"', all } } * User-specified options that alter the dataset * - Temporarily rename original variables * - Create new tempvar that has the modified contents (eg, braces removed) * - At the end of the texsave program, drop the tempvars and rename the original vars back to their original names if "`fix'"=="" | "`endash'"=="" | `"`bold'`italics'`underline'`slanted'`smallcaps'`sansserif'`monospace'`emphasis'"'!="" | "`decimalalign'"!="" { tempvar match str1 str2 isreal local renamed = "yes" * Variables - create new temporary ones that have bad chars stripped out of them and are formatted as specified by user qui foreach v of local varlist { tempname `v'temp ren `v' ``v'temp' gen `v' = ``v'temp' * Retain display formatting and value labels local varformat : format ``v'temp' format `v' `varformat' local vallabel : value label ``v'temp' cap label values `v' `vallabel' capture confirm string var `v' if _rc==0 { * Fix problematic symbols if "`fix'"=="" { foreach symbol in _ % # $ & ~ { qui replace `v' = subinstr(`v',"`symbol'","\\`symbol'",.) } qui replace `v' = subinstr(`v',"{","\{",.) qui replace `v' = subinstr(`v',"}","\}",.) qui replace `v' = subinstr(`v',"^","\^{}",.) } * Reformat negative signs from "-" to "--" (en-dash), unless decimalalign option is specified * Only reformat negative signs if they are followed by a number and not preceded by an alphabetic character or negative sign. Do this up to 10 times. qui if "`endash'"=="" & "`decimalalign'"=="" { gen `match' = regexm(`v', "(^|[^A-Za-z\-])-[0-9]") summ `match', meanonly local ismatch = r(max) local counter = 1 while `ismatch'==1 & `counter'<10 { gen `str1' = regexs(0) if regexm(`v', "(^|[^A-Za-z\-])-[0-9]") gen `str2' = subinstr(`str1',"-","--",1) replace `v' = subinstr(`v',`str1',`str2',1) drop `match' `str1' `str2' gen `match' = regexm(`v', "(^|[^A-Za-z\-])-[0-9]") summ `match', meanonly local ismatch = r(max) local `counter' = `counter'+1 } cap drop `match' } * Formatting options local tex_code "\textbf{ \textit{ \underline{ \textsl{ \textsc{ \textsf{ \texttt{ \emph{" local run_no = 1 foreach opt in bold italics underline slanted smallcaps sansserif monospace emphasis { local command : word `run_no' of `tex_code' tokenize `"``opt''"' while `"`1'"' != "" { qui replace `v' = "`command'" + `v' + "}" if strpos(`v',`"`1'"')!=0 macro shift } local run_no = `run_no'+1 } * For decimalalign option, when variable is a formatted string, surround text data with "{...}" but not numeric data if "`decimalalign'"!="" { qui gen `isreal' = real(`v') replace `v' = "{" + `v' + "}" if mi(`isreal') drop `isreal' } } } } ******************************************************************************************** ***** Write out table header to the -using- file ******************************************************************************************** * Open the file tempfile data1 data2 end_file tempname fh qui file open `fh' using "`using'", write `replace' ****** ** Preamble ****** if "`frag'" == "" { file write `fh' "\documentclass{article}" _n file write `fh' "\usepackage{booktabs}" _n file write `fh' "\usepackage{tabularx}" _n file write `fh' "\usepackage[margin=1in]{geometry}" _n if "`landscape'"!="" file write `fh' "\usepackage{pdflscape}" _n if "`decimalalign'"!="" file write `fh' "\usepackage{siunitx}" _n } * Preamble option. This is always outputted, whether or not frag option is specified if `"`preamble'"' != "" { tokenize `"`preamble'"' while `"`1'"' != "" { file write `fh' `"`1'"' _n macro shift } } ****** ** \begin{document} ****** if "`frag'" == "" file write `fh' "\begin{document}" _n(2) if "`landscape'"!="" file write `fh' "\begin{landscape}" _n if `"`geometry'"'!="" file write `fh' `"\newgeometry{`geometry'}"' _n * Headlines option if `"`headlines'"' != "" { tokenize `"`headlines'"' while `"`1'"' != "" { file write `fh' `"`1'"' _n macro shift } } if "`sw'"!="" file write `fh' "%TCIMACRO{\TeXButton{B}{\begin{table}[`location'] \centering}}" _n if "`sw'"!="" file write `fh' "%BeginExpansion" _n ****** ** \begin{table} ****** file write `fh' "\begin{table}[`location'] \centering" _n * tablelines option if `"`tablelines'"' != "" { tokenize `"`tablelines'"' while `"`1'"' != "" { file write `fh' `"`1'"' _n macro shift } } if `rowstretch'!=. file write `fh' "\renewcommand{\arraystretch}{`rowstretch'}" _n if `"`rowheight'"'!="" file write `fh' "\setlength\extrarowheight{`rowheight'}" _n if `"`colwidth'"'!="" file write `fh' "\setlength{\tabcolsep}{`colwidth'}" _n file write `fh' "\newcolumntype{R}{>{\raggedleft\arraybackslash}X}" _n file write `fh' "\newcolumntype{L}{>{\raggedright\arraybackslash}X}" _n file write `fh' "\newcolumntype{C}{>{\centering\arraybackslash}X}" _n(2) if "`sw'"!="" file write `fh' "%EndExpansion" _n if `"`title'"'!="" file write `fh' `"\caption{`title'}"' _n if `"`marker'"'!="" file write `fh' `"\label{`marker'}"' _n if `"`size'"'!="" file write `fh' `"{\\`size'"' _n ****** ** \begin{tabularx} ****** file write `fh' "\begin{tabularx}{`width'}{`align'}" _n(2) * Create double-line or thick line, depending on booktabs file write `fh' "\toprule" _n ******** ** Header, if specified. headerlines, autonumber, and colnames all stack together ******** if "`autonumber'"!="" qui file write `fh' "`header_autonumber'" _n if `"`header_headerlines'"'!="" qui file write `fh' "`header_headerlines'" _n if "`header_colnames'"!="" qui file write `fh' "`header_colnames'" _n * Only write out a horizontal line if there is a header if `"`header_headerlines'`autonumber'`header_colnames'"'!="" qui file write `fh' "`horiz_line'`headersep'" _n file close `fh' ******************************************************************************************** ***** Write out dataset (body of table) to "data1", then convert to "data2" ******************************************************************************************** * If hlines() option is specified, need to split up dataset and insert hlines. Write out using -file write- commands. if "`hlines'"!="" { tempvar dummy rownum touse tempfile tmp qui gen byte `touse' = 1 `if' `in' qui replace `touse' = 0 if mi(`touse') * Generate table rownumbers qui gen byte `dummy' = 1 qui gen `rownum' = sum(`dummy') `if' `in' * Outsheet the groups one by one tokenize `hlines' forval grp = 1/`=`num_hlines'+1' { * First group if `grp'==1 { qui outsheet `varlist' if `touse'==1 & inrange(`rownum',1,``grp'') using "`data1'", replace delimiter(`delimiter') nonames noquote `nolabel' } * Rest of groups else { * Append hline qui file open `fh' using "`data1'", write append file write `fh' "`horiz_line' " qui file close `fh' if `grp' == `=`num_hlines'+1' { local g1 = ``=`grp'-1'' + 1 // This allows for double/triple lines etc since it will make inrange(x+1,x) local g2 . } else { local g1 = ``=`grp'-1'' + 1 local g2 ``grp'' } * Append next group qui outsheet `varlist' if `touse'==1 & inrange(`rownum',`g1',`g2') using "`tmp'", replace delimiter(`delimiter') nonames noquote `nolabel' appendfile "`tmp'" "`data1'" } } } * Else just outsheet the dataset else qui outsheet `varlist' `if' `in' using "`data1'", replace delimiter(`delimiter') nonames noquote `nolabel' filefilter "`data1'" "`data2'", from("`eol_char'") to(" \BStabularnewline`rowsep'`eol_char'") replace ******************************************************************************************** ***** Write out table bottom to "end_file" ******************************************************************************************** * Close out tabularx (with footnote spacer, if footnote present) qui file open `fh' using "`end_file'", write `replace' file write `fh' `"\bottomrule `footnotespace'"' _n(2) file write `fh' "\end{tabularx}" _n * Table footnote, if specified if `"`footnote'"'!="" file write `fh' `"\\ \parbox{`footnotewidth'}{\\`footnotesize' `footnote'}"' _n * Close out table if `"`size'"'!="" file write `fh' "}" _n if "`sw'"!="" file write `fh' "%TCIMACRO{\TeXButton{E}{\end{table}}}%" _n if "`sw'"!="" file write `fh' "%BeginExpansion" _n file write `fh' "\end{table}" _n if "`sw'"!="" file write `fh' "%EndExpansion" _n * Footlines option if `"`footlines'"' != "" { tokenize `"`footlines'"' while `"`1'"' != "" { file write `fh' `"`1'"' _n macro shift } } if `"`geometry'"'!="" file write `fh' "\restoregeometry" _n if "`landscape'"!="" file write `fh' "\end{landscape}" _n * End the tex document if "`frag'"=="" file write `fh' "\end{document}" _n * Close the bottom file file close `fh' ******************************************************************************************** ***** Append table start ("using"), data2, and end_file together ******************************************************************************************** if "`dataonly'"!="" copy "`data2'" "`using'", public replace else { appendfile "`data2'" "`using'" appendfile "`end_file'" "`using'" } * Return vars to original state if "`renamed'"=="yes" { * Variables foreach v of local varlist { qui drop `v' qui ren ``v'temp' `v' } } end **EOF