// reference policies do not hold information, thus functions and sys-pol can just link the original policy // see "explanation wrt new handling of reference policies" in Explanations.cs private void GatherRefPol(EM2Item pol, EM2Country.Content ctryContent) { if (pol.properties.ContainsKey(EM2TAGS.REFPOL_ID) && !string.IsNullOrEmpty(pol.properties[EM2TAGS.REFPOL_ID])) { ctryContent.referencePolicies.Add(pol.id, pol.properties[EM2TAGS.REFPOL_ID]); } }
private void RemoveRefPol(EM2Country.Content ctryContent) { // after remembering the ids (see above) the reference policies can now be remove foreach (string rp in ctryContent.referencePolicies.Keys) { ctryContent.policies.Remove(rp); } }
// called for each extension-switch, see "explanation wrt new handling of policy switches" in Explanations.cs private void AdaptExtensionSwitchProp(EM2Data.PolSwitchItem sw, List <List <MultiProp> > extensionInfo, // info from global extensions file Dictionary <string, EM2Item> policies, // all polices of the country out bool remove, EM2Country.Content ctryContent) { sw.value = XmlHelpers.RemoveCData(sw.value); // e.g. <![CDATA[off]]> -> off (CDATA is just unnecessary) remove = sw.value == DefPar.Value.NA; // remove the (local) switch if set to n/a if (remove) { return; } // if "old style": generate a content for the extension: // get id(s) of policy/ies in the country that match(es) the pattern of the global switch policy // e.g. get id of Hungarian policy BTA_hu if sw.switchPolID points to global policy-switch with pattern 'BTA_??' foreach (string polID in GetExtensionPolIDs(sw.switchPolID, extensionInfo, policies)) { if ((from ep in ctryContent.extensionPol where ep.Item1 == sw.switchPolID && ep.Item2 == polID select ep).Count() == 0) // avoid double input, e.g. already transfered to "new style", but still the short-name (aka name-pattern) is BTA_?? { ctryContent.extensionPol.Add(new System.Tuple <string, string, string>(sw.switchPolID, polID, "false")); } } }
/// <summary> /// reads a country's EM2 country- and dataconfig-XML-files and transfers them to EM3 style /// creates the country folder, if it does not exist, and overwrites any existing country file /// note: the intended usage is "single country transformation" /// the EM3All class is responsible for complete EM-content transformation (using the EM3Country.Write function) /// </summary> /// <param name="emPath"> EuromodFiles folder (containing EM2-files in XMLParam and (will contain) EM3-files in EM3Translation\XMLParam) </param> /// <param name="country"> short-name of country </param> /// <param name="errors"> critical and non-critical erros during the transformation-process, empty structure for no errors </param> public static bool Transform(string emPath, string country, out List <string> errors) { errors = new List <string>(); EMPath pathHandler = new EMPath(emPath); string em2CountryFile = string.Empty, em2DataFile = string.Empty; try { DirectoryInfo di = Directory.CreateDirectory(pathHandler.GetCountryFolderPath(country)); // read EM2-files em2CountryFile = pathHandler.GetCountryFilePath(country: country, em2: true); em2DataFile = pathHandler.GetEM2DataConfigFilePath(country); bool up2D1 = TransformerCommon.IsFileUpToDate(em2CountryFile, pathHandler.GetCountryFolderPath(country), out string hash1); bool up2D2 = TransformerCommon.IsFileUpToDate(em2DataFile, pathHandler.GetCountryFolderPath(country), out string hash2); if (up2D1 && up2D2) { return(true); // do not combine in one if, to make sure that both hash-files (for country and dataconfig) are generated } EM2Country.Content ctryContent = EM2Country.Read(em2CountryFile, out List <string> cErrors); EM2Data.Content dataContent = EM2Data.Read(em2DataFile, out List <string> dErrors); // need the global file with policy-switches for proper transformation of local policy switches List <List <MultiProp> > extensions = EM2Global.ReadSwitchPol(pathHandler.GetFolderConfig(em2: true), out List <string> gErrors); errors.AddRange(cErrors); errors.AddRange(dErrors); errors.AddRange(gErrors); if (ctryContent == null || dataContent == null || extensions == null) { return(false); } // write EM3-file (includes EM2->EM3 adaptations, via EM23Adapt class) string em3CountryFile = pathHandler.GetCountryFilePath(country); bool success = Write(ctryContent, dataContent, extensions, em3CountryFile, out List <string> wErrors); errors.AddRange(wErrors); if (success && errors.Count == 0) { TransformerCommon.WriteUpToDate(em2CountryFile, pathHandler.GetCountryFolderPath(country), hash1); TransformerCommon.WriteUpToDate(em2DataFile, pathHandler.GetCountryFolderPath(country), hash2); } return(success); } catch (Exception exception) { errors.Add($"{country}: {exception.Message}"); return(false); } }
// sort policies, functions and parameters and to achieve continuous numbers // the purpose of this action is to produce error messages in the executable which reflect the visual order in the UI // the decision to do it here instead of the XmlReader was because we need some ordering anyway for setting the run-options (see below) // (note that the respective orders and parent-ids were stored with the pol/fun/par-items in the reading process only for this purpose) private void AdaptOrder(EM2Country.Content ctryContent) { Dictionary <string, string> itemOrders = new Dictionary <string, string>(); List <EM2Item> orderedFuns = new List <EM2Item>(); // this is for setting function's run-options (see below) long oPol = 1; foreach (var pol in ctryContent.policies.Values.OrderBy(o => o.order)) { itemOrders.Add(pol.id, oPol.ToString()); ++oPol; long oFun = 1; foreach (var fun in ctryContent.functions.Values.Where(w => w.partentId == pol.id).OrderBy(o => o.order)) { itemOrders.Add(fun.id, oFun.ToString()); ++oFun; orderedFuns.Add(fun); long oPar = 1; foreach (var par in ctryContent.parameters.Values.Where(w => w.partentId == fun.id).OrderBy(o => o.order)) { itemOrders.Add(par.id, oPar.ToString()); ++oPar; } } } // once we have the proper order put it to the <SysPol>/<SysFun>/<SysPar>-items were the order is actually stored in the EM3 format foreach (var sysPol in ctryContent.sysPol) { sysPol.order = itemOrders[sysPol.itemID]; } foreach (var sysFun in ctryContent.sysFun) { sysFun.order = itemOrders[sysFun.itemID]; } foreach (var sysPar in ctryContent.sysPar) { sysPar.order = itemOrders[sysPar.itemID]; } }
/// <summary> /// reads an AddOn's EM2 country-XML-files and transfers it to EM3 style /// creates the AddOn folder, if it does not exist, and overwrites any existing country AddOn /// note: the intended usage is "single AddOn transformation" /// the EM3All class is responsible for complete EM-content transformation (using the EM3Country.Write function) /// </summary> /// <param name="emPath"> EuromodFiles folder (containing EM2-files in XMLParam and (will contain) EM3-files in EM3Translation\XMLParam) </param> /// <param name="addOn"> short-name of AddOn </param> /// <param name="errors"> critical and non-critical erros during the transformation-process, empty structure for no errors </param> public static bool TransformAddOn(string emPath, string addOn, out List <string> errors) { errors = new List <string>(); EMPath pathHandler = new EMPath(emPath); try { DirectoryInfo di = Directory.CreateDirectory(pathHandler.GetAddOnFolderPath(addOn)); // read EM2-files string em2AddOnFile = pathHandler.GetAddOnFilePath(addOn: addOn, em2: true); if (TransformerCommon.IsFileUpToDate(em2AddOnFile, pathHandler.GetAddOnFolderPath(addOn), out string hashCode)) { return(true); } EM2Country.Content addOnContent = EM2Country.Read(em2AddOnFile, out errors); if (addOnContent == null) { return(false); } // write EM3-file (includes EM2->EM3 adaptations, via EM23Adapt class) string em3AddOnFile = pathHandler.GetAddOnFilePath(addOn); bool success = Write(addOnContent, null, new List <List <MultiProp> >(), em3AddOnFile, out List <string> wErrors); errors.AddRange(wErrors); if (success && errors.Count == 0) { TransformerCommon.WriteUpToDate(em2AddOnFile, pathHandler.GetAddOnFolderPath(addOn), hashCode); } return(success); } catch (Exception exception) { errors.Add($"{addOn}: {exception.Message}"); return(false); } }
/// <summary> reads all components of an EM2 version </summary> /// <param name="emPath"> path to EUROMOD version (e.g. C:\euromod\EuromodContent\EuromodFiles_H0.13\) </param> /// <param name="errors"> list of errors, only critical errors lead to failure-return (null), i.e. there may be errors on "success"</param> /// <param name="progressAction"> /// optional function for progress reporting, in the form 'void funName(string message)' /// is called for each item (country, add-on, global file) and sets message to e.g. 'Finished reading ...', 'Failed reading ...' /// </param> /// <param name="cancelSrc"> optional cancellation item </param> /// <returns> Content structure upon success, null upon failure </returns> public static Content Read(string emPath, out List <string> errors, Action <string> progressAction = null, CancellationTokenSource cancelSrc = null) { Content content = new Content(); List <string> _errors = new List <string>(); object writeLock = new object(); ParallelOptions parallelOptions = new ParallelOptions(); if (cancelSrc != null) { parallelOptions.CancellationToken = cancelSrc.Token; } try { EMPath pathHandler = new EMPath(emPath); // READ COUNTRIES IN PARALLEL Parallel.ForEach(new DirectoryInfo(pathHandler.GetFolderCountries(em2: true)).GetDirectories(), parallelOptions, folder => { parallelOptions.CancellationToken.ThrowIfCancellationRequested(); string ctryFileName = pathHandler.GetCountryFilePath(country: folder.Name, em2: true); string dataFileName = pathHandler.GetEM2DataConfigFilePath(country: folder.Name); EM2Country.Content c = EM2Country.Read(ctryFileName, out List <string> cErrors); EM2Data.Content d = EM2Data.Read(dataFileName, out List <string> dErrors); bool success = c != null && d != null; lock (writeLock) { if (success) { content.countries.Add(new CountryContent() { country = c, data = d }); } _errors.AddRange(cErrors); _errors.AddRange(dErrors); } ReportSuccess(folder.Name, success, cErrors.Count + dErrors.Count); }); // the rest is read sequentially (parallel would be unnecessary overhead) // READ ADD-ONS foreach (DirectoryInfo folder in new DirectoryInfo(pathHandler.GetFolderAddOns(em2: true)).GetDirectories()) { if ((new List <string>() { "HypoData", "SumStat", "XYZ" }).Contains(folder.Name)) { continue; // that's just old stuff, I think hardcoding isn't a faux pas } string aoFileName = pathHandler.GetAddOnFilePath(addOn: folder.Name, em2: true); EM2Country.Content ao = EM2Country.Read(aoFileName, out List <string> aoErrors); if (ao != null) { content.addOns.Add(ao); } _errors.AddRange(aoErrors); ReportSuccess(folder.Name, ao != null, aoErrors.Count); } ; // READ GLOBAL FILES string configPath = pathHandler.GetFolderConfig(em2: true); content.exRates = EM2Global.ReadExRates(configPath, out List <string> exErrors); _errors.AddRange(exErrors); ReportSuccess("Exchange rates", exErrors.Count == 0, exErrors.Count); content.hicp = EM2Global.ReadHICP(configPath, out List <string> hicpErrors); _errors.AddRange(hicpErrors); ReportSuccess("HICP", hicpErrors.Count == 0, hicpErrors.Count); content.switchPol = EM2Global.ReadSwitchPol(configPath, out List <string> spErrors); _errors.AddRange(spErrors); ReportSuccess("Policy switches", spErrors.Count == 0, spErrors.Count); // READ VARIABLES content.varConfig = EM2Variables.Read(configPath, out List <string> vErrors); _errors.AddRange(vErrors); ReportSuccess(EM2Variables.FILE_VARCONFIG, content.varConfig != null, vErrors.Count); return(content); } catch (OperationCanceledException) { progressAction($"Reading {emPath} cancelled!"); return(content); } catch (Exception exception) { lock (writeLock) { _errors.Add(exception.Message); } return(null); } finally { errors = _errors; } void ReportSuccess(string what, bool success, int cntErrors) { if (success) { progressAction($"Finished reading {what} with " + $"{(cntErrors == 0 ? "success" : $"{cntErrors} errors") }"); }
/// <summary> /// performs all EM2->EM3 content (see above) adaptations for country- and AddOn-files /// *!*!*!*!*!*!* NOTE: ALL "explanation wrt ..." ARE FOUND IN explanations.cs *!*!*!*!*!*!* /// </summary> /// <param name="ctryContent"> content of country-XML-file as read by EM2Country.Read </param> /// <param name="dataContent"> content of country-dataconfig-XML-file as read by EM2Data.Read </param> /// <param name="extensionInfo"> content of global policy-switches-XML-file (provides info for adaptation, see below) </param> internal void AdaptCountry(EM2Country.Content ctryContent, EM2Data.Content dataContent, List <List <MultiProp> > extensionInfo, out List <string> errors) { errors = new List <string>(); // *** C O U N T R Y *** ctryContent.general.id = null; // remove country-id (which never had any use) // *** A D A P T O R D E R T O V I S U A L O R D E R *** // *** S E T F U N C T I O N ' S R U N - O P T I O N *** AdaptOrder(ctryContent); // *** S Y S T E M S *** foreach (var sys in ctryContent.systems) { AdaptSysProp(sys.Value); // remove outdated properties (ExchangeRateEuro, Private, ...) } // *** P O L I C I E S *** foreach (var pol in ctryContent.policies) { AdaptPolProp(pol.Value); // remove outdated properties (Type, Color, Private, ...) GatherRefPol(pol.Value, ctryContent); // see "explanation wrt to new handling of reference policies" } RemoveRefPol(ctryContent); // see "explanation wrt to new handling of reference policies" // *** F U N C T I O N S *** List <string> funIds = (from f in ctryContent.functions select f.Key).ToList(); foreach (string funId in funIds) // it is necessary to run over the ids instead of the functions themselves { // because we may add functions and thus change the collection EM2Item fun = ctryContent.functions[funId]; AdaptFunProp(fun); // remove outdated properties (Color, Private, ...) AdaptChangeParam_Fun(fun, ctryContent, errors); // see "explanation wrt to new handling of ChangeParam" } // *** P A R A M E T E R S *** foreach (var par in ctryContent.parameters) { AdaptParProp(par.Value); // remove outdated properties (Color, ValueType, Private, ...) EM2Item fun = ctryContent.functions[par.Value.properties[EM2TAGS.FUNCTION_ID]]; AdaptTotals_Par(par.Value, fun); // merge Agg_Var and Agg_IL AdaptCZDefConfig_Par(par.Value); // solve a special case for CZ (actually a syntax error due to very old notation) AdaptFootnotes_Par(par.Value); // replace #_LowLim_Amount by #_LowLim, #_Income by #_Info, etc. } // *** S Y S T E M V A L U E S O F P O L I C I E S *** foreach (var sysPol in ctryContent.sysPol) { ReplaceSwitchAndToggle(sysPol); // see "explanation wrt to toggle and switch" } // *** S Y S T E M V A L U E S O F F U N C T I O N S *** foreach (var sysFun in ctryContent.sysFun) { ReplaceSwitchAndToggle(sysFun); // see "explanation wrt to toggle and switch" } // *** S Y S T E M V A L U E S O F P A R A M E T E R S *** foreach (var sysPar in ctryContent.sysPar) { ReplaceEmptyByNA(sysPar); // it seems that there are (faulty!) parameter values set to "<![CDATA[]]>" (i.e. empty CDATA) ReplaceWhoMustBeEligAliases(sysPar); // replace 'one_member' by 'one', 'all_members' and 'taxunit' by 'all' and 'all_adult' by 'all_adults' } // *** U P R A T I N G I N D I C E S *** foreach (var upIndex in ctryContent.upInd) { AdaptYearValues(upIndex.Value, ctryContent.upIndVal, EM_XmlHandler.TAGS.UPIND_ID); // put year-values in own elements (see comment in function) } // *** I N D I R E C T T A X E S *** foreach (var indTax in ctryContent.indTax) { AdaptYearValues(indTax.Value, ctryContent.indTaxVal, EM_XmlHandler.TAGS.INDTAX_ID); // put year-values in own elements (see above) } if (dataContent == null) { return; } // BREAK HERE FOR ADD-ONS !!! the rest only concerns countries //--------------------------------------------------------------------------------------- // *** D A T A S E T S *** //foreach (var dataSet in dataContent.dataSets) { } // nothing to do currently // *** S Y S T E M - D A T A - C O M B I N A T I O N S *** foreach (var sysData in dataContent.sysData) { AdaptSysDataProp(sysData); // remove outdated properties (UseCommonDefault, ...) } // *** S W I T C H - G R O U P S *** for (int i = dataContent.policySwitches.Count - 1; i >= 0; --i) // see "explanation wrt new handling of policy switches" { AdaptExtensionSwitchProp(dataContent.policySwitches[i], extensionInfo, ctryContent.policies, out bool remove, ctryContent); if (remove) // remove if no respective switch policy found (maybe outdated switch) or if set to n/a { dataContent.policySwitches.RemoveAt(i); } } }
// also see "explanation wrt new handling of ChangeParam" in Explanations.cs private void AdaptChangeParam_Fun(EM2Item fun, EM2Country.Content ctryContent, List <string> errors) { if (fun.name.ToLower() != DefFun.ChangeParam.ToLower()) { return; } // === G A T H E R I N F O R M A T I O N === // get all parameters of the ChangeParam ... // i.e. Param_Id, Param_NewVal, Param_CondVal, RunCond, #_DataBasename, #_xxx (i.e. other footnote-parameters) var allPar = from p in ctryContent.parameters where p.Value.properties.ContainsKey(EM2TAGS.FUNCTION_ID) && p.Value.properties[EM2TAGS.FUNCTION_ID] == fun.id select p; // ... and put them into lists that allow deciding on what to do with them string idRunCond = null; // id of Run_Cond-parameter List <string> idDataBaseFootnotes = new List <string>(); // ids of footnote-parameters #_DataBase (referred by Run_Cond) List <string> idsParamId_ChangeSwitch = new List <string>(); // ids of Param_Id-parameters, which refer to a function or policy List <string> idsVal_ChangeSwitch = new List <string>(); // ids of Param_New/CondVal-parameters with value on/off/toggle List <string> idsParamId_ChangeParam = new List <string>(); // ids of Param_Id-parameters, which refer to a parameter List <string> idsVal_ChangeParam = new List <string>(); // ids of Param_CondVal-parameters with another value than on/off/toggle List <string> idsOtherFootnotes = new List <string>(); // ids of other footnote-parameters than #_DataBase (rather unlikely) bool hasChangeParam_Par = false; // has parameters that belong into a ChangeParam function bool hasChangeSwitch_Par = false; // has parameters that belong into a ChangeSwitch function foreach (var par in allPar) { // --- Param_Id --- if (par.Value.name.ToLower() == DefPar.ChangeParam.Param_Id.ToLower()) { // Param_Id refers to a policy or function -> belongs into a ChangeSwitch function bool?refersToPolFun = RefersToPolFun(par.Value.id); if (refersToPolFun == true) { idsParamId_ChangeSwitch.Add(par.Value.id); hasChangeSwitch_Par = true; } // Param_Id refers to a parameter -> belongs into a ChangeParam function else if (refersToPolFun == false) { idsParamId_ChangeParam.Add(par.Value.id); hasChangeParam_Par = true; } //else: parameter is n/a in all systems } // --- Run_Cond --- else if (par.Value.name.ToLower() == DefPar.Common.Run_Cond.ToLower()) { idRunCond = par.Key; } // --- #_DataBase --- else if (par.Value.name.ToLower() == DefQuery.Par.DataBasename.ToLower()) { idDataBaseFootnotes.Add(par.Key); } // --- Param_CondVal or Param_NewVal --- else if (par.Value.name.ToLower() == DefPar.ChangeParam.Param_NewVal.ToLower() || par.Value.name.ToLower() == DefPar.ChangeParam.Param_CondVal.ToLower()) { // Param_Cond/NewVal's value = off/on/toggle -> belongs into a ChangeSwitch function bool?isSwitchChange = IsSwitchChange(par.Value.id); if (isSwitchChange == true) { idsVal_ChangeSwitch.Add(par.Value.id); hasChangeSwitch_Par = true; } // Param_Cond/NewVal's value = some parameter-change -> belongs into a ChangeParam function else if (isSwitchChange == false) { idsVal_ChangeParam.Add(par.Value.id); hasChangeParam_Par = true; } //else: parameter is n/a in all systems } // --- other (valid) parameter can only be another footnote parameter than #_DataBase --- else { idsOtherFootnotes.Add(par.Key); } } // === T R A N S F O R M A C T I O N S === if (hasChangeSwitch_Par && hasChangeParam_Par) // splitting the function in two is complicated (must invent ids ...) and is not { // necessary for existing countries/add-ons, thus do not forsee before necessary ... // AddError("impossible to assign all parameters to either a ChangeParam or ChangeSwitch function"); // return; // unfortunately it seems that we need a solution for this a bit complicated case, thus: SeparateSwitchFun(); hasChangeSwitch_Par = false; } // note (!hasChangeSwitch_Par && !hasChangeParam_Par) is theoretically possible, if all pars are n/a, but that wouldn't do any harm // --- change to ChangeSwitch function --- if (hasChangeSwitch_Par) { fun.name = DefFun.ChangeSwitch; foreach (string id in idsParamId_ChangeSwitch) // rename 'Param_Id' to 'PolFun' { ctryContent.parameters[id].name = DefPar.ChangeSwitch.PolFun; } foreach (string id in idsVal_ChangeSwitch) // rename 'Param_CondVal'/'Param_NewVal' to 'SwitchNewVal' { // and change possible 'toggle'-value to 'off' ctryContent.parameters[id].name = DefPar.ChangeSwitch.Switch_NewVal; foreach (var sysPar in GetSysPar(id)) { if (XmlHelpers.RemoveCData(sysPar.value).ToLower() == DefPar.Value.TOGGLE) { sysPar.value = DefPar.Value.OFF; } } // else: no change necessary for on / off / n/a } // no change necessary for other parameters, i.e. Run_Cond and associated footnotes } // --- adapt ChangeParam function --- if (hasChangeParam_Par) { // if 'RunCond={IsUsedDatabase#1}' transform to 'Database=xxx', otherwise error (e.g. an actual run-time RunCond or a more complicated RunCond) if (idRunCond != null && !TransformRunCond()) { return; } // rename from Param_CondVal to Param_NewVal if necessary foreach (string id in idsVal_ChangeParam) { ctryContent.parameters[id].name = DefPar.ChangeParam.Param_NewVal; } // no change necessary for Param_Id (keeps Guid or symbolic id) // ignore other parameters - if there are e.g. unnecessary footnotes, this is also wrong for the old executable } // === H E L P E R F U N C T I O N S === void AddError(string error) { errors.Add($"{ctryContent.general.name}: Failed to transform {fun.name} ({fun.id}): {error} (hint: case not yet handled by transformer)"); } bool TransformRunCond() { bool runCondDefined = false; List <EM2Country.SysItem> runCondVals = GetSysPar(idRunCond).ToList(); foreach (EM2Country.SysItem runCondVal in runCondVals) { string runCond = XmlHelpers.RemoveCData(runCondVal.value).Replace(" ", "").ToLower(); if (runCond == DefPar.Value.NA) { continue; } // remove everything that is allowed in a simple enough run-cond, to then check for empty string for (int i = 999; i >= 1; --i) { runCond = runCond.Replace($"isuseddatabase#{i}", ""); } runCond = runCond.Replace("{}", "").Replace("|", ""); if (runCond != string.Empty) { AddError("too complex Run_Cond"); // still continue, so the country still runs } runCondDefined = true; } if (runCondDefined) // rename '#_Database' to 'Dataset' if RunCond is actually {IsUsedDatabase#1} { // (system values are ok, i.e. contain the respective dataset-name) if (idDataBaseFootnotes.Count() == 0) { AddError("missing database specification"); return(false); } foreach (string idDataBaseFootnote in idDataBaseFootnotes) { ctryContent.parameters[idDataBaseFootnote].name = DefPar.ChangeParam.Dataset; } } // remove the RunCond and its system values ctryContent.parameters.Remove(idRunCond); foreach (EM2Country.SysItem runCondVal in runCondVals) { ctryContent.sysPar.Remove(runCondVal); } return(true); } bool?RefersToPolFun(string id_Param_Id) { bool allNa = true; foreach (EM2Country.SysItem sysPar in GetSysPar(id_Param_Id)) { string id = (XmlHelpers.RemoveCData(sysPar.value)); if (id == DefPar.Value.NA) { continue; } allNa = false; if (EM_Helpers.IsGuid(id)) // i.e. most likely this is a country-file (and not an add-on) { if (!ctryContent.parameters.ContainsKey(id)) { return(true); // a bit unsafe, as only "most likely" a country-file } } // i.e. could still be a country-par-guid in an add-on else // i.e. must be an add-on (unless this is an invalid use of symbolic-id in a country file or otherwise invalid) { if (!AddOn.IsParSymbolicID(id)) { return(true); } } // note: a maybe "safer" way to assess whether the id refers to a parameter or to a policy or function } // would be to analyse the corresponding ParamCond/NewVal (on/off/toggle->Pol/Fun), but that's complex to code if (allNa) { return(null); } return(false); } bool?IsSwitchChange(string id_Param_NewVal) { bool allNa = true; foreach (EM2Country.SysItem sysPar in GetSysPar(id_Param_NewVal)) { string newVal = (XmlHelpers.RemoveCData(sysPar.value)).ToLower(); if (newVal == DefPar.Value.OFF || newVal == DefPar.Value.ON || newVal == DefPar.Value.TOGGLE) { return(true); } if (newVal != DefPar.Value.NA) { allNa = false; } } if (allNa) { return(null); } return(false); } IEnumerable <EM2Country.SysItem> GetSysPar(string parId) { return(from sp in ctryContent.sysPar where sp.itemID == parId select sp); } void SeparateSwitchFun() { // first generate a separate ChangeSwitch function ... EM2Item switchFun = EM2Item.Copy(fun, ctryContent.sysFun, out List <EM2Country.SysItem> switchFunSysVals); switchFun.name = DefFun.ChangeSwitch; switchFun.order = 1234567; // unimportant for a ChangeSwitch ctryContent.functions.Add(switchFun.id, switchFun); ctryContent.sysFun.AddRange(switchFunSysVals); // ... then "move" parameters from existing ChangeParam to new ChangeSwitch ... foreach (string id in idsParamId_ChangeSwitch) { ctryContent.parameters[id].partentId = switchFun.id; // move ... ctryContent.parameters[id].properties[EM2TAGS.FUNCTION_ID] = switchFun.id; ctryContent.parameters[id].name = DefPar.ChangeSwitch.PolFun; // ... and rename 'Param_Id' to 'PolFun' foreach (var x in ctryContent.parameters[id].properties) { RandColor.WriteLine($"{x.Key}={x.Value}"); } } foreach (string id in idsVal_ChangeSwitch) { ctryContent.parameters[id].partentId = switchFun.id; // move ... ctryContent.parameters[id].properties[EM2TAGS.FUNCTION_ID] = switchFun.id; ctryContent.parameters[id].name = DefPar.ChangeSwitch.Switch_NewVal; // ... rename 'Param_CondVal'/'Param_NewVal' to 'SwitchNewVal' ... foreach (var sysPar in GetSysPar(id)) // ... and change possible 'toggle'-value to 'off' { if (XmlHelpers.RemoveCData(sysPar.value).ToLower() == DefPar.Value.TOGGLE) { sysPar.value = DefPar.Value.OFF; } } } // ... finally copy possible run-condition ... if (idRunCond == null) { return; } EM2Item runCond = EM2Item.Copy(ctryContent.parameters[idRunCond], ctryContent.sysPar, out List <EM2Country.SysItem> runCondSysVals); runCond.partentId = switchFun.id; runCond.properties[EM2TAGS.FUNCTION_ID] = switchFun.id; ctryContent.parameters.Add(runCond.id, runCond); ctryContent.sysPar.AddRange(runCondSysVals); // ... and its footnotes foreach (string footNoteId in idDataBaseFootnotes) { EM2Item footNote = EM2Item.Copy(ctryContent.parameters[footNoteId], ctryContent.sysPar, out List <EM2Country.SysItem> footNoteSysVals); footNote.partentId = switchFun.id; footNote.properties[EM2TAGS.FUNCTION_ID] = switchFun.id; ctryContent.parameters.Add(footNote.id, footNote); ctryContent.sysPar.AddRange(footNoteSysVals); } } }
/// <summary> /// transfers a country's EM2 country- and data-config-content into EM3 style and creates the EM3 country-file /// note: this class is only responsible for writing, EM2->EM3 adaptations are accomplished by a call to the EM23Adapt class /// also note: does not create the country folder /// i.e. the function is not intended to be used "stand-alone", but by EM3.Transform/AddOn and EM3All.Write /// </summary> /// <param name="ctryContent"> content of country-XML-file as read by EM2Country.Read </param> /// <param name="dataContent"> content of country-dataconfig-XML-file as read by EM2Data.Read </param> /// <param name="extensionInfo"> content of global extension-XML-file (necessary for EM2->EM3 adaptation) </param> /// <param name="fileName"> full path of EM3 country-XML-file (i.e. the file to write) </param> /// <param name="errors"> critical and non-critical erros during the write-process, empty structure for no errors </param> public static bool Write(EM2Country.Content ctryContent, EM2Data.Content dataContent, List <List <MultiProp> > extensionInfo, string fileName, out List <string> errors) { errors = new List <string>(); XmlWriter writer; // declare here to be able to use it in sub-functions try { // "summarised" EM2->EM3 adaptation process (new EM23Adapt()).AdaptCountry(ctryContent, dataContent, extensionInfo, out errors); using (writer = XmlWriter.Create(fileName, TransformerCommon.GetXmlWriterSettings())) { writer.WriteStartElement(TransformerCommon.ROOT_ELEMENT); // COUNTRY WriteItem(TAGS.COUNTRY, ctryContent.general); // SYSTEMS writer.WriteStartElement(TAGS.Enclosure(TAGS.SYS)); foreach (var sys in ctryContent.systems.Values) { WriteItem(TAGS.SYS, sys); } writer.WriteEndElement(); // POLICIES writer.WriteStartElement(TAGS.Enclosure(TAGS.POL)); foreach (var pol in ctryContent.policies.Values) { WriteItem(TAGS.POL, pol); } writer.WriteEndElement(); // REFERENCE POLICIES writer.WriteStartElement(TAGS.Enclosure(TAGS.REFPOL)); foreach (var refPol in ctryContent.referencePolicies) { writer.WriteStartElement(TAGS.REFPOL); TransformerCommon.TranslateAndWriteElement(writer, TAGS.ID, refPol.Key, TAGS.REFPOL); TransformerCommon.TranslateAndWriteElement(writer, TAGS.REFPOL_ID, refPol.Value, TAGS.REFPOL); writer.WriteEndElement(); } writer.WriteEndElement(); // FUNCTIONS writer.WriteStartElement(TAGS.Enclosure(TAGS.FUN)); foreach (var fun in ctryContent.functions.Values) { WriteItem(TAGS.FUN, fun); } writer.WriteEndElement(); // PARAMETERS writer.WriteStartElement(TAGS.Enclosure(TAGS.PAR)); foreach (var par in ctryContent.parameters.Values) { WriteItem(TAGS.PAR, par); } writer.WriteEndElement(); // SYS-POL-ITEMS writer.WriteStartElement(TAGS.Enclosure(TAGS.SYS_POL)); foreach (var syspol in ctryContent.sysPol) { WriteSysItem(TAGS.SYS_POL, TAGS.POL_ID, TAGS.SWITCH, syspol); } writer.WriteEndElement(); // SYS-FUNC-ITEMS writer.WriteStartElement(TAGS.Enclosure(TAGS.SYS_FUN)); foreach (var sysfun in ctryContent.sysFun) { WriteSysItem(TAGS.SYS_FUN, TAGS.FUN_ID, TAGS.SWITCH, sysfun); } writer.WriteEndElement(); // SYS-PAR-ITEMS writer.WriteStartElement(TAGS.Enclosure(TAGS.SYS_PAR)); foreach (var syspar in ctryContent.sysPar) { WriteSysItem(TAGS.SYS_PAR, TAGS.PAR_ID, TAGS.VALUE, syspar); } writer.WriteEndElement(); // BREAK HERE FOR ADD-ONS !!! the rest only concerns countries if (dataContent == null) { return(true); } // ------------------------------------------------------------------- // UPRATING INDICES - PROPERTIES (name, description, ...) writer.WriteStartElement(TAGS.Enclosure(TAGS.UPIND)); foreach (var upInd in ctryContent.upInd.Values) { WriteItem(TAGS.UPIND, upInd); } writer.WriteEndElement(); // UPRATING INDICES - VALUES PER YEAR writer.WriteStartElement(TAGS.Enclosure(TAGS.UPIND_YEAR)); foreach (var upIndVal in ctryContent.upIndVal) { writer.WriteStartElement(TAGS.UPIND_YEAR); WriteProperties(upIndVal, TAGS.UPIND_YEAR); writer.WriteEndElement(); } writer.WriteEndElement(); // DATASETS writer.WriteStartElement(TAGS.Enclosure(TAGS.DATA)); foreach (var data in dataContent.dataSets.Values) { writer.WriteStartElement(TAGS.DATA); TransformerCommon.TranslateAndWriteElement(writer, TAGS.ID, data.id, TAGS.DATA); TransformerCommon.TranslateAndWriteElement(writer, TAGS.NAME, data.name, TAGS.DATA); WriteProperties(data.properties, TAGS.DATA); writer.WriteEndElement(); } writer.WriteEndElement(); // SYS-DATA-ITEMS writer.WriteStartElement(TAGS.Enclosure(TAGS.SYS_DATA)); foreach (var ds in dataContent.sysData) { writer.WriteStartElement(TAGS.SYS_DATA); TransformerCommon.TranslateAndWriteElement(writer, TAGS.DATA_ID, ds.dataID, TAGS.SYS_DATA); TransformerCommon.TranslateAndWriteElement(writer, TAGS.SYS_ID, ds.sysID, TAGS.SYS_DATA); WriteProperties(ds.properties, TAGS.SYS_DATA); writer.WriteEndElement(); } writer.WriteEndElement(); // EXTENSIONS // extensions themself writer.WriteStartElement(TAGS.Enclosure(TAGS.LOCAL_EXTENSION)); foreach (var ext in dataContent.localExtensions) { WriteItem(TAGS.LOCAL_EXTENSION, ext); } writer.WriteEndElement(); // extensions' policies writer.WriteStartElement(TAGS.Enclosure(TAGS.EXTENSION_POL)); foreach (var ep in ctryContent.extensionPol) { writer.WriteStartElement(TAGS.EXTENSION_POL); TransformerCommon.TranslateAndWriteElement(writer, TAGS.EXTENSION_ID, ep.Item1, TAGS.EXTENSION_POL); TransformerCommon.TranslateAndWriteElement(writer, TAGS.POL_ID, ep.Item2, TAGS.EXTENSION_POL); TransformerCommon.TranslateAndWriteElement(writer, TAGS.EXENTSION_BASEOFF, ep.Item3, TAGS.EXTENSION_POL); writer.WriteEndElement(); } writer.WriteEndElement(); // extensions' functions writer.WriteStartElement(TAGS.Enclosure(TAGS.EXTENSION_FUN)); foreach (var fp in ctryContent.extensionFun) { writer.WriteStartElement(TAGS.EXTENSION_FUN); TransformerCommon.TranslateAndWriteElement(writer, TAGS.EXTENSION_ID, fp.Item1, TAGS.EXTENSION_FUN); TransformerCommon.TranslateAndWriteElement(writer, TAGS.FUN_ID, fp.Item2, TAGS.EXTENSION_FUN); TransformerCommon.TranslateAndWriteElement(writer, TAGS.EXENTSION_BASEOFF, fp.Item3, TAGS.EXTENSION_FUN); writer.WriteEndElement(); } writer.WriteEndElement(); // extensions' parameters writer.WriteStartElement(TAGS.Enclosure(TAGS.EXTENSION_PAR)); foreach (var ep in ctryContent.extensionPar) { writer.WriteStartElement(TAGS.EXTENSION_PAR); TransformerCommon.TranslateAndWriteElement(writer, TAGS.EXTENSION_ID, ep.Item1, TAGS.EXTENSION_PAR); TransformerCommon.TranslateAndWriteElement(writer, TAGS.PAR_ID, ep.Item2, TAGS.EXTENSION_PAR); TransformerCommon.TranslateAndWriteElement(writer, TAGS.EXENTSION_BASEOFF, ep.Item3, TAGS.EXTENSION_PAR); writer.WriteEndElement(); } writer.WriteEndElement(); // extensions' switches writer.WriteStartElement(TAGS.Enclosure(TAGS.EXTENSION_SWITCH)); foreach (var sw in dataContent.policySwitches) { writer.WriteStartElement(TAGS.EXTENSION_SWITCH); TransformerCommon.TranslateAndWriteElement(writer, TAGS.EXTENSION_ID, sw.switchPolID, TAGS.EXTENSION_SWITCH); TransformerCommon.TranslateAndWriteElement(writer, TAGS.SYS_ID, sw.sysID, TAGS.EXTENSION_SWITCH); TransformerCommon.TranslateAndWriteElement(writer, TAGS.DATA_ID, sw.dataID, TAGS.EXTENSION_SWITCH); TransformerCommon.TranslateAndWriteElement(writer, TAGS.VALUE, sw.value, TAGS.EXTENSION_SWITCH); writer.WriteEndElement(); } writer.WriteEndElement(); // LOOKGROUPS // groups themself writer.WriteStartElement(TAGS.Enclosure(TAGS.LOOKGROUP)); foreach (var lg in ctryContent.lookGroup.Values) { WriteItem(TAGS.LOOKGROUP, lg); } writer.WriteEndElement(); // groups' policies writer.WriteStartElement(TAGS.Enclosure(TAGS.LOOKGROUP_POL)); foreach (var lgp in ctryContent.lookGroupPol) { writer.WriteStartElement(TAGS.LOOKGROUP_POL); TransformerCommon.TranslateAndWriteElement(writer, TAGS.LOOKGROUP_ID, lgp.Item1, TAGS.LOOKGROUP_POL); TransformerCommon.TranslateAndWriteElement(writer, TAGS.POL_ID, lgp.Item2, TAGS.LOOKGROUP_POL); writer.WriteEndElement(); } writer.WriteEndElement(); // groups' functions writer.WriteStartElement(TAGS.Enclosure(TAGS.LOOKGROUP_FUN)); foreach (var lgf in ctryContent.lookGroupFun) { writer.WriteStartElement(TAGS.LOOKGROUP_FUN); TransformerCommon.TranslateAndWriteElement(writer, TAGS.LOOKGROUP_ID, lgf.Item1, TAGS.LOOKGROUP_FUN); TransformerCommon.TranslateAndWriteElement(writer, TAGS.FUN_ID, lgf.Item2, TAGS.LOOKGROUP_FUN); writer.WriteEndElement(); } writer.WriteEndElement(); // groups' parameters writer.WriteStartElement(TAGS.Enclosure(TAGS.LOOKGROUP_PAR)); foreach (var lgp in ctryContent.lookGroupPar) { writer.WriteStartElement(TAGS.LOOKGROUP_PAR); TransformerCommon.TranslateAndWriteElement(writer, TAGS.LOOKGROUP_ID, lgp.Item1, TAGS.LOOKGROUP_PAR); TransformerCommon.TranslateAndWriteElement(writer, TAGS.PAR_ID, lgp.Item2, TAGS.LOOKGROUP_PAR); writer.WriteEndElement(); } writer.WriteEndElement(); // INDIRECT TAX - PROPERTIES (name, comment, ...) writer.WriteStartElement(TAGS.Enclosure(TAGS.INDTAX)); foreach (var indTax in ctryContent.indTax.Values) { WriteItem(TAGS.INDTAX, indTax); } writer.WriteEndElement(); // INDIRECT TAX - VALUES PER YEAR writer.WriteStartElement(TAGS.Enclosure(TAGS.INDTAX_YEAR)); foreach (var indTaxVal in ctryContent.indTaxVal) { writer.WriteStartElement(TAGS.INDTAX_YEAR); WriteProperties(indTaxVal, TAGS.INDTAX_YEAR); writer.WriteEndElement(); } writer.WriteEndElement(); writer.WriteEndElement(); // ROOT_ELEMENT return(true); } } catch (Exception exception) { errors.Add($"{ctryContent.general.name}: {exception.Message}"); return(false); } void WriteItem(string tag, EM2Item item) { writer.WriteStartElement(tag); if (item.id != null) { TransformerCommon.TranslateAndWriteElement(writer, TAGS.ID, item.id, tag); } if (item.name != null) { TransformerCommon.TranslateAndWriteElement(writer, TAGS.NAME, item.name, tag); } WriteProperties(item.properties, tag); writer.WriteEndElement(); } void WriteProperties(Dictionary <string, string> properties, string tag) { foreach (var p in properties) { if (!string.IsNullOrEmpty(p.Value)) { TransformerCommon.TranslateAndWriteElement(writer, p.Key, p.Value, tag); } } } void WriteSysItem(string mainTag, string itemTag, string valTag, EM2Country.SysItem sysItem) { writer.WriteStartElement(mainTag); TransformerCommon.TranslateAndWriteElement(writer, TAGS.SYS_ID, sysItem.sysID, mainTag); TransformerCommon.TranslateAndWriteElement(writer, itemTag, sysItem.itemID, mainTag); TransformerCommon.TranslateAndWriteElement(writer, TAGS.ORDER, sysItem.order, mainTag); TransformerCommon.TranslateAndWriteElement(writer, valTag, sysItem.value, mainTag); writer.WriteEndElement(); } }