using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; using System.Globalization; using Microsoft.AnalysisServices; namespace BismNormalizer.TabularCompare.MultidimensionalMetadata { /// /// Abstraction of a tabular model measure with properties and methods for comparison purposes. /// public class Measure : ITabularObject { private TabularModel _parentTabularModel; private string _tableName; private string _name; private string _expression; private string _objectDefinition; private bool _IsKpiReferenceMeasure; /// /// Initializes a new instance of the Measure class using multiple parameters. /// /// TabularModel object that the measure belongs to. /// Name of the table that the Measure belongs to. /// Name of the measure. /// DAX expression public Measure(TabularModel parentTabularModel, string tableName, string measureName, string expression) { _parentTabularModel = parentTabularModel; _tableName = tableName; _name = measureName; _expression = expression; _IsKpiReferenceMeasure = false; PopulateObjectDefinition(); } private void PopulateObjectDefinition() { _objectDefinition = "Expression:\n" + _expression + "\n\n"; if (this.AmoCalculationProperty != null) { if (this.AmoCalculationProperty.Annotations.Contains("Format") && this.AmoCalculationProperty.Annotations["Format"].Value.Attributes["Format"] != null) { _objectDefinition += "Format & Visibility:\n"; switch (this.AmoCalculationProperty.Annotations["Format"].Value.Attributes["Format"].Value) { case "General": _objectDefinition += "Format: General"; break; case "NumberDecimal": _objectDefinition += "Format: Decimal Number" + (this.AmoCalculationProperty.Annotations["Format"].Value.Attributes["Accuracy"] != null ? ", Decimal Places: " + this.AmoCalculationProperty.Annotations["Format"].Value.Attributes["Accuracy"].Value : "") + (this.AmoCalculationProperty.Annotations["Format"].Value.Attributes["ThousandSeparator"] != null ? ", Show Thousand Separator: " + this.AmoCalculationProperty.Annotations["Format"].Value.Attributes["ThousandSeparator"].Value : ""); break; case "NumberWhole": _objectDefinition += "Format: Whole Number" + (this.AmoCalculationProperty.Annotations["Format"].Value.Attributes["ThousandSeparator"] != null ? ", Show Thousand Separator: " + this.AmoCalculationProperty.Annotations["Format"].Value.Attributes["ThousandSeparator"].Value : ""); break; case "Percentage": _objectDefinition += "Format: Percentage" + (this.AmoCalculationProperty.Annotations["Format"].Value.Attributes["Accuracy"] != null ? ", Decimal Places: " + this.AmoCalculationProperty.Annotations["Format"].Value.Attributes["Accuracy"].Value : "") + (this.AmoCalculationProperty.Annotations["Format"].Value.Attributes["ThousandSeparator"] != null ? ", Show Thousand Separator: " + this.AmoCalculationProperty.Annotations["Format"].Value.Attributes["ThousandSeparator"].Value : ""); break; case "Scientific": _objectDefinition += "Format: Scientific" + (this.AmoCalculationProperty.Annotations["Format"].Value.Attributes["Accuracy"] != null ? ", Decimal Places: " + this.AmoCalculationProperty.Annotations["Format"].Value.Attributes["Accuracy"].Value : ""); break; case "Currency": _objectDefinition += "Format: Currency" + (this.AmoCalculationProperty.Annotations["Format"].Value.Attributes["Accuracy"] != null ? ", Decimal Places: " + this.AmoCalculationProperty.Annotations["Format"].Value.Attributes["Accuracy"].Value : "") + (this.AmoCalculationProperty.Annotations["Format"].Value.HasChildNodes && this.AmoCalculationProperty.Annotations["Format"].Value.ChildNodes[0].Attributes["DisplayName"] != null ? ", Currency Symbol: " + this.AmoCalculationProperty.Annotations["Format"].Value.ChildNodes[0].Attributes["DisplayName"].Value : ""); break; case "DateTimeCustom": _objectDefinition += "Format: Date" + (this.AmoCalculationProperty.Annotations["Format"].Value.HasChildNodes && this.AmoCalculationProperty.Annotations["Format"].Value.ChildNodes[0].HasChildNodes && this.AmoCalculationProperty.Annotations["Format"].Value.ChildNodes[0].ChildNodes[0].Attributes["FormatString"] != null ? ", Date Format: " + this.AmoCalculationProperty.Annotations["Format"].Value.ChildNodes[0].ChildNodes[0].Attributes["FormatString"].Value : ""); break; case "Boolean": _objectDefinition += "Format: TRUE/FALSE"; break; default: break; } if (this.AmoCalculationProperty.Annotations.Contains("IsPrivate") && this.AmoCalculationProperty.Annotations["IsPrivate"].Value != null) { _objectDefinition += ", Hidden: " + this.AmoCalculationProperty.Annotations["IsPrivate"].Value.Value; } //if (_parentTabularModel.ComparisonInfo.OptionsInfo.OptionDisplayFolders) //{ // _objectDefinition += ", Display Folder: " + (this.AmoCalculationProperty.DisplayFolder == null ? "" : this.AmoCalculationProperty.DisplayFolder); //} //if (_parentTabularModel.ComparisonInfo.OptionsInfo.OptionTranslations) //{ // _objectDefinition += ", Measure Translations: "; // if (this.AmoCalculationProperty.Translations.Count > 0) // { // _objectDefinition += "["; // foreach (Translation measureTranslation in this.AmoCalculationProperty.Translations) // { // _objectDefinition += CultureInfo.GetCultureInfo(measureTranslation.Language).DisplayName + ": " + measureTranslation.Caption + ", "; // } // _objectDefinition = _objectDefinition.Substring(0, _objectDefinition.Length - 2) + "]"; // } // if (_parentTabularModel.ComparisonInfo.OptionsInfo.OptionDisplayFolders) // { // _objectDefinition += ", Display Folder Translations: "; // if (this.AmoCalculationProperty.Translations.Count > 0) // { // _objectDefinition += "["; // foreach (Translation measureDisplayFolderTranslation in this.AmoCalculationProperty.Translations) // { // _objectDefinition += CultureInfo.GetCultureInfo(measureDisplayFolderTranslation.Language).DisplayName + ": " + measureDisplayFolderTranslation.DisplayFolder + ", "; // } // _objectDefinition = _objectDefinition.Substring(0, _objectDefinition.Length - 2) + "]"; // } // } //} _objectDefinition += "\n\n"; } } } /// /// TabularModel object that the Measure object belongs to. /// public TabularModel ParentTabularModel => _parentTabularModel; /// /// Name of the table that the Measure oject belongs to. /// public string TableName { get { return _tableName; } set { _tableName = value; } } /// /// Name of the Measure object. /// public string Name { get { return _name; } set { _name = value; } } /// /// Long name of the Measure object. /// public string LongName => _name; /// /// Id of the Measure object. /// public string Id => $"'{_tableName}'[{_name}]"; /// /// Substitute Id of the Measure object. /// public string SubstituteId => this.Id; /// /// DAX expression of the Measure object. /// public string Expression { get { return _expression; } set { _expression = value; } } /// /// Boolean indicating if the Measure object is a KPI reference measure. /// public bool IsKpiReferenceMeasure { get { return _IsKpiReferenceMeasure; } set { _IsKpiReferenceMeasure = value; } } /// /// Object definition of the Measure object. This is a simplified list of relevant attribute values for comparison; not the XMLA definition of the abstracted AMO object. /// public virtual string ObjectDefinition => _objectDefinition; /// /// Calculation reference of the Measure object. /// public string CalculationReference => $"[{_name}]"; /// /// Analysis Management Objects CalculationProperty object for the Measure object. /// public CalculationProperty AmoCalculationProperty { get { if (_parentTabularModel.AmoDatabase.Cubes[0].MdxScripts[0].CalculationProperties.Contains(this.CalculationReference)) { return _parentTabularModel.AmoDatabase.Cubes[0].MdxScripts[0].CalculationProperties[this.CalculationReference]; } else { return null; } } } public override string ToString() => this.GetType().FullName; /// /// Find missing dependencies. /// /// List of dependencies. public List FindMissingCalculationDependencies() { List dependencies = new List(); using (StringReader lines = new StringReader(_expression)) { string line = string.Empty; while ((line = lines.ReadLine()) != null) { string whatsLeftOfLine = line; while (whatsLeftOfLine.Contains('[') && whatsLeftOfLine.Contains(']')) { int openSquareBracketPosition = whatsLeftOfLine.IndexOf('[', 0); //someone has ]] instead of ] int closeSquareBracketPosition = whatsLeftOfLine.Replace("]]", " ").IndexOf(']', openSquareBracketPosition + 1); if (openSquareBracketPosition < closeSquareBracketPosition - 1) { string potentialDependency = whatsLeftOfLine.Substring(openSquareBracketPosition + 1, closeSquareBracketPosition - openSquareBracketPosition - 1); if (!potentialDependency.Contains('"') && !dependencies.Contains(potentialDependency)) { //someone did a replace on ] with ]] dependencies.Add(potentialDependency); } } whatsLeftOfLine = whatsLeftOfLine.Substring(closeSquareBracketPosition + 1); } } } List missingDependencies = new List(); foreach (string dependency in dependencies) { bool foundDependency = false; //need to check internal measures because of references to KPI goals in measures //// check if it is another measure //foreach (Measure measure in _parentTabularModel.Measures) //{ // if (measure.Name == dependency) // { // foundDependency = true; // break; // } //} foreach (Measure measure in _parentTabularModel.MeasuresFull) { if (measure.Name == dependency) { foundDependency = true; break; } } if (!foundDependency) { // check if it is a kpi foreach (Measure kpi in _parentTabularModel.Kpis) { if (kpi.Name == dependency) { foundDependency = true; break; } } } if (!foundDependency) { // check if it is a column foreach (Table table in _parentTabularModel.Tables) { foreach (DimensionAttribute column in table.AmoDimension.Attributes) { if (column.Name == dependency) { foundDependency = true; break; } } if (foundDependency) { break; } } } if (!foundDependency) { missingDependencies.Add(dependency); } } return missingDependencies; } } }