*! version 2.2.0 29jul2026 program define surveye, rclass version 16.0 if `"`0'"' == "" { display as error "surveye requires a questionnaire HTML file" display as error "Try {stata help surveye:help surveye}." exit 198 } gettoken subcmd rest : 0 local subcmd = lower(`"`subcmd'"') if `"`subcmd'"' == "describe" { _surveye_describe `rest' return add exit } if `"`subcmd'"' == "demo" { _surveye_demo `rest' return add exit } if `"`subcmd'"' == "build" { _surveye_main `rest' return add exit } _surveye_main `0' return add end program define _surveye_main, rclass version 16.0 syntax [varlist(default=none max=120000)] [if] [in] using/ /// [aweight fweight iweight pweight], /// SAVing(string) /// [ REPLACE OPEN /// TITle(string) SUBTitle(string) BYLine(string) /// QUESTions(string asis) /// SECTions(numlist integer >0) SECTIONMatch(string) /// EXclude(string asis) FILters(varlist) HIGHlights(varlist) /// KEYMessages(string) CUSTOMSections(string) /// CUSTOMVars(varlist) ADDToSections(string) /// VARGroups(string) NOAUTOGroups UNGROUPVars(varlist) /// DISCrete(varlist) CONTinuous(varlist) NOAUTODISCrete /// COMPARE(varlist) COMPAREBy(varname) /// COMPARETitle(string) COMPARELevels(string) /// USDVars(varlist) USDRate(numlist min=1 max=1) /// CURRENCY(string) /// TABLEBy(varname) TABLEVars(varlist) /// TABLESTats(string asis) TABLELAbels(string asis) /// TABLETItle(string) TABLESUbtitle(string) /// TABLETOtal(string) TABLEWeightLabel(string) /// BARs(varlist) DONUTs(varlist) HISTograms(varlist) /// MAXCategories(integer 12) MAXPanels(integer 100) /// MISSingcodes(numlist missingokay) /// LATitude(varname numeric) LONGitude(varname numeric) /// COUNTRY(string) BOUNDaries(string) /// MAPLevel(string) MAPTYpe(string) BASEMap(string) MAPBY(varname) /// MAPTItle(string) LOGO(string) THEME(string) DENSity(string) /// BACKGround(string) TYPOGraphy(string) CORNers(string) /// SHADow(string) MOTion(string) PAGEWidth(integer 0) /// UILANGuage(string) DIRection(string) /// NOTE(string) SOURCE(string) DISCLAIMer(string) /// CI LEVel(numlist min=1 max=1) STRICT DIAGNostics(string) SHOWEmpty ] // ---------- Files and mutually dependent options ---------- capture confirm file `"`using'"' if _rc { display as error "surveye: questionnaire file not found" display as error `" `using'"' exit 601 } if strtrim(`"`saving'"') == "" { display as error "surveye: saving() requires a filename" exit 198 } if !regexm(lower(`"`saving'"'), "[.]html?$") { local saving `"`saving'.html"' } if `"`replace'"' == "" { capture confirm new file `"`saving'"' if _rc { display as error `"surveye: output file already exists: `saving'"' display as error "Specify {bf:replace} or choose another saving() filename." exit 602 } } if `"`boundaries'"' != "" { capture confirm file `"`boundaries'"' if _rc { display as error `"surveye: boundaries file not found: `boundaries'"' exit 601 } } if `"`logo'"' != "" { capture confirm file `"`logo'"' if _rc { display as error `"surveye: logo file not found: `logo'"' exit 601 } } local qkey = ustrlower(strtrim(`"`using'"')) local outkey = ustrlower(strtrim(`"`saving'"')) local diagkey = ustrlower(strtrim(`"`diagnostics'"')) if `"`outkey'"' == `"`qkey'"' { display as error "surveye: saving() may not overwrite the questionnaire" exit 198 } if `"`diagkey'"' != "" & inlist(`"`diagkey'"', `"`qkey'"', `"`outkey'"') { display as error "surveye: diagnostics() must differ from the questionnaire and saving()" exit 198 } if `"`boundaries'"' != "" & `"`diagkey'"' == ustrlower(strtrim(`"`boundaries'"')) { display as error "surveye: diagnostics() may not overwrite boundaries()" exit 198 } if `"`logo'"' != "" & `"`diagkey'"' == ustrlower(strtrim(`"`logo'"')) { display as error "surveye: diagnostics() may not overwrite logo()" exit 198 } if `"`diagnostics'"' != "" & `"`replace'"' == "" { capture confirm new file `"`diagnostics'"' if _rc { display as error `"surveye: diagnostics file already exists: `diagnostics'"' display as error "Specify {bf:replace} or choose another diagnostics() filename." exit 602 } } if `"`sections'"' != "" & `"`sectionmatch'"' != "" { display as error "surveye: sections() and sectionmatch() may not be combined" exit 198 } if `"`customsections'"' != "" & `"`addtosections'"' != "" { display as error "surveye: customsections() and addtosections() may not be combined" exit 198 } if (`"`latitude'"' == "") != (`"`longitude'"' == "") { display as error "surveye: latitude() and longitude() must be specified together" exit 198 } local maprequested = (`"`latitude'"' != "") if !`maprequested' & /// (`"`country'`boundaries'`maplevel'`maptype'`basemap'`mapby'`maptitle'"' != "") { display as error "surveye: map options require latitude() and longitude()" exit 198 } if `maprequested' & strtrim(`"`country'"') == "" { display as error "surveye: country() is required for a GPS map" display as error "Specify a country name, ISO-2 code, or ISO-3 code; for example, country(Kenya)." exit 198 } if `maprequested' & `"`maptype'"' == "" local maptype "points" if `maprequested' & `"`basemap'"' == "" local basemap "google_hybrid" if `"`maptype'"' != "" & !inlist(lower(`"`maptype'"'), "cluster", "heat", "points") { display as error "surveye: maptype() must be cluster, heat, or points" exit 198 } if `"`basemap'"' != "" & !inlist(lower(`"`basemap'"'), /// "google_hybrid", "google_sat", "google_road", "osm") { display as error "surveye: basemap() must be google_hybrid, google_sat, google_road, or osm" exit 198 } if `"`maplevel'"' != "" & !inlist(lower(`"`maplevel'"'), "country", "admin2") { display as error "surveye: maplevel() must be country or admin2" exit 198 } if `maxcategories' < 2 | `maxcategories' > 200 { display as error "surveye: maxcategories() must be between 2 and 200" exit 198 } if `maxpanels' < 0 | `maxpanels' > 5000 { display as error "surveye: maxpanels() must be between 0 and 5,000" exit 198 } if `"`level'"' != "" & `"`ci'"' == "" { display as error "surveye: level() requires ci" exit 198 } if `"`level'"' == "" local level 95 if missing(`level') | `level' <= 50 | `level' >= 100 { display as error "surveye: level() must be greater than 50 and less than 100" exit 198 } local appearance_theme = lower(strtrim(`"`theme'"')) if `"`appearance_theme'"' == "" local appearance_theme "editorial" local engine_theme `"`appearance_theme'"' if `"`appearance_theme'"' == "editorial" local engine_theme "worldbank" if !inlist(`"`appearance_theme'"', "editorial", "worldbank", "clean", "forest", "dark") { display as error "surveye: theme() must be editorial, worldbank, clean, forest, or dark" exit 198 } local background = lower(strtrim(`"`background'"')) local typography = lower(strtrim(`"`typography'"')) local corners = lower(strtrim(`"`corners'"')) local shadow = lower(strtrim(`"`shadow'"')) local motion = lower(strtrim(`"`motion'"')) if `"`background'"' == "" { if `"`appearance_theme'"' == "editorial" local background "glow" else local background "auto" } if `"`typography'"' == "" { if `"`appearance_theme'"' == "editorial" local typography "editorial" else local typography "auto" } if `"`corners'"' == "" { if `"`appearance_theme'"' == "editorial" local corners "rounded" else local corners "auto" } if `"`shadow'"' == "" { if `"`appearance_theme'"' == "editorial" local shadow "soft" else local shadow "auto" } if `"`motion'"' == "" { if `"`appearance_theme'"' == "editorial" local motion "subtle" else local motion "none" } if `pagewidth' == 0 & `"`appearance_theme'"' == "editorial" local pagewidth 1280 if !inlist(`"`background'"', "auto", "glow", "paper", "plain") { display as error "surveye: background() must be auto, glow, paper, or plain" exit 198 } if !inlist(`"`typography'"', "auto", "editorial", "modern", "system") { display as error "surveye: typography() must be auto, editorial, modern, or system" exit 198 } if !inlist(`"`corners'"', "auto", "rounded", "soft", "square") { display as error "surveye: corners() must be auto, rounded, soft, or square" exit 198 } if !inlist(`"`shadow'"', "auto", "soft", "lifted", "none") { display as error "surveye: shadow() must be auto, soft, lifted, or none" exit 198 } if !inlist(`"`motion'"', "none", "subtle", "reveal") { display as error "surveye: motion() must be none, subtle, or reveal" exit 198 } if `pagewidth' != 0 & (`pagewidth' < 960 | `pagewidth' > 2000) { display as error "surveye: pagewidth() must be 0 or between 960 and 2,000 pixels" exit 198 } if `"`density'"' != "" & !inlist(lower(`"`density'"'), "compact", "comfortable") { display as error "surveye: density() must be compact or comfortable" exit 198 } if `"`uilanguage'"' != "" { local uilanguage = lower(strtrim(`"`uilanguage'"')) if `"`uilanguage'"' == "en" local uilanguage "english" if `"`uilanguage'"' == "ar" local uilanguage "arabic" if `"`uilanguage'"' == "ur" local uilanguage "urdu" if !inlist(`"`uilanguage'"', "auto", "english", "arabic", "urdu") { display as error "surveye: uilanguage() must be auto, english, arabic, or urdu" exit 198 } } if `"`direction'"' != "" { local direction = lower(strtrim(`"`direction'"')) if !inlist(`"`direction'"', "auto", "ltr", "rtl") { display as error "surveye: direction() must be auto, ltr, or rtl" exit 198 } } local nhighlights : word count `highlights' if `nhighlights' > 6 { display as error "surveye: highlights() accepts at most six variables" exit 198 } // questions() names questionnaire questions rather than Stata columns. // This matters for multiselects exported only as question__code columns. local questionlist = subinstr(`"`questions'"', char(34), "", .) local questionlist = subinstr(strtrim(`"`questionlist'"'), ",", " ", .) local questionlist : list retokenize questionlist if strtrim(`"`questions'"') != "" & `"`questionlist'"' == "" { display as error "surveye: questions() requires at least one question name" exit 198 } foreach question of local questionlist { capture confirm names `question' if _rc { display as error `"surveye: invalid Survey Solutions question name in questions(): `question'"' display as error "Question names may contain letters, numbers, and underscores and may not use wildcards." exit 198 } } // exclude() accepts ordinary Stata varlists and logical questionnaire // names. The latter is needed when a multiselect exists only as q__# // expansion columns and no Stata variable named q exists. local excluderaw = subinstr(`"`exclude'"', char(34), "", .) local excluderaw = subinstr(strtrim(`"`excluderaw'"'), ",", " ", .) local excluderaw : list retokenize excluderaw local excludelist "" foreach item of local excluderaw { local expanded "" capture unab expanded : `item' if !_rc local excludelist `"`excludelist' `expanded'"' else { capture confirm names `item' if _rc { display as error `"surveye: invalid name or varlist element in exclude(): `item'"' exit 198 } local excludelist `"`excludelist' `item'"' } } local exclude : list uniq excludelist local selectedvars `"`varlist' `questionlist'"' local selectedvars : list uniq selectedvars // Related questionnaire items are grouped automatically by the engine. // Manual groups use the deliberately simple Title:: varlist grammar. Do // the Stata varlist expansion here so ranges and wildcards have their // usual Stata meaning and Java receives an unambiguous member list. local normalizedgroups "" local groupedvars "" local grouprest `"`macval(vargroups)'"' while strtrim(`"`macval(grouprest)'"') != "" { local pipe = strpos(`"`macval(grouprest)'"', "|") if `pipe' > 0 { local onegroup = strtrim(substr(`"`macval(grouprest)'"', 1, `pipe' - 1)) local grouprest = substr(`"`macval(grouprest)'"', `pipe' + 1, .) } else { local onegroup = strtrim(`"`macval(grouprest)'"') local grouprest "" } local divider = strpos(`"`macval(onegroup)'"', "::") if `divider' <= 1 { display as error "surveye: vargroups() groups must use Title:: varlist" display as error `"Invalid group: `macval(onegroup)'"' exit 198 } local grouptitle = strtrim(substr(`"`macval(onegroup)'"', 1, `divider' - 1)) local groupmembersraw = strtrim(substr(`"`macval(onegroup)'"', `divider' + 2, .)) if `"`macval(grouptitle)'"' == "" | `"`macval(groupmembersraw)'"' == "" { display as error "surveye: every vargroups() group requires a title and at least two variables" exit 198 } local expandedmembers "" capture unab expandedmembers : `groupmembersraw' if _rc { display as error `"surveye: invalid or unavailable varlist in vargroups(): `macval(groupmembersraw)'"' exit 198 } local ngroupmembers : word count `expandedmembers' if `ngroupmembers' < 2 { display as error `"surveye: group `macval(grouptitle)' requires at least two variables"' exit 198 } local duplicate : list groupedvars & expandedmembers if `"`duplicate'"' != "" { display as error `"surveye: a variable may appear in only one vargroups() group: `duplicate'"' exit 198 } local groupedvars `"`groupedvars' `expandedmembers'"' local groupedvars : list uniq groupedvars if `"`macval(normalizedgroups)'"' == "" /// local normalizedgroups `"`macval(grouptitle)'::`expandedmembers'"' else local normalizedgroups `"`macval(normalizedgroups)'|`macval(grouptitle)'::`expandedmembers'"' } local vargroups `"`macval(normalizedgroups)'"' local ungroupvars : list uniq ungroupvars local discrete : list uniq discrete local continuous : list uniq continuous local compare : list uniq compare local usdvars : list uniq usdvars local tablevars : list uniq tablevars local overlap : list groupedvars & ungroupvars if `"`overlap'"' != "" { display as error `"surveye: variables cannot be both manually grouped and ungrouped: `overlap'"' exit 198 } local overlap : list discrete & continuous if `"`overlap'"' != "" { display as error `"surveye: variables cannot be both discrete and continuous: `overlap'"' exit 198 } foreach typeopt in discrete continuous { foreach variable of local `typeopt' { capture confirm numeric variable `variable' if _rc { display as error `"surveye: `typeopt'(`variable') requires a numeric variable"' exit 109 } local variableformat : format `variable' if regexm(lower(strtrim(`"`variableformat'"')), "^%t[bcdwmqhy]") { display as error `"surveye: date/time variable `variable' may not be specified in `typeopt'()"' exit 198 } } } local overlap : list discrete & histograms if `"`overlap'"' != "" { display as error `"surveye: discrete() and histograms() give conflicting instructions for: `overlap'"' exit 198 } local overlap : list continuous & bars if `"`overlap'"' == "" local overlap : list continuous & donuts if `"`overlap'"' != "" { display as error `"surveye: continuous() conflicts with a categorical chart override for: `overlap'"' exit 198 } local ncompare : word count `compare' if `ncompare' > 12 { display as error "surveye: compare() accepts at most 12 variables" exit 198 } if `"`compare'"' != "" & `"`compareby'"' == "" { display as error "surveye: compare() requires compareby()" exit 198 } if `"`compare'"' == "" & /// (`"`compareby'"' != "" | `"`comparetitle'"' != "" | `"`comparelevels'"' != "") { display as error "surveye: compareby(), comparetitle(), and comparelevels() require compare()" exit 198 } if `"`compare'"' != "" { local overlap : list compare & compareby if `"`overlap'"' != "" { display as error "surveye: compareby() may not also be one of the compare() variables" exit 198 } } // ---------- Optional weight/USD switches and summary profile table ---------- // usdrate() is the number of local-currency units in one US dollar. The // dashboard keeps the original values in DATA and converts only at render // time, so switching currencies never changes the analysis sample. if `"`usdvars'"' != "" & `"`usdrate'"' == "" { display as error "surveye: usdvars() requires usdrate()" exit 198 } if `"`usdvars'"' == "" & `"`usdrate'"' != "" { display as error "surveye: usdrate() requires usdvars()" exit 198 } if `"`usdvars'"' == "" & `"`currency'"' != "" { display as error "surveye: currency() requires usdvars()" exit 198 } if `"`usdrate'"' != "" { local usdratevalue : word 1 of `usdrate' if missing(real(`"`usdratevalue'"')) | real(`"`usdratevalue'"') <= 0 { display as error "surveye: usdrate() must be greater than zero" exit 198 } } foreach variable of local usdvars { capture confirm numeric variable `variable' if _rc { display as error `"surveye: usdvars(`variable') requires a numeric variable"' exit 109 } local variableformat : format `variable' if regexm(lower(strtrim(`"`variableformat'"')), "^%t[bcdwmqhy]") { display as error `"surveye: date/time variable `variable' may not be specified in usdvars()"' exit 198 } } local hastable = (`"`tableby'"' != "" | `"`tablevars'"' != "") if (`"`tableby'"' == "") != (`"`tablevars'"' == "") { display as error "surveye: tableby() and tablevars() must be specified together" exit 198 } if !`hastable' & (`"`macval(tablestats)'"' != "" | /// `"`macval(tablelabels)'"' != "" | `"`tabletitle'"' != "" | /// `"`tablesubtitle'"' != "" | `"`tabletotal'"' != "" | /// `"`tableweightlabel'"' != "") { display as error "surveye: table options require tableby() and tablevars()" exit 198 } if `hastable' { local overlap : list tablevars & tableby if `"`overlap'"' != "" { display as error "surveye: tableby() may not also appear in tablevars()" exit 198 } // string asis preserves Stata's syntax quotes. Remove one matching // outer pair before validating the pipe-delimited entries. local tablestatsclean = strtrim(`"`macval(tablestats)'"') if strlen(`"`macval(tablestatsclean)'"') >= 2 & /// substr(`"`macval(tablestatsclean)'"', 1, 1) == char(34) & /// substr(`"`macval(tablestatsclean)'"', strlen(`"`macval(tablestatsclean)'"'), 1) == char(34) { local tablestatsclean = substr(`"`macval(tablestatsclean)'"', 2, strlen(`"`macval(tablestatsclean)'"') - 2) } local tablelabelsclean = strtrim(`"`macval(tablelabels)'"') if strlen(`"`macval(tablelabelsclean)'"') >= 2 & /// substr(`"`macval(tablelabelsclean)'"', 1, 1) == char(34) & /// substr(`"`macval(tablelabelsclean)'"', strlen(`"`macval(tablelabelsclean)'"'), 1) == char(34) { local tablelabelsclean = substr(`"`macval(tablelabelsclean)'"', 2, strlen(`"`macval(tablelabelsclean)'"') - 2) } local ntablevars : word count `tablevars' local normalizedstats "" local statrest `"`macval(tablestatsclean)'"' local statcount = 0 while strtrim(`"`macval(statrest)'"') != "" { local pipe = strpos(`"`macval(statrest)'"', "|") if `pipe' > 0 { local onestat = strtrim(substr(`"`macval(statrest)'"', 1, `pipe' - 1)) local statrest = substr(`"`macval(statrest)'"', `pipe' + 1, .) } else { local onestat = strtrim(`"`macval(statrest)'"') local statrest "" } if `"`macval(onestat)'"' == "" { display as error "surveye: tablestats() may not contain an empty entry" exit 198 } local statkey = lower(`"`macval(onestat)'"') local validstat = inlist(`"`statkey'"', "auto", "share", "mean", "median", "sum") if !`validstat' & regexm(`"`statkey'"', "^share:.+$") local validstat = 1 if !`validstat' { display as error `"surveye: invalid tablestats() entry: `macval(onestat)'"' display as error "Use auto, share, share:, mean, median, or sum." exit 198 } local ++statcount if `statcount' > `ntablevars' { display as error "surveye: tablestats() must have one pipe-delimited entry per tablevars() variable" exit 198 } local statvariable : word `statcount' of `tablevars' if inlist(`"`statkey'"', "mean", "median", "sum") { capture confirm numeric variable `statvariable' if _rc { display as error `"surveye: tablestats(`macval(onestat)') requires numeric variable `statvariable'"' exit 109 } } if `"`macval(normalizedstats)'"' == "" local normalizedstats `"`macval(onestat)'"' else local normalizedstats `"`macval(normalizedstats)'|`macval(onestat)'"' } if `"`macval(tablestatsclean)'"' != "" & `statcount' != `ntablevars' { display as error "surveye: tablestats() must have one pipe-delimited entry per tablevars() variable" exit 198 } local tablestats `"`macval(normalizedstats)'"' local labelrest `"`macval(tablelabelsclean)'"' local labelcount = 0 while strtrim(`"`macval(labelrest)'"') != "" { local pipe = strpos(`"`macval(labelrest)'"', "|") if `pipe' > 0 { local onelabel = strtrim(substr(`"`macval(labelrest)'"', 1, `pipe' - 1)) local labelrest = substr(`"`macval(labelrest)'"', `pipe' + 1, .) } else { local onelabel = strtrim(`"`macval(labelrest)'"') local labelrest "" } if `"`macval(onelabel)'"' == "" { display as error "surveye: tablelabels() may not contain an empty entry" exit 198 } local ++labelcount } if `"`macval(tablelabelsclean)'"' != "" & `labelcount' != `ntablevars' { display as error "surveye: tablelabels() must have one pipe-delimited entry per tablevars() variable" exit 198 } local tablelabels `"`macval(tablelabelsclean)'"' } local overlap : list groupedvars & exclude if `"`overlap'"' == "" local overlap : list ungroupvars & exclude if `"`overlap'"' == "" local overlap : list discrete & exclude if `"`overlap'"' == "" local overlap : list continuous & exclude if `"`overlap'"' == "" local overlap : list compare & exclude if `"`overlap'"' != "" { display as error `"surveye: grouping, type, or comparison options cannot use excluded variables: `overlap'"' exit 198 } // With a focused selection, presentation options may only refer to a // selected questionnaire item or a variable declared in customvars(). if `"`selectedvars'"' != "" { local allowedselected `"`selectedvars' `customvars'"' local allowedselected : list uniq allowedselected foreach selectionopt in groupedvars ungroupvars discrete continuous compare { local notselected : list `selectionopt' - allowedselected if `"`notselected'"' != "" { local optionname "`selectionopt'()" if `"`selectionopt'"' == "groupedvars" local optionname "vargroups()" display as error `"surveye: `optionname' contains variable(s) not selected by the main varlist, questions(), or customvars(): `notselected'"' exit 198 } } } local overlap : list customvars & exclude if `"`overlap'"' != "" { display as error `"surveye: customvars() variables cannot also be excluded: `overlap'"' exit 198 } local strictflag = (`"`strict'"' != "") local showemptyflag = (`"`showempty'"' != "") local replaceflag = (`"`replace'"' != "") local showciflag = (`"`ci'"' != "") local noautogroupsflag = (`"`noautogroups'"' != "") local noautodiscreteflag = (`"`noautodiscrete'"' != "") // Explicit chart-type overrides must be unambiguous. local overlap : list bars & donuts if `"`overlap'"' == "" local overlap : list bars & histograms if `"`overlap'"' == "" local overlap : list donuts & histograms if `"`overlap'"' != "" { display as error `"surveye: chart type specified more than once for: `overlap'"' exit 198 } local chartvars `"`bars' `donuts' `histograms'"' local chartvars : list uniq chartvars local overlap : list chartvars & exclude if `"`overlap'"' != "" { display as error `"surveye: variables cannot be both charted and excluded: `overlap'"' exit 198 } if `"`selectedvars'"' != "" { local overlap : list selectedvars & exclude if `"`overlap'"' != "" { display as error `"surveye: variables cannot be both selected and excluded: `overlap'"' exit 198 } local allowedcharts `"`selectedvars' `customvars'"' local allowedcharts : list uniq allowedcharts foreach chartopt in bars donuts histograms { local notselected : list `chartopt' - allowedcharts if `"`notselected'"' != "" { display as error `"surveye: `chartopt'() contains variable(s) not selected by the main varlist, questions(), or customvars(): `notselected'"' exit 198 } } } // ---------- Analysis sample and weight ---------- if c(k) == 0 { display as error "surveye: the dataset in memory has no variables" exit 102 } // marksample creates the temporary marker and stores its name in the // caller's local macro. Pass the macro name, not an expanded tempvar. marksample touse, novarlist local weightvar "" local weighttype "" local weighted = 0 if `"`weight'"' != "" { local weighttype `"`weight'"' local rawweight = strtrim(subinstr(`"`exp'"', "=", "", 1)) capture unab weightvar : `rawweight' if _rc { display as error "surveye: the weight must be a single numeric variable" display as error `" received: `exp'"' exit 198 } local nweight : word count `weightvar' if `nweight' != 1 { display as error "surveye: the weight must be a single numeric variable" display as error `" received: `exp'"' exit 198 } capture confirm numeric variable `weightvar' if _rc { display as error `"surveye: weight variable `weightvar' must be numeric"' exit 109 } quietly count if `touse' & `weightvar' < 0 if r(N) { display as error `"surveye: weight variable `weightvar' contains negative values"' exit 402 } if `"`weighttype'"' == "fweight" { quietly count if `touse' & !missing(`weightvar') & `weightvar' != floor(`weightvar') if r(N) { display as error `"surveye: fweight variable `weightvar' must contain integers"' exit 401 } } // Match ordinary Stata command behavior: missing and zero weights do // not belong to the analysis sample. This also keeps zero-weight rows // out of raw counts, filters, highlights, and GPS layers. quietly replace `touse' = 0 if missing(`weightvar') | `weightvar' == 0 // Importance weights have no general sampling interpretation. Keep // their descriptive weighted point estimates, but never imply that a // Wilson/Kish interval is inferentially meaningful. if `"`weighttype'"' == "iweight" & `showciflag' { display as text "note: confidence intervals are disabled for iweights" local showciflag = 0 } local weighted = 1 } quietly count if `touse' local sample_N = r(N) if `sample_N' == 0 { display as error "surveye: no observations remain after if/in and weight restrictions" exit 2000 } // ---------- Export the smallest rectangular data slice needed ---------- if `"`selectedvars'"' == "" { unab exportvars : _all } else { local exportvars `"`varlist'"' // Add exact columns and Survey Solutions multiselect expansions for // logical names supplied through questions(). The double-underscore // form is canonical. A single underscore is accepted only with an // all-numeric suffix, avoiding accidental prefix matches. foreach question of local questionlist { capture confirm variable `question', exact if !_rc local exportvars `"`exportvars' `question'"' local doubleexpanded "" capture unab doubleexpanded : `question'__* if !_rc local exportvars `"`exportvars' `doubleexpanded'"' local singlecandidates "" capture unab singlecandidates : `question'_* if !_rc { local prefixbytes = strlen(`"`question'_"') foreach candidate of local singlecandidates { local suffix = substr(`"`candidate'"', `prefixbytes' + 1, .) if regexm(`"`suffix'"', "^[0-9]+$") { local exportvars `"`exportvars' `candidate'"' } } } } } local exportvars `"`exportvars' `customvars' `filters' `highlights' `latitude' `longitude' `mapby' `compare' `compareby' `usdvars' `tableby' `tablevars' `weightvar'"' local exportvars : list uniq exportvars if `"`exportvars'"' == "" { if !`showemptyflag' { display as error "surveye: none of the names in questions() matched data columns" display as error "For a multiselect question q, expected q or expansion columns such as q__1 and q__2." exit 111 } } // Keep one nonmissing internal sentinel in the CSV. Otherwise an // observation missing on every selected field becomes a blank CSV record // and is indistinguishable from an empty line to a standards-based parser. // The sentinel is not part of selectedvars, so it is never charted. local exportvars `"`exportvars' `touse'"' local exportvars : list uniq exportvars tempfile datafile configfile statusfile preserve quietly keep if `touse' quietly keep `exportvars' capture noisily export delimited `exportvars' using `"`datafile'"', /// replace nolabel quote local export_rc = _rc restore if `export_rc' { display as error "surveye: could not create the temporary UTF-8 data export" exit `export_rc' } // Config values are one UTF-8 keyvalue record per line. tempname cfg file open `cfg' using `"`configfile'"', write text replace _surveye_cfgline `cfg' mode `"build"' _surveye_cfgline `cfg' questionnaire `"`macval(using)'"' _surveye_cfgline `cfg' data `"`macval(datafile)'"' _surveye_cfgline `cfg' output `"`macval(saving)'"' _surveye_cfgline `cfg' status `"`macval(statusfile)'"' _surveye_cfgline `cfg' variables `"`macval(selectedvars)'"' _surveye_cfgline `cfg' title `"`macval(title)'"' _surveye_cfgline `cfg' subtitle `"`macval(subtitle)'"' _surveye_cfgline `cfg' byline `"`macval(byline)'"' _surveye_cfgline `cfg' sections `"`macval(sections)'"' _surveye_cfgline `cfg' sectionmatch `"`macval(sectionmatch)'"' _surveye_cfgline `cfg' exclude `"`macval(exclude)'"' _surveye_cfgline `cfg' filters `"`macval(filters)'"' _surveye_cfgline `cfg' highlights `"`macval(highlights)'"' _surveye_cfgline `cfg' keymessages `"`macval(keymessages)'"' _surveye_cfgline `cfg' customsections `"`macval(customsections)'"' _surveye_cfgline `cfg' customvars `"`macval(customvars)'"' _surveye_cfgline `cfg' addtosections `"`macval(addtosections)'"' _surveye_cfgline `cfg' vargroups `"`macval(vargroups)'"' _surveye_cfgline `cfg' noautogroups `"`macval(noautogroupsflag)'"' _surveye_cfgline `cfg' ungroupvars `"`macval(ungroupvars)'"' _surveye_cfgline `cfg' discrete `"`macval(discrete)'"' _surveye_cfgline `cfg' continuous `"`macval(continuous)'"' _surveye_cfgline `cfg' noautodiscrete `"`macval(noautodiscreteflag)'"' _surveye_cfgline `cfg' compare `"`macval(compare)'"' _surveye_cfgline `cfg' compareby `"`macval(compareby)'"' _surveye_cfgline `cfg' comparetitle `"`macval(comparetitle)'"' _surveye_cfgline `cfg' comparelevels `"`macval(comparelevels)'"' _surveye_cfgline `cfg' usdvars `"`macval(usdvars)'"' _surveye_cfgline `cfg' usdrate `"`macval(usdrate)'"' _surveye_cfgline `cfg' currency `"`macval(currency)'"' _surveye_cfgline `cfg' tableby `"`macval(tableby)'"' _surveye_cfgline `cfg' tablevars `"`macval(tablevars)'"' _surveye_cfgline `cfg' tablestats `"`macval(tablestats)'"' _surveye_cfgline `cfg' tablelabels `"`macval(tablelabels)'"' _surveye_cfgline `cfg' tabletitle `"`macval(tabletitle)'"' _surveye_cfgline `cfg' tablesubtitle `"`macval(tablesubtitle)'"' _surveye_cfgline `cfg' tabletotal `"`macval(tabletotal)'"' _surveye_cfgline `cfg' tableweightlabel `"`macval(tableweightlabel)'"' _surveye_cfgline `cfg' bars `"`macval(bars)'"' _surveye_cfgline `cfg' donuts `"`macval(donuts)'"' _surveye_cfgline `cfg' histograms `"`macval(histograms)'"' _surveye_cfgline `cfg' maxcategories `"`macval(maxcategories)'"' _surveye_cfgline `cfg' maxpanels `"`macval(maxpanels)'"' _surveye_cfgline `cfg' missingcodes `"`macval(missingcodes)'"' _surveye_cfgline `cfg' latitude `"`macval(latitude)'"' _surveye_cfgline `cfg' longitude `"`macval(longitude)'"' _surveye_cfgline `cfg' country `"`macval(country)'"' _surveye_cfgline `cfg' boundaries `"`macval(boundaries)'"' _surveye_cfgline `cfg' maplevel `"`macval(maplevel)'"' _surveye_cfgline `cfg' maptype `"`macval(maptype)'"' _surveye_cfgline `cfg' basemap `"`macval(basemap)'"' _surveye_cfgline `cfg' mapby `"`macval(mapby)'"' _surveye_cfgline `cfg' maptitle `"`macval(maptitle)'"' _surveye_cfgline `cfg' logo `"`macval(logo)'"' _surveye_cfgline `cfg' theme `"`macval(engine_theme)'"' _surveye_cfgline `cfg' density `"`macval(density)'"' _surveye_cfgline `cfg' uilanguage `"`macval(uilanguage)'"' _surveye_cfgline `cfg' direction `"`macval(direction)'"' _surveye_cfgline `cfg' note `"`macval(note)'"' _surveye_cfgline `cfg' source `"`macval(source)'"' if `"`disclaimer'"' != "" /// _surveye_cfgline `cfg' disclaimer `"`macval(disclaimer)'"' _surveye_cfgline `cfg' strict `"`macval(strictflag)'"' _surveye_cfgline `cfg' diagnostics `"`macval(diagnostics)'"' _surveye_cfgline `cfg' showempty `"`macval(showemptyflag)'"' _surveye_cfgline `cfg' replace `"`macval(replaceflag)'"' _surveye_cfgline `cfg' weight `"`macval(weightvar)'"' _surveye_cfgline `cfg' weighttype `"`macval(weighttype)'"' _surveye_cfgline `cfg' showci `"`macval(showciflag)'"' _surveye_cfgline `cfg' cilevel `"`macval(level)'"' // Preserve Stata labels/types anywhere a data-only variable can surface, // including panels, filters, highlights, and map groups. Questionnaire // metadata remains authoritative when the same name is in the HTML. // selectedvars may contain a logical multiselect question name that has no // physical Stata column, so add only selected names that actually exist. // This lets data-only variables selected through questions() retain a // meaningful Stata label without breaking expanded multiselects. local metadatavars `"`customvars' `filters' `highlights' `mapby' `compareby' `usdvars' `tableby' `tablevars'"' foreach metaselected of local selectedvars { capture confirm variable `metaselected' if !_rc local metadatavars `"`metadatavars' `metaselected'"' } local metadatavars : list uniq metadatavars foreach metavariable of local metadatavars { _surveye_custommeta `cfg' `metavariable' `touse' `maxcategories' } file close `cfg' _surveye_invoke `"`configfile'"' `"`statusfile'"' `"`diagnostics'"' mata: st_local("outfile", st_global("r(filename)")) mata: st_local("outtitle", st_global("r(title)")) local outN = r(N) local outsections = r(k_sections) local outcharts = r(k_charted) mata: st_local("outfilters", st_global("r(filters)")) local hasmap = r(has_map) local mapN = r(map_N) local mapmissing = r(map_missing) local mapoutside = r(map_outside) local outwarnings = r(warnings) mata: st_local("outquestionnaire", st_global("r(questionnaire)")) local statusoutfile `"`macval(outfile)'"' if `"`macval(outfile)'"' == "" { local outfile `"`macval(saving)'"' } _surveye_apply_appearance `"`macval(outfile)'"' `"`macval(appearance_theme)'"' /// `"`macval(background)'"' `"`macval(typography)'"' `"`macval(corners)'"' /// `"`macval(shadow)'"' `"`macval(motion)'"' `"`pagewidth'"' display as text _newline "{hline 72}" display as result "OK" as text " SurvEye dashboard created" if `"`macval(outtitle)'"' != "" display as text " Title " as result `"`macval(outtitle)'"' if !missing(`outN') display as text " Observations " as result %12.0fc `outN' if !missing(`outsections') display as text " Sections " as result %12.0fc `outsections' if !missing(`outcharts') display as text " Indicators " as result %12.0fc `outcharts' if `"`macval(outfilters)'"' != "" display as text " Filters " as result `"`macval(outfilters)'"' if `hasmap' == 1 & !missing(`mapN') & /// !missing(`mapmissing') & !missing(`mapoutside') { display as text " GPS map " as result %12.0fc `mapN' /// as text " valid; " as result %9.0fc `mapmissing' as text " omitted; " /// as result %9.0fc `mapoutside' as text " outside" } if !missing(`outwarnings') & `outwarnings' > 0 { display as text " Flags " as result %12.0fc `outwarnings' /// as text " (details are shown inside the dashboard)" } display as text " Saved " as result `"`macval(outfile)'"' display as text `" {browse "`macval(outfile)'":Open dashboard}"' display as text "{hline 72}" if `"`open'"' != "" { capture noisily view browse `"`macval(outfile)'"' if _rc { display as text "Flag The dashboard was created, but Stata could not open the browser." } } // Return results last so browser handling cannot overwrite r(). _surveye_read_status using `"`statusfile'"' return add if `"`macval(statusoutfile)'"' == "" { return local output `"`macval(saving)'"' return local filename `"`macval(saving)'"' } if `"`macval(outquestionnaire)'"' == "" return local questionnaire `"`macval(using)'"' if missing(`outN') return scalar N = `sample_N' return scalar sample_N = `sample_N' return scalar weighted = `weighted' return local package_version "2.2.0" end program define _surveye_apply_appearance version 16.0 args output theme background typography corners shadow motion pagewidth local jarname "surveye_2_2_0.jar" capture findfile `jarname' if _rc { display as error "`jarname' is not installed on the Stata ado-path" display as error "Reinstall the complete package, type {bf:discard}, and restart Stata." exit 601 } capture noisily javacall org.worldbank.surveye.AppearancePlugin apply, /// jars(`jarname') args(`"`output'"' `"`theme'"' `"`background'"' /// `"`typography'"' `"`corners'"' `"`shadow'"' `"`motion'"' `"`pagewidth'"') local appearance_rc = _rc if `appearance_rc' { display as error "surveye: the dashboard was created, but its appearance layer could not be applied" display as error "Reinstall the complete package, type {bf:discard}, and restart Stata." exit `appearance_rc' } end program define _surveye_describe, rclass version 16.0 syntax using/ [, DETAIL STRICT DIAGNostics(string) REPLACE] capture confirm file `"`using'"' if _rc { display as error `"surveye: questionnaire file not found: `using'"' exit 601 } if `"`diagnostics'"' != "" & /// ustrlower(strtrim(`"`diagnostics'"')) == ustrlower(strtrim(`"`using'"')) { display as error "surveye: diagnostics() may not overwrite the questionnaire" exit 198 } if `"`diagnostics'"' != "" & `"`replace'"' == "" { capture confirm new file `"`diagnostics'"' if _rc { display as error `"surveye: diagnostics file already exists: `diagnostics'"' display as error "Specify {bf:replace} or choose another diagnostics() filename." exit 602 } } tempfile configfile statusfile local detailflag = (`"`detail'"' != "") local strictflag = (`"`strict'"' != "") local replaceflag = (`"`replace'"' != "") tempname cfg file open `cfg' using `"`configfile'"', write text replace _surveye_cfgline `cfg' mode `"describe"' _surveye_cfgline `cfg' questionnaire `"`macval(using)'"' _surveye_cfgline `cfg' status `"`macval(statusfile)'"' _surveye_cfgline `cfg' detail `"`macval(detailflag)'"' _surveye_cfgline `cfg' strict `"`macval(strictflag)'"' _surveye_cfgline `cfg' diagnostics `"`macval(diagnostics)'"' _surveye_cfgline `cfg' replace `"`macval(replaceflag)'"' file close `cfg' _surveye_invoke `"`configfile'"' `"`statusfile'"' `"`diagnostics'"' mata: st_local("outtitle", st_global("r(title)")) local outsections = r(k_sections) local outcharts = r(k_charted) mata: st_local("chartvars", st_global("r(chartvars)")) mata: st_local("sectionlist", st_global("r(sections)")) mata: st_local("filtercandidates", st_global("r(filter_candidates)")) mata: st_local("gpscandidates", st_global("r(gps_candidates)")) mata: st_local("engine", st_global("r(engine_version)")) local outwarnings = r(warnings) mata: st_local("outquestionnaire", st_global("r(questionnaire)")) display as text _newline "{hline 72}" display as result "OK" as text " Questionnaire read successfully" if `"`macval(outtitle)'"' != "" display as text " Title " as result `"`macval(outtitle)'"' if !missing(`outsections') display as text " Sections " as result %12.0fc `outsections' if !missing(`outcharts') display as text " Chartable items " as result %12.0fc `outcharts' if `"`macval(engine)'"' != "" display as text " Engine " as result `"`macval(engine)'"' if !missing(`outwarnings') /// display as text " Warnings " as result %12.0fc `outwarnings' if `"`detail'"' != "" & `"`macval(sectionlist)'"' != "" { display as text _newline "Questionnaire sections" local sectionrest `"`macval(sectionlist)'"' while `"`macval(sectionrest)'"' != "" { local pipe = strpos(`"`macval(sectionrest)'"', "|") if `pipe' > 0 { local onesection = substr(`"`macval(sectionrest)'"', 1, `pipe' - 1) local sectionrest = substr(`"`macval(sectionrest)'"', `pipe' + 1, .) } else { local onesection `"`macval(sectionrest)'"' local sectionrest "" } display as text `" `macval(onesection)'"' } } if `"`detail'"' != "" & `"`macval(chartvars)'"' != "" { display as text _newline "Chartable variables" display as text `"`macval(chartvars)'"' } if `"`detail'"' != "" & `"`macval(filtercandidates)'"' != "" { display as text _newline "Possible filters" display as text `"`macval(filtercandidates)'"' } if `"`detail'"' != "" & `"`macval(gpscandidates)'"' != "" { display as text _newline "Possible GPS variables" display as text `"`macval(gpscandidates)'"' } display as text "{hline 72}" _surveye_read_status using `"`statusfile'"' return add if `"`macval(outquestionnaire)'"' == "" return local questionnaire `"`macval(using)'"' return local package_version "2.2.0" end program define _surveye_demo, rclass version 16.0 syntax using/, SAVing(string) /// [ N(integer 240) SEED(integer 20260714) MAXPanels(integer 100) REPLACE OPEN /// TITle(string) SUBTitle(string) BYLine(string) LOGO(string) THEME(string) DENSity(string) /// BACKGround(string) TYPOGraphy(string) CORNers(string) /// SHADow(string) MOTion(string) PAGEWidth(integer 0) /// UILANGuage(string) DIRection(string) /// NOTE(string) SOURCE(string) DISCLAIMer(string) /// CI LEVel(numlist min=1 max=1) DIAGNostics(string) ] capture confirm file `"`using'"' if _rc { display as error `"surveye: questionnaire file not found: `using'"' exit 601 } if strtrim(`"`saving'"') == "" { display as error "surveye demo: saving() requires a filename" exit 198 } if `n' < 1 { display as error "surveye demo: n() must be at least 1" exit 198 } if `n' > 100000 { display as error "surveye demo: n() may not exceed 100,000" exit 198 } if `maxpanels' < 0 | `maxpanels' > 5000 { display as error "surveye demo: maxpanels() must be between 0 and 5,000" exit 198 } if `"`level'"' != "" & `"`ci'"' == "" { display as error "surveye demo: level() requires ci" exit 198 } if `"`level'"' == "" local level 95 if missing(`level') | `level' <= 50 | `level' >= 100 { display as error "surveye demo: level() must be greater than 50 and less than 100" exit 198 } if !regexm(lower(`"`saving'"'), "[.]html?$") local saving `"`saving'.html"' if `"`replace'"' == "" { capture confirm new file `"`saving'"' if _rc { display as error `"surveye: output file already exists: `saving'"' display as error "Specify {bf:replace} or choose another saving() filename." exit 602 } } if `"`logo'"' != "" { capture confirm file `"`logo'"' if _rc { display as error `"surveye: logo file not found: `logo'"' exit 601 } } local qkey = ustrlower(strtrim(`"`using'"')) local outkey = ustrlower(strtrim(`"`saving'"')) local diagkey = ustrlower(strtrim(`"`diagnostics'"')) if `"`outkey'"' == `"`qkey'"' { display as error "surveye: saving() may not overwrite the questionnaire" exit 198 } if `"`diagkey'"' != "" & inlist(`"`diagkey'"', `"`qkey'"', `"`outkey'"') { display as error "surveye: diagnostics() must differ from the questionnaire and saving()" exit 198 } if `"`logo'"' != "" & `"`diagkey'"' == ustrlower(strtrim(`"`logo'"')) { display as error "surveye: diagnostics() may not overwrite logo()" exit 198 } local appearance_theme = lower(strtrim(`"`theme'"')) if `"`appearance_theme'"' == "" local appearance_theme "editorial" local engine_theme `"`appearance_theme'"' if `"`appearance_theme'"' == "editorial" local engine_theme "worldbank" if !inlist(`"`appearance_theme'"', "editorial", "worldbank", "clean", "forest", "dark") { display as error "surveye: theme() must be editorial, worldbank, clean, forest, or dark" exit 198 } local background = lower(strtrim(`"`background'"')) local typography = lower(strtrim(`"`typography'"')) local corners = lower(strtrim(`"`corners'"')) local shadow = lower(strtrim(`"`shadow'"')) local motion = lower(strtrim(`"`motion'"')) if `"`background'"' == "" { if `"`appearance_theme'"' == "editorial" local background "glow" else local background "auto" } if `"`typography'"' == "" { if `"`appearance_theme'"' == "editorial" local typography "editorial" else local typography "auto" } if `"`corners'"' == "" { if `"`appearance_theme'"' == "editorial" local corners "rounded" else local corners "auto" } if `"`shadow'"' == "" { if `"`appearance_theme'"' == "editorial" local shadow "soft" else local shadow "auto" } if `"`motion'"' == "" { if `"`appearance_theme'"' == "editorial" local motion "subtle" else local motion "none" } if `pagewidth' == 0 & `"`appearance_theme'"' == "editorial" local pagewidth 1280 if !inlist(`"`background'"', "auto", "glow", "paper", "plain") { display as error "surveye: background() must be auto, glow, paper, or plain" exit 198 } if !inlist(`"`typography'"', "auto", "editorial", "modern", "system") { display as error "surveye: typography() must be auto, editorial, modern, or system" exit 198 } if !inlist(`"`corners'"', "auto", "rounded", "soft", "square") { display as error "surveye: corners() must be auto, rounded, soft, or square" exit 198 } if !inlist(`"`shadow'"', "auto", "soft", "lifted", "none") { display as error "surveye: shadow() must be auto, soft, lifted, or none" exit 198 } if !inlist(`"`motion'"', "none", "subtle", "reveal") { display as error "surveye: motion() must be none, subtle, or reveal" exit 198 } if `pagewidth' != 0 & (`pagewidth' < 960 | `pagewidth' > 2000) { display as error "surveye: pagewidth() must be 0 or between 960 and 2,000 pixels" exit 198 } if `"`density'"' != "" & !inlist(lower(`"`density'"'), "compact", "comfortable") { display as error "surveye: density() must be compact or comfortable" exit 198 } if `"`uilanguage'"' != "" { local uilanguage = lower(strtrim(`"`uilanguage'"')) if `"`uilanguage'"' == "en" local uilanguage "english" if `"`uilanguage'"' == "ar" local uilanguage "arabic" if `"`uilanguage'"' == "ur" local uilanguage "urdu" if !inlist(`"`uilanguage'"', "auto", "english", "arabic", "urdu") { display as error "surveye: uilanguage() must be auto, english, arabic, or urdu" exit 198 } } if `"`direction'"' != "" { local direction = lower(strtrim(`"`direction'"')) if !inlist(`"`direction'"', "auto", "ltr", "rtl") { display as error "surveye: direction() must be auto, ltr, or rtl" exit 198 } } if `"`diagnostics'"' != "" & `"`replace'"' == "" { capture confirm new file `"`diagnostics'"' if _rc { display as error `"surveye: diagnostics file already exists: `diagnostics'"' display as error "Specify {bf:replace} or choose another diagnostics() filename." exit 602 } } local replaceflag = (`"`replace'"' != "") local showciflag = (`"`ci'"' != "") tempfile configfile statusfile tempname cfg file open `cfg' using `"`configfile'"', write text replace _surveye_cfgline `cfg' mode `"demo"' _surveye_cfgline `cfg' questionnaire `"`macval(using)'"' _surveye_cfgline `cfg' output `"`macval(saving)'"' _surveye_cfgline `cfg' status `"`macval(statusfile)'"' _surveye_cfgline `cfg' demon `"`macval(n)'"' _surveye_cfgline `cfg' seed `"`macval(seed)'"' _surveye_cfgline `cfg' maxpanels `"`macval(maxpanels)'"' _surveye_cfgline `cfg' replace `"`macval(replaceflag)'"' _surveye_cfgline `cfg' title `"`macval(title)'"' _surveye_cfgline `cfg' subtitle `"`macval(subtitle)'"' _surveye_cfgline `cfg' byline `"`macval(byline)'"' _surveye_cfgline `cfg' logo `"`macval(logo)'"' _surveye_cfgline `cfg' theme `"`macval(engine_theme)'"' _surveye_cfgline `cfg' density `"`macval(density)'"' _surveye_cfgline `cfg' uilanguage `"`macval(uilanguage)'"' _surveye_cfgline `cfg' direction `"`macval(direction)'"' _surveye_cfgline `cfg' showci `"`macval(showciflag)'"' _surveye_cfgline `cfg' cilevel `"`macval(level)'"' _surveye_cfgline `cfg' note `"`macval(note)'"' _surveye_cfgline `cfg' source `"`macval(source)'"' if `"`disclaimer'"' != "" /// _surveye_cfgline `cfg' disclaimer `"`macval(disclaimer)'"' _surveye_cfgline `cfg' diagnostics `"`macval(diagnostics)'"' file close `cfg' _surveye_invoke `"`configfile'"' `"`statusfile'"' `"`diagnostics'"' mata: st_local("outfile", st_global("r(filename)")) mata: st_local("outtitle", st_global("r(title)")) local outN = r(N) local outwarnings = r(warnings) mata: st_local("outquestionnaire", st_global("r(questionnaire)")) local statusoutfile `"`macval(outfile)'"' if `"`macval(outfile)'"' == "" { local outfile `"`macval(saving)'"' } _surveye_apply_appearance `"`macval(outfile)'"' `"`macval(appearance_theme)'"' /// `"`macval(background)'"' `"`macval(typography)'"' `"`macval(corners)'"' /// `"`macval(shadow)'"' `"`macval(motion)'"' `"`pagewidth'"' display as text _newline "{hline 72}" display as result "OK" as text " Simulated SurvEye dashboard created" display as text " Data " as result "SIMULATED -- PREVIEW ONLY" if `"`macval(outtitle)'"' != "" display as text " Title " as result `"`macval(outtitle)'"' if !missing(`outN') display as text " Simulated rows " as result %12.0fc `outN' if !missing(`outwarnings') & `outwarnings' > 0 /// display as text " Flags " as result %12.0fc `outwarnings' display as text " Saved " as result `"`macval(outfile)'"' display as text `" {browse "`macval(outfile)'":Open dashboard}"' display as text "{hline 72}" if `"`open'"' != "" { capture noisily view browse `"`macval(outfile)'"' if _rc display as text "Flag Dashboard created, but Stata could not open the browser." } _surveye_read_status using `"`statusfile'"' return add if `"`macval(statusoutfile)'"' == "" { return local output `"`macval(saving)'"' return local filename `"`macval(saving)'"' } if `"`macval(outquestionnaire)'"' == "" return local questionnaire `"`macval(using)'"' return local package_version "2.2.0" end program define _surveye_custommeta version 16.0 args handle variable touse maxcategories local variablelabel : variable label `variable' if strtrim(`"`macval(variablelabel)'"') == "" local variablelabel `"`variable'"' local variableformat : format `variable' local variableformat = lower(strtrim(`"`variableformat'"')) local datatype "numeric" capture confirm string variable `variable' if !_rc local datatype "single" else { local valuelabel : value label `variable' if regexm(`"`variableformat'"', "^%t[cdwmqhy]") local datatype "date" else if `"`valuelabel'"' != "" local datatype "single" } _surveye_cfgline `handle' datalabel.`variable' `"`macval(variablelabel)'"' _surveye_cfgline `handle' datatype.`variable' `"`datatype'"' if `"`datatype'"' == "date" /// _surveye_cfgline `handle' dataformat.`variable' `"`variableformat'"' // For a categorical numeric custom variable, preserve observed Stata // value-label text. Questionnaire categories remain questionnaire-led. if `"`datatype'"' == "single" { local valuelabel : value label `variable' if `"`valuelabel'"' != "" { capture quietly levelsof `variable' if `touse' & !missing(`variable'), local(customlevels) if !_rc { local customlevelcount : word count `customlevels' if `customlevelcount' <= `maxcategories' { local valueindex = 0 foreach onelevel of local customlevels { local ++valueindex local labeltext "" capture local labeltext : label `valuelabel' `onelevel' if _rc | strtrim(`"`macval(labeltext)'"') == "" { local labeltext `"`onelevel'"' } _surveye_cfgline `handle' datavalue.`variable'.`valueindex' /// `"`onelevel'::`macval(labeltext)'"' } } } } } end program define _surveye_cfgline version 16.0 args handle key value // The config format is line-oriented. Tabs/newlines in user text are folded // to spaces so one option can never create a second record. local value = subinstr(`"`macval(value)'"', char(9), " ", .) local value = subinstr(`"`macval(value)'"', char(10), " ", .) local value = subinstr(`"`macval(value)'"', char(13), " ", .) file write `handle' `"`key'"' _tab `"`macval(value)'"' _n end program define _surveye_invoke, rclass version 16.0 args configfile statusfile diagnostics local jarname "surveye_2_2_0.jar" capture findfile `jarname' if _rc { display as error "`jarname' is not installed on the Stata ado-path" display as error "Reinstall the complete package; {bf:net get} is not required." exit 601 } local jar `"`r(fn)'"' // jars() is documented to search the ado-path. Use the installed JAR's // name here (rather than feeding javacall findfile's platform-specific // path) so Windows drive letters, mixed separators, and spaces never // become part of javacall's JAR-list parsing. noisily is intentional: // the Java entry point is silent during normal Stata use, while Stata can // still show a class-loader/JVM diagnostic if invocation fails before the // engine has a chance to create its status file. capture noisily javacall org.worldbank.surveye.StataPlugin stata, /// jars(`jarname') args(`"`configfile'"' `"`statusfile'"') local java_rc = _rc capture confirm file `"`statusfile'"' if _rc { display as error "surveye: Stata could not start the installed Java engine" if `java_rc' display as error `"javacall returned code `java_rc'; its diagnostic is shown above."' display as error `"Installed JAR: `jar'"' if `java_rc' == 5100 { display as error "The JAR found first on your ado-path is stale or incompatible." display as error "In particular, it may not contain the required Stata bridge entry point." } local hasdiagnostics = 0 if `"`diagnostics'"' != "" { capture confirm file `"`diagnostics'"' if !_rc local hasdiagnostics = 1 } if `hasdiagnostics' display as error `"See diagnostics: `diagnostics'"' else if `java_rc' { display as error "This failure occurred before engine diagnostics could be written." display as error "Reinstall the complete package, type {bf:discard}, and try again." display as error "If it persists, run {bf:java query} and include that output in the report." } else display as error "Rerun with diagnostics(filename) for technical details." exit 499 } capture noisily _surveye_read_status using `"`statusfile'"' local status_rc = _rc if `status_rc' { if `"`diagnostics'"' != "" { capture confirm file `"`diagnostics'"' if !_rc display as error `"See diagnostics: `diagnostics'"' } exit `status_rc' } return add if `java_rc' { display as error `"surveye: Java engine returned code `java_rc'"' if `"`diagnostics'"' != "" display as error `"See diagnostics: `diagnostics'"' exit 499 } end program define _surveye_read_status, rclass version 16.0 syntax using/ local success "" local message "" local title "" local N "" local k_charted "" local k_panels "" local k_families "" local k_comparisons "" local k_skipped "" local k_sections "" local k_filters "" local k_questions "" local chartvars "" local skippedvars "" local filters "" local sections "" local warnings "" local weighted "" local has_map "" local map_N "" local map_missing "" local map_outside "" local engine_version "" local output "" local questionnaire "" local filter_candidates "" local gps_candidates "" tempname fh file open `fh' using `"`using'"', read text file read `fh' line while r(eof) == 0 { local tab = char(9) local pos = strpos(`"`macval(line)'"', `"`tab'"') if `pos' > 0 { local key = lower(strtrim(substr(`"`macval(line)'"', 1, `pos' - 1))) local value = substr(`"`macval(line)'"', `pos' + 1, .) if `"`key'"' == "success" local success `"`macval(value)'"' else if `"`key'"' == "message" local message `"`macval(value)'"' else if `"`key'"' == "title" local title `"`macval(value)'"' else if `"`key'"' == "n" local N `"`macval(value)'"' else if inlist(`"`key'"', "k_charted", "k_chartable") local k_charted `"`macval(value)'"' else if `"`key'"' == "k_panels" local k_panels `"`macval(value)'"' else if `"`key'"' == "k_families" local k_families `"`macval(value)'"' else if `"`key'"' == "k_comparisons" local k_comparisons `"`macval(value)'"' else if `"`key'"' == "k_skipped" local k_skipped `"`macval(value)'"' else if `"`key'"' == "k_sections" local k_sections `"`macval(value)'"' else if `"`key'"' == "k_filters" local k_filters `"`macval(value)'"' else if `"`key'"' == "k_questions" local k_questions `"`macval(value)'"' else if `"`key'"' == "chartvars" local chartvars `"`macval(value)'"' else if `"`key'"' == "skippedvars" local skippedvars `"`macval(value)'"' else if `"`key'"' == "filters" local filters `"`macval(value)'"' else if `"`key'"' == "sections" local sections `"`macval(value)'"' else if `"`key'"' == "warnings" local warnings `"`macval(value)'"' else if `"`key'"' == "weighted" local weighted `"`macval(value)'"' else if `"`key'"' == "has_map" local has_map `"`macval(value)'"' else if `"`key'"' == "map_n" local map_N `"`macval(value)'"' else if `"`key'"' == "map_missing" local map_missing `"`macval(value)'"' else if `"`key'"' == "map_outside" local map_outside `"`macval(value)'"' else if `"`key'"' == "engine_version" local engine_version `"`macval(value)'"' else if `"`key'"' == "output" local output `"`macval(value)'"' else if `"`key'"' == "questionnaire" local questionnaire `"`macval(value)'"' else if `"`key'"' == "filter_candidates" local filter_candidates `"`macval(value)'"' else if `"`key'"' == "gps_candidates" local gps_candidates `"`macval(value)'"' } file read `fh' line } file close `fh' local success_l = lower(strtrim(`"`macval(success)'"')) if !inlist(`"`success_l'"', "1", "true", "yes") { if `"`macval(message)'"' == "" local message "The Java engine reported an unspecified error." display as error `"surveye: `macval(message)'"' exit 499 } return scalar success = 1 return local message `"`macval(message)'"' return local title `"`macval(title)'"' return local chartvars `"`macval(chartvars)'"' return local skippedvars `"`macval(skippedvars)'"' return local filters `"`macval(filters)'"' return local sections `"`macval(sections)'"' return local engine_version `"`macval(engine_version)'"' return local output `"`macval(output)'"' return local filename `"`macval(output)'"' return local questionnaire `"`macval(questionnaire)'"' return local filter_candidates `"`macval(filter_candidates)'"' return local gps_candidates `"`macval(gps_candidates)'"' foreach scalar in N k_charted k_panels k_families k_comparisons k_skipped k_sections k_filters k_questions warnings /// weighted has_map map_N map_missing map_outside { // Resolve the loop-selected local by name, without recursively // expanding anything that may be stored in its value. local value : copy local `scalar' local value = strtrim(`"`macval(value)'"') // Many status fields are mode-specific. For example, describe has // k_questions but no N or map counts, while build has N but no // k_questions. Return Stata missing for an absent field without // relying on confirm number's handling of a standalone '.'. if inlist(`"`macval(value)'"', "", ".") { return scalar `scalar' = . } else { capture confirm number `value' if _rc { display as error `"surveye: malformed numeric value for `scalar' in the Java status file"' exit 498 } return scalar `scalar' = real(`"`macval(value)'"') } } end