******************************************************************************* *! __wbod_parameters v2.0.0 04Feb2026 *! Read wbopendata parameters from YAML (replaces hardcoded return values) *! Returns identical r() interface for backward compatibility ******************************************************************************* program define __wbod_parameters, rclass version 14.0 return add *--------------------------------------------------------------------------- * Locate the parameters YAML file *--------------------------------------------------------------------------- __wbod_get_yaml_path, type(parameters) local yaml_path = r(path) if (!fileexists("`yaml_path'")) { di as error "_wbopendata_parameters.yaml not found" di as text "Expected at: `yaml_path'" exit 601 } *--------------------------------------------------------------------------- * Parse the YAML file using infix *--------------------------------------------------------------------------- preserve quietly { infix str500 rawline 1-500 using "`yaml_path'", clear gen long linenum = _n * Skip comment lines gen byte is_comment = substr(strtrim(rawline), 1, 1) == "#" *----------------------------------------------------------------------- * Track which section we're in (sources vs topics vs top-level) *----------------------------------------------------------------------- gen str10 sec_type = "" replace sec_type = "sources" if rawline == "sources:" replace sec_type = "topics" if rawline == "topics:" replace sec_type = "flat" if inlist(rawline, "_metadata:", /// "total:", "number_indicators:", "dt_update:", "dt_lastcheck:", /// "ctrymetadata:", "dt_ctrylastupdate:", "dt_ctrylastcheck:", /// "dt_ctryupdate:") * Forward-fill section type replace sec_type = sec_type[_n-1] if sec_type == "" & _n > 1 *----------------------------------------------------------------------- * Extract key-value pairs *----------------------------------------------------------------------- gen int colon_pos = strpos(rawline, ":") gen str30 field_key = "" gen str244 field_val = "" replace field_key = strtrim(substr(rawline, 1, colon_pos - 1)) /// if colon_pos > 0 & !is_comment replace field_val = strtrim(substr(rawline, colon_pos + 1, .)) /// if colon_pos > 0 & !is_comment * Remove surrounding quotes from values replace field_val = substr(field_val, 2, length(field_val) - 2) /// if (substr(field_val, 1, 1) == "'" | substr(field_val, 1, 1) == `"""') /// & length(field_val) >= 2 *----------------------------------------------------------------------- * Extract flat scalars (top-level key-value pairs) *----------------------------------------------------------------------- local total = "" local number_indicators = "" local dt_update = "" local dt_lastcheck = "" local ctrymetadata = "" local dt_ctrylastupdate = "" local dt_ctrylastcheck = "" local dt_ctryupdate = "" count if field_key == "total" & sec_type != "sources" & sec_type != "topics" if (r(N) > 0) { sum linenum if field_key == "total" & sec_type != "sources" & sec_type != "topics", meanonly local total = field_val[`r(min)'] } count if field_key == "number_indicators" if (r(N) > 0) { sum linenum if field_key == "number_indicators", meanonly local number_indicators = field_val[`r(min)'] } count if field_key == "dt_update" if (r(N) > 0) { sum linenum if field_key == "dt_update", meanonly local dt_update = field_val[`r(min)'] } count if field_key == "dt_lastcheck" if (r(N) > 0) { sum linenum if field_key == "dt_lastcheck", meanonly local dt_lastcheck = field_val[`r(min)'] } count if field_key == "ctrymetadata" if (r(N) > 0) { sum linenum if field_key == "ctrymetadata", meanonly local ctrymetadata = field_val[`r(min)'] } count if field_key == "dt_ctrylastupdate" if (r(N) > 0) { sum linenum if field_key == "dt_ctrylastupdate", meanonly local dt_ctrylastupdate = field_val[`r(min)'] } count if field_key == "dt_ctrylastcheck" if (r(N) > 0) { sum linenum if field_key == "dt_ctrylastcheck", meanonly local dt_ctrylastcheck = field_val[`r(min)'] } count if field_key == "dt_ctryupdate" if (r(N) > 0) { sum linenum if field_key == "dt_ctryupdate", meanonly local dt_ctryupdate = field_val[`r(min)'] } *----------------------------------------------------------------------- * Extract source/topic entries (code, count, name) *----------------------------------------------------------------------- * Detect entry lines: lines matching pattern 'CODE': gen byte is_entry = regexm(rawline, "^'[0-9a-zA-Z]+':") gen str20 entry_code = "" replace entry_code = regexs(1) if regexm(rawline, "^'([0-9a-zA-Z]+)':") * Propagate entry code to its child fields gen long entry_group = sum(is_entry) bysort entry_group: replace entry_code = entry_code[1] if entry_code == "" * Extract count and name for entries gen str10 entry_count = "" gen str244 entry_name = "" replace entry_count = field_val if field_key == "count" & (sec_type == "sources" | sec_type == "topics") replace entry_name = field_val if field_key == "name" & (sec_type == "sources" | sec_type == "topics") * Collapse to one row per entry tempfile parsed collapse (firstnm) entry_code (firstnm) entry_count (firstnm) entry_name /// (firstnm) sec_type, by(entry_group) drop if entry_code == "" save `parsed' *----------------------------------------------------------------------- * Build source return values *----------------------------------------------------------------------- use `parsed' if sec_type == "sources", clear destring entry_code, gen(sort_num) force sort sort_num drop sort_num local sourcereturn = "" local sourceid = "" count local n_sources = r(N) forvalues i = 1/`n_sources' { local scode = entry_code[`i'] local scount = entry_count[`i'] local sname = entry_name[`i'] * Pad source code to 2 digits for sourceidNN format local scode_pad = "`scode'" if (strlen("`scode_pad'") == 1) local scode_pad "0`scode_pad'" * Return individual source count: r(sourceidNN) return local sourceid`scode_pad' = `scount' * Build sourcereturn list if ("`sourcereturn'" == "") { local sourcereturn "sourceid`scode_pad'" } else { local sourcereturn "`sourcereturn' sourceid`scode_pad'" } * Build sourceid compound-quoted list local sourceid `"`sourceid' `"`scode_pad' `sname'"'"' } local sourceid = strtrim(`"`sourceid'"') *----------------------------------------------------------------------- * Build topic return values *----------------------------------------------------------------------- use `parsed' if sec_type == "topics", clear * Sort: numeric topics first, then topicID last gen byte is_special = entry_code == "topicID" destring entry_code, gen(sort_num) force replace sort_num = 999 if is_special sort sort_num drop is_special sort_num local topicreturn = "" local topicid = "" count local n_topics = r(N) forvalues i = 1/`n_topics' { local tcode = entry_code[`i'] local tcount = entry_count[`i'] local tname = entry_name[`i'] * Pad numeric topic codes to 2 digits, keep topicID as-is local tcode_pad = "`tcode'" if (strlen("`tcode_pad'") == 1) local tcode_pad "0`tcode_pad'" * Return individual topic count: r(topicidNN) or r(topicidtopicID) return local topicid`tcode_pad' = `tcount' * Build topicreturn list if ("`topicreturn'" == "") { local topicreturn "topicid`tcode_pad'" } else { local topicreturn "`topicreturn' topicid`tcode_pad'" } * Build topicid compound-quoted list local topicid `"`topicid' `"`tcode_pad' `tname'"'"' } local topicid = strtrim(`"`topicid'"') } *--------------------------------------------------------------------------- * Return all values (identical interface to old hardcoded version) *--------------------------------------------------------------------------- return local total = `total' return local sourcereturn "`sourcereturn'" return local topicreturn "`topicreturn'" return local sourceid `"`sourceid'"' return local topicid `"`topicid'"' return local number_indicators = `number_indicators' return local dt_update "`dt_update'" return local dt_lastcheck "`dt_lastcheck'" return local ctrymetadata = `ctrymetadata' return local dt_ctrylastupdate "`dt_ctrylastupdate'" return local dt_ctrylastcheck "`dt_ctrylastcheck'" return local dt_ctryupdate "`dt_ctryupdate'" restore end