Example #1
0
 // 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]);
     }
 }
Example #2
0
 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") }");
                }
Example #8
0
        /// <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);
                }
            }
        }
Example #10
0
        /// <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();
            }
        }