*! version 0.3 24Jul2025
* Programmed by Gustavo Iglésias

program define metaxl_apply
/*
Applies metadata to data in memory.
*/

syntax, METAfile(string) [DOfile(string) TRUNCate CHARS NOTES NOBackup]

qui describe
if `r(N)' == 0 | `r(k)' == 0 {
	di as error "No data in memory"
	exit 198
}

if "`truncate'" == "" {
	qui ds
	foreach var in `r(varlist)' {
		local len = length("`var'")
		if `len' > 25 {
			di as error "Length of `var' longer than 25 characters. This will" ///
			" generate an error when creating the xlsx file with the metadata." ///
			`" Please specify the option "truncate" to truncate variable names"' ///
			" longer than 25 characters"
			exit 198
		}
	}
	qui label dir
	foreach vl in `r(names)' {
		local len = length("`vl'")
		if `len' > 25 {
			di as error "Length of value label `vl' longer than 27 characters. This will" ///
			" generate an error when creating the xlsx file with the metadata." ///
			`" Please specify the option "truncate" to truncate value label names"' ///
			" longer than 27 characters"
			exit 198
		}
	}
}
else {
	metaxl_truncate
}

qui metaxl_check, meta(`metafile')

if trim(`"`r(inconsistencies)'"') != "" {
    di as error `""`metafile'" contains inconsistencies. Cannot apply meta "' ///
	`"data. Please run the command "metaxl check" with option "checkfile" to get"' ///
	" a more thourough look at the inconsistencies found."
	exit 198
}

local metafile "`metafile'.xlsx"

if "`nobackup'" != "nobackup" {
	quietly metaxl extract, meta(_bk_meta) replace
	di
	di "{text:Backup meta file {bf:_bk_meta.xlsx} saved in the current}" ///
		"{text: working directory}"
}

metaxl clear, force

tempname metaframe
frame create `metaframe'
cap file close metado
if ("`dofile'" == "") {
	tempname tempdo
	qui file open metado using "`tempdo'.do", write replace
}
else {
	qui file open metado using "`dofile'.do", write replace
}

write_data_features, metafile(`metafile') metaframe(`metaframe') `chars' `notes'

get_data_features, metafile(`metafile') metaframe(`metaframe')
local labellang "`r(labellang)'"

write_var_features, metafile(`metafile') metaframe(`metaframe') labellang(`labellang') ///
	`chars' `notes'

// file write metado `"save "`file'", replace"' _n

file write metado `"compress"' _n

file close metado

if ("`dofile'" == "") {
	run "`tempdo'.do"
	cap rm "`tempdo'.do"
}
else {
	run "`dofile'.do"
	di
	di as text "File " as result "`dofile'.do" as text " saved"
}


end


program define write_data_features

syntax, metafile(string) metaframe(string) [CHARS NOTES]

get_data_features, metafile(`metafile') metaframe(`metaframe')

file write metado `"* Data features"' _n
if ("`r(datalabel)'" != "") file write metado `"label data "`r(datalabel)'""' _n
if ("`r(sorted_by)'" != "") file write metado `"capture sort `r(sorted_by)'"' _n
local labellang "`r(labellang)'"
foreach lang in `labellang' {
    file write metado `"cap label language `lang', new"' _n
}

if ("`notes'" == "notes") {
	if (`r(note_count)') {
		forvalues i=1/`r(note_count)' {
			file write metado `"note: `r(data_note_`i')'"' _n
		}
	}
	else {
		di
		di "{err:Data notes not available in metadata file}"
	}
}


if ("`r(data_chars)'" == "") local data_chars 0
else local data_chars `r(data_chars)'
if (`data_chars)') {
	frame `metaframe' {
		cap import excel using `metafile', sheet("char_dta") first clear
		if (_rc == 601) {
			di
			di "{err:Worksheet {bf:char_dta} not found in metafile}"
		}
		else {
			* Remove Stata default chars
			qui drop if substr(char, 1, 1) == "_" | ///
				substr(char, 1, 4) == "note"
			qui count
			if `r(N)' {
				forvalues i=1/`r(N)' {
					local char = char[`i']
					local value = value[`i']
					file write metado `"char _dta[`char'] `value'"' _n
				}
			}
		}
	}
}
else {
	di
	di "{err:Data characteristics not available in metadata file}"
}



file write metado _n(2)

end


program define get_data_features, rclass

syntax, metafile(string) metaframe(string)

frame `metaframe' {
    qui import excel using `metafile', sheet("data_features_gen") clear first
	local excel_sheet "data_features_gen"
	local i = 1
	foreach feature in "Data Label" "Sorted by" "Label languages" "Data characteristics" {
		qui glevelsof Content if Features == "`feature'", local(levels`i')
		if ("`feature'" == "Data Label") return local datalabel `: word 1 of `levels`i'''
		if ("`feature'" == "Sorted by") return local sorted_by `: word 1 of `levels`i'''
		if ("`feature'" == "Label languages") return local labellang `: word 1 of `levels`i'''
		if ("`feature'" == "Data characteristics") return local data_chars `: word 1 of `levels`i'''
		local ++i
	}
	* Notes
	qui glevelsof Content if strpos(Features, "Data note"), local(notes_levels)
	return local note_count = `: word count `notes_levels''
	local i = 1
	foreach note in `notes_levels' {
	    return local data_note_`i' = `"`note'"'
		local ++i
	}
	clear
}

end


program define write_var_features

syntax, metafile(string) metaframe(string) labellang(string) [CHARS NOTES]

tempfile temp
tempname fvallab fchars fnotes
foreach frm in fvallab fchars fnotes {
    frame create ``frm''
}
qui ds
local variables "`r(varlist)'"

frame `metaframe' {
	qui local_to_column, local(`variables') col(variable)
	qui save "`temp'", replace
    qui import excel using `metafile', sheet("variables") first clear
	tempvar _merge
	qui merge 1:1 variable using `temp', gen(`_merge')
	qui count
	forvalues i=1/`r(N)' {
		local var = variable[`i']
		if `_merge'[`i'] == 1 {
			di as error `"variable "`var'" only available in "`metafile'""'
		}
		else if `_merge'[`i'] == 2 {
			di as error `"variable "`var'" not found in "`metafile'""'
		}
		else {
			file write metado `"**** `var' ****"' _n(2)
			local var_type = type[`i']
			write_var_type, var(`var') type(`var_type')
			local var_format = format[`i']
			write_var_format, var(`var') format(`var_format')
			foreach lang in `labellang' {
				if  !missing(label_`lang'[`i']) {
					local label_`lang' = label_`lang'[`i']
					write_label, var(`var') lang(`lang') label(`"`label_`lang''"')
				}
				if !missing(value_label_`lang'[`i']) {
					local value_label_`lang' = value_label_`lang'[`i']
					* Check if value label is already defined
					check_vldef, vl(`value_label_`lang'') vls(`vls')
					if `r(vldef)' {
						write_value_label, var(`var') frame(`fvallab') lang(`lang') ///
							value_label(`value_label_`lang'') metafile(`metafile')
					}
					else {
						local vls = "`vls'" + " `value_label_`lang''"
						local vls = trim("`vls'")
						write_value_label, var(`var') frame(`fvallab') lang(`lang') ///
							value_label(`value_label_`lang'') metafile(`metafile') def
					}
				}
			}
			if ("`chars'" == "chars") {
				local chars_var = chars[`i']
				if (`chars_var' > 0) {
					cap write_chars, var(`var') frame(`fchars') metafile(`metafile')
					if (_rc == 601) {
						di
						di "{err:worksheet {bf:char_`var'} not found. Skipping }" ///
							"{err:apply for {bf:`var'} characteristic}"
					}
				}
			}
			if ("`notes'" == "notes") {
				local notes_var = notes[`i']
				if (`notes_var' > 0) {
					cap write_notes, var(`var') frame(`fnotes') metafile(`metafile')
					if (_rc == 601) {
						di
						di "{err:worksheet {bf:note_`var'} not found. Skipping }" ///
							"{err:apply for {bf:`var'} note}"
					}
				}
			}
			file write metado _n(2)
		}
	}
}

foreach frm in fvallab fchars fnotes {
    frame drop ``frm''
}

end


program define check_vldef, rclass

syntax, vl(string) [vls(string)]

local def 0

foreach lab in `vls' {
	if trim("`vl'") == trim("`lab'") {
		return local vldef 1
		local def 1
		continue, break
	}
}

if (!`def') return local vldef 0

end


program define local_to_column

syntax, local(string) col(string)

local i = 1
foreach item in `local' {
	set obs `i'
	if `i' == 1 {
		gen `col' = "`item'" in `i'
	}
	else {
		replace `col' = "`item'" in `i'
	}
	local ++i
}

end


program define write_var_type

syntax, var(string) type(string)

file write metado `"* Type"' _n
file write metado `"recast `type' `var'"' _n

end


program define write_var_format

syntax, var(string) format(string)

file write metado `"* Format"' _n
file write metado `"format `format' `var'"' _n

end


program define write_label

syntax, var(string) lang(string) label(string)

// di `"`label'"'

file write metado `"* Variable label - `lang'"' _n
file write metado `"label language `lang'"' _n
file write metado `"label variable `var' `"`label'"'"' _n

end


program define write_value_label

syntax, var(string) frame(string) lang(string) value_label(string) ///
		metafile(string) [def]

file write metado `"* Value label - `lang'"' _n
frame `frame' {
	qui import excel using `metafile', sheet("vl_`value_label'") first clear
	qui count
	if "`def'" == "def" {
		forvalues i = 1/`r(N)' {
			local value = value[`i']
			local label = label[`i']
			if (!inlist(trim("`value'"), ".", "") & trim(`"`label'"') != "") {
				file write metado `"label define `value_label' `value' `"`label'"', add"' _n
			}
		}
	}
	file write metado `"label values `var' `value_label', nofix"' _n
}

end


program define write_chars

syntax, var(string) frame(string) metafile(string)

frame `frame' {
	qui import excel using `metafile', sheet("char_`var'") first clear
	* Remove Stata default chars
	qui drop if substr(char, 1, 1) == "_" | ///
		substr(char, 1, length("datasignature")) == "datasignature" | ///
		substr(char, 1, length("destring")) == "destring" | ///
		substr(char, 1, length("tostring")) == "tostring" | ///
		substr(char, 1, 4) == "note"
	qui count
	if `r(N)' {
	    file write metado `"* Characteristics"' _n
		forvalues i=1/`r(N)' {
			local char = char[`i']
			local value = value[`i']
			file write metado `"char `var'[`char'] `value'"' _n
		}
	}
}

end


program define write_notes

syntax, var(string) frame(string) metafile(string)

file write metado `"* Notes"' _n
frame `frame' {
	qui import excel using `metafile', sheet("note_`var'") first clear
	qui count
	forvalues i=1/`r(N)' {
	    local note = note[`i']
		file write metado `"note `var': `note'"' _n
	}
}

end