From b498a07532f70079c0f153ed76bb10aebc486dcf Mon Sep 17 00:00:00 2001 From: Robin Munn Date: Tue, 16 Nov 2021 15:53:12 +0700 Subject: [PATCH 01/17] Initial data for canonical Language Forge tags --- data/canonical-lf-tags.xml | 237 +++++++++++++++++++++++++++++++++++++ 1 file changed, 237 insertions(+) create mode 100644 data/canonical-lf-tags.xml diff --git a/data/canonical-lf-tags.xml b/data/canonical-lf-tags.xml new file mode 100644 index 00000000..4d3e32c8 --- /dev/null +++ b/data/canonical-lf-tags.xml @@ -0,0 +1,237 @@ + + + + + + + + + + + + star + + étoile + + + + + + + + + + + + + + + + red + + rouge + + + + + + red + + rouge + + + + + + + + + + + + + + + + yellow + + jaune + + + + + + yellow + + jaune + + + + + + + + + + + + + + + + green + + vert + + + + + + green + + vert + + + + + + + + + + + + + + + + blue + + bleu + + + + + + blue + + bleu + + + + + + + + + + + + + + + + purple + + violet + + + + + + purple + + violet + + + + + + + + + + + + + + + + brown + + marron + + + + + + brown + + marron + + + + + + + + + + + + + + + + lt. gray + + gris cl. + + + + + + light gray + + gris clair + + + + + + + + + + + + + + + + dk. gray + + gris f. + + + + + + dark gray + + gris foncé + + + + + + + + + + + + + + + From 2588fd3a5a4ab68144dc9898b2994df93e4de600 Mon Sep 17 00:00:00 2001 From: Robin Munn Date: Tue, 16 Nov 2021 15:54:58 +0700 Subject: [PATCH 02/17] Assign GUIDs to canonical LF tags --- data/canonical-lf-tags.xml | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/data/canonical-lf-tags.xml b/data/canonical-lf-tags.xml index 4d3e32c8..63bfed79 100644 --- a/data/canonical-lf-tags.xml +++ b/data/canonical-lf-tags.xml @@ -1,6 +1,6 @@ - + @@ -26,7 +26,7 @@ - + red rouge @@ -52,7 +52,7 @@ - + yellow jaune @@ -78,7 +78,7 @@ - + green vert @@ -104,7 +104,7 @@ - + blue bleu @@ -130,7 +130,7 @@ - + purple violet @@ -156,7 +156,7 @@ - + brown marron @@ -182,7 +182,7 @@ - + lt. gray gris cl. @@ -208,7 +208,7 @@ - + dk. gray gris f. From 703fdb42172437ffa4e5b9b33a713e7e6f81e33e Mon Sep 17 00:00:00 2001 From: Robin Munn Date: Tue, 16 Nov 2021 16:28:57 +0700 Subject: [PATCH 03/17] Handle canonical LF tag names in LfMerge Now LfMerge can create a CmPossibilityList in FLEx data if it doesn't already exist, which will allow us to create the custom field that will store LF tags in FLEx data. --- .../CanonicalSources/CanonicalLfTagItem.cs | 62 +++++++++++++++++++ .../CanonicalSources/CanonicalLfTagSource.cs | 18 ++++++ .../CanonicalOptionListSource.cs | 2 + src/LfMerge.Core/LfMerge.Core.csproj | 4 ++ src/LfMerge.Core/MagicStrings.cs | 2 + 5 files changed, 88 insertions(+) create mode 100644 src/LfMerge.Core/DataConverters/CanonicalSources/CanonicalLfTagItem.cs create mode 100644 src/LfMerge.Core/DataConverters/CanonicalSources/CanonicalLfTagSource.cs diff --git a/src/LfMerge.Core/DataConverters/CanonicalSources/CanonicalLfTagItem.cs b/src/LfMerge.Core/DataConverters/CanonicalSources/CanonicalLfTagItem.cs new file mode 100644 index 00000000..ff321ab7 --- /dev/null +++ b/src/LfMerge.Core/DataConverters/CanonicalSources/CanonicalLfTagItem.cs @@ -0,0 +1,62 @@ +// Copyright (c) 2016-2018 SIL International +// This software is licensed under the MIT license (http://opensource.org/licenses/MIT) +using System.Xml; +using SIL.LCModel; + +namespace LfMerge.Core.DataConverters.CanonicalSources +{ + public class CanonicalLfTagItem : CanonicalItem + { + public override void PopulateFromXml(XmlReader reader) + { + if (reader.LocalName != "item" || string.IsNullOrEmpty(reader.GetAttribute("guid"))) + return; // If we weren't on the right kind of node, do nothing + GuidStr = reader.GetAttribute("guid"); + while (reader.Read()) + { + switch (reader.NodeType) + { + case XmlNodeType.Element: + { + switch (reader.LocalName) + { + case "item": + if (!string.IsNullOrEmpty(reader.GetAttribute("id"))) + { + Key = reader.GetAttribute("id"); + } + break; + case "abbrev": + AddAbbrev(reader.GetAttribute("ws"), reader.ReadInnerXml()); + break; + case "term": + AddName(reader.GetAttribute("ws"), reader.ReadInnerXml()); + break; + case "def": + AddDescription(reader.GetAttribute("ws"), reader.ReadInnerXml()); + break; + } + break; + } + case XmlNodeType.EndElement: + { + if (reader.LocalName == "item") + { + if (string.IsNullOrEmpty(Key)) { + Key = AbbrevByWs(KeyWs); + } + reader.Read(); // Skip past the closing element before returning + return; + } + break; + } + } + } + } + + protected override void PopulatePossibilityFromExtraData(ICmPossibility poss) + { + // CanonicalLfTagItem instances don't need anything from ExtraData + } + } +} diff --git a/src/LfMerge.Core/DataConverters/CanonicalSources/CanonicalLfTagSource.cs b/src/LfMerge.Core/DataConverters/CanonicalSources/CanonicalLfTagSource.cs new file mode 100644 index 00000000..e4313b4b --- /dev/null +++ b/src/LfMerge.Core/DataConverters/CanonicalSources/CanonicalLfTagSource.cs @@ -0,0 +1,18 @@ +// Copyright (c) 2016 SIL International +// This software is licensed under the MIT license (http://opensource.org/licenses/MIT) + +namespace LfMerge.Core.DataConverters.CanonicalSources +{ + public class CanonicalLfTagSource : CanonicalOptionListSource + { + public CanonicalLfTagSource() + : base("canonical-lf-tags.xml", "item") + { + } + + public override void LoadCanonicalData() + { + LoadCanonicalData(); + } + } +} diff --git a/src/LfMerge.Core/DataConverters/CanonicalSources/CanonicalOptionListSource.cs b/src/LfMerge.Core/DataConverters/CanonicalSources/CanonicalOptionListSource.cs index c8fc23c0..2371447e 100644 --- a/src/LfMerge.Core/DataConverters/CanonicalSources/CanonicalOptionListSource.cs +++ b/src/LfMerge.Core/DataConverters/CanonicalSources/CanonicalOptionListSource.cs @@ -36,6 +36,8 @@ public static CanonicalOptionListSource Create(string listCode) return new CanonicalPartOfSpeechSource(); else if (listCode == MagicStrings.LfOptionListCodeForSemanticDomains) return new CanonicalSemanticDomainSource(); + else if (listCode == MagicStrings.LfOptionListCodeForLfTags) + return new CanonicalLfTagSource(); else return null; } diff --git a/src/LfMerge.Core/LfMerge.Core.csproj b/src/LfMerge.Core/LfMerge.Core.csproj index 59101c2e..8375badb 100644 --- a/src/LfMerge.Core/LfMerge.Core.csproj +++ b/src/LfMerge.Core/LfMerge.Core.csproj @@ -59,5 +59,9 @@ See full changelog at https://github.com/sillsdev/LfMerge/blob/master/CHANGELOG. SemDom.xml SemDom.xml + + canonical-lf-tags.xml + canonical-lf-tags.xml + \ No newline at end of file diff --git a/src/LfMerge.Core/MagicStrings.cs b/src/LfMerge.Core/MagicStrings.cs index bef4967f..cfb8f598 100644 --- a/src/LfMerge.Core/MagicStrings.cs +++ b/src/LfMerge.Core/MagicStrings.cs @@ -16,6 +16,7 @@ static MagicStrings() // Option lists that are currently used in LF (as of 2016-03-01) { LfOptionListCodeForGrammaticalInfo, "Part of Speech" }, { LfOptionListCodeForSemanticDomains, "Semantic Domain" }, + { LfOptionListCodeForLfTags, "LF Tags" }, { LfOptionListCodeForAcademicDomainTypes, "Academic Domains" }, { LfOptionListCodeForEnvironments, "Environments" }, { LfOptionListCodeForLocations, "Location" }, @@ -44,6 +45,7 @@ static MagicStrings() // Option lists that are currently used in LF (as of 2016-03-01) public const string LfOptionListCodeForGrammaticalInfo = "grammatical-info"; public const string LfOptionListCodeForSemanticDomains = "semantic-domain-ddp4"; + public const string LfOptionListCodeForLfTags = "lf-entry-tags"; public const string LfOptionListCodeForAcademicDomainTypes = "domain-type"; public const string LfOptionListCodeForEnvironments = "environments"; public const string LfOptionListCodeForLocations = "location"; From a1fda54719001e8b1ede6c461ce565397feb67e6 Mon Sep 17 00:00:00 2001 From: Robin Munn Date: Thu, 18 Nov 2021 17:21:04 +0700 Subject: [PATCH 04/17] Send/Receive can now create canonical LF Tags list FLEx projects will now have a custom list created if it doesn't already exist, using a GUID reserved for this purpose, for storing Language Forge tags. We should probably put something in the list description like "Please do not edit this list", but since it will be re-created if it doesn't exist, that's probably not needed. --- .../CanonicalSources/CanonicalLfTagSource.cs | 24 ++++++++++++ .../ConvertLcmToMongoLexicon.cs | 2 + .../ConvertMongoToLcmLexicon.cs | 37 ++++++++++++++++++- .../ConvertMongoToLcmOptionList.cs | 14 ++++--- .../Infrastructure/LanguageForgeProxy.cs | 14 +++++++ src/LfMerge.Core/MagicStrings.cs | 4 ++ 6 files changed, 89 insertions(+), 6 deletions(-) diff --git a/src/LfMerge.Core/DataConverters/CanonicalSources/CanonicalLfTagSource.cs b/src/LfMerge.Core/DataConverters/CanonicalSources/CanonicalLfTagSource.cs index e4313b4b..370ed6b9 100644 --- a/src/LfMerge.Core/DataConverters/CanonicalSources/CanonicalLfTagSource.cs +++ b/src/LfMerge.Core/DataConverters/CanonicalSources/CanonicalLfTagSource.cs @@ -1,6 +1,10 @@ // Copyright (c) 2016 SIL International // This software is licensed under the MIT license (http://opensource.org/licenses/MIT) +using LfMerge.Core.FieldWorks; +using SIL.LCModel; +using System.Collections.Generic; + namespace LfMerge.Core.DataConverters.CanonicalSources { public class CanonicalLfTagSource : CanonicalOptionListSource @@ -14,5 +18,25 @@ public override void LoadCanonicalData() { LoadCanonicalData(); } + + public ICmPossibilityList EnsureLcmPossibilityListExists(FwServiceLocatorCache serviceLocator, System.Guid parentListGuid, string listName, int wsForKeys) { + if (byKey.Count == 0) { + LoadCanonicalData(); + } + var repo = serviceLocator.GetInstance(); + var listFactory = serviceLocator.GetInstance(); + var guid = new System.Guid(MagicStrings.LcmCustomFieldGuidForLfTags); + ICmPossibilityList possList; + if (!repo.TryGetObject(guid, out possList) { + possList = listFactory.CreateUnowned(guid, MagicStrings.LcmCustomFieldNameForLfTags, wsForKeys); + } + var converter = new ConvertMongoToLcmOptionList(serviceLocator.GetInstance(), null, null, possList, wsEn, this); + foreach (KeyValuePair item in this.byKey) + { + converter.FindOrCreateFromCanonicalItem(item.Value); + } + // TODO: Write acceptance test that verifies that a CmPossibilityList with the right GUID gets created and populated. + return possList; + } } } diff --git a/src/LfMerge.Core/DataConverters/ConvertLcmToMongoLexicon.cs b/src/LfMerge.Core/DataConverters/ConvertLcmToMongoLexicon.cs index 21692017..6fe54d31 100644 --- a/src/LfMerge.Core/DataConverters/ConvertLcmToMongoLexicon.cs +++ b/src/LfMerge.Core/DataConverters/ConvertLcmToMongoLexicon.cs @@ -43,6 +43,7 @@ public class ConvertLcmToMongoLexicon private const string SenseTypeListCode = MagicStrings.LfOptionListCodeForSenseTypes; private const string AnthroCodeListCode = MagicStrings.LfOptionListCodeForAnthropologyCodes; private const string StatusListCode = MagicStrings.LfOptionListCodeForStatus; + private const string LfTagsListCode = MagicStrings.LfOptionListCodeForLfTags; private IDictionary ListConverters; @@ -83,6 +84,7 @@ public ConvertLcmToMongoLexicon(ILfProject lfProject, ILogger logger, IMongoConn ListConverters[SenseTypeListCode] = ConvertOptionListFromLcm(LfProject, SenseTypeListCode, ServiceLocator.LanguageProject.LexDbOA.SenseTypesOA); ListConverters[AnthroCodeListCode] = ConvertOptionListFromLcm(LfProject, AnthroCodeListCode, ServiceLocator.LanguageProject.AnthroListOA); ListConverters[StatusListCode] = ConvertOptionListFromLcm(LfProject, StatusListCode, ServiceLocator.LanguageProject.StatusOA); + // ListConverters[LfTagsListCode] = ConvertOptionListFromLcm(LfProject, StatusListCode, null); // Don't do this after all. This one needs special handling. _convertCustomField = new ConvertLcmToMongoCustomField(Cache, ServiceLocator, logger); foreach (KeyValuePair pair in _convertCustomField.GetCustomFieldParentLists()) diff --git a/src/LfMerge.Core/DataConverters/ConvertMongoToLcmLexicon.cs b/src/LfMerge.Core/DataConverters/ConvertMongoToLcmLexicon.cs index 4d67e33f..9d4b1bbc 100644 --- a/src/LfMerge.Core/DataConverters/ConvertMongoToLcmLexicon.cs +++ b/src/LfMerge.Core/DataConverters/ConvertMongoToLcmLexicon.cs @@ -51,6 +51,7 @@ public class ConvertMongoToLcmLexicon private const string SenseTypeListCode = MagicStrings.LfOptionListCodeForSenseTypes; private const string AnthroCodeListCode = MagicStrings.LfOptionListCodeForAnthropologyCodes; private const string StatusListCode = MagicStrings.LfOptionListCodeForStatus; + private const string LfTagsListCode = MagicStrings.LfOptionListCodeForLfTags; private IDictionary ListConverters; @@ -83,6 +84,7 @@ public ConvertMongoToLcmLexicon(LfMergeSettings settings, ILfProject lfproject, ListConverters[SenseTypeListCode] = PrepareOptionListConverter(SenseTypeListCode); ListConverters[AnthroCodeListCode] = PrepareOptionListConverter(AnthroCodeListCode); ListConverters[StatusListCode] = PrepareOptionListConverter(StatusListCode); + ListConverters[LfTagsListCode] = PrepareOptionListConverterFromCanonicalSource(LfTagsListCode); _wsEn = ServiceLocator.WritingSystemFactory.GetWsFromStr("en"); @@ -112,7 +114,36 @@ private ConvertMongoToLcmOptionList PrepareOptionListConverter(string listCode) { LfOptionList optionListToConvert = Connection.GetLfOptionListByCode(LfProject, listCode); return new ConvertMongoToLcmOptionList(GetInstance(), - optionListToConvert, Logger, CanonicalOptionListSource.Create(listCode)); + optionListToConvert, Logger, null, 0, CanonicalOptionListSource.Create(listCode)); + } + + private ConvertMongoToLcmOptionList PrepareOptionListConverterFromCanonicalSource(string listCode) + { + // lf-tags custom field needs special handling + // 1. Check if parent list for LF Tags already exists in LCM + // 2. Create it if it doesn't, using canonical source data + + CanonicalLfTagSource canonicalSource = (CanonicalLfTagSource)CanonicalOptionListSource.Create(listCode); + // TODO: That's hard-coded for LfTags list. Handle it way better in the future by making it able to handle other lists. + // I.e., move the method up into the parent, and skip it for lists that we shouldn't necessarily create (semantic domains, grammatical categories) + ICmPossibilityList possList = canonicalSource?.EnsureLcmPossibilityListExists( + ServiceLocator, + new System.Guid(MagicStrings.LcmCustomFieldGuidForLfTags), + MagicStrings.LcmCustomFieldNameForLfTags, + _wsEn + ); + + // 3. Check if custom field already exists in LCM + // 4. Create it if it doesn't, using parent list that is now guaranteed to exist + + // TODO: Implement using some of the following methods: + // int AddCustomField(string className, string fieldName, CellarPropertyType fieldType, int destinationClass, string fieldHelp, int fieldWs, Guid fieldListRoot); + // bool FieldExists(int classId, string fieldName, bool includeBaseClasses); + // bool FieldExists(string className, string fieldName, bool includeBaseClasses); + // bool FieldExists(int flid); + + return new ConvertMongoToLcmOptionList(GetInstance(), + null, Logger, possList, _wsEn, CanonicalOptionListSource.Create(listCode)); } // Once we allow LanguageForge to create optionlist items with "canonical" values (parts of speech, semantic domains, etc.), replace the function @@ -570,6 +601,10 @@ private void LfLexEntryToLcmLexEntry(LfLexEntry lfEntry) // lfEntry.Senses -> LcmEntry.SensesOS SetLcmListFromLfList(LcmEntry, LcmEntry.SensesOS, lfEntry.Senses, LfSenseToLcmSense); + // TODO: Handle lf-tags custom field with something like the following + // ListConverters[AnthroCodeListCode].UpdatePossibilitiesFromStringArray(LcmSense.AnthroCodesRC, + // lfSense.AnthropologyCategories); + _convertCustomField.SetCustomFieldsForThisCmObject(LcmEntry, "entry", lfEntry.CustomFields, lfEntry.CustomFieldGuids); diff --git a/src/LfMerge.Core/DataConverters/ConvertMongoToLcmOptionList.cs b/src/LfMerge.Core/DataConverters/ConvertMongoToLcmOptionList.cs index 164bbce3..a74b8c11 100644 --- a/src/LfMerge.Core/DataConverters/ConvertMongoToLcmOptionList.cs +++ b/src/LfMerge.Core/DataConverters/ConvertMongoToLcmOptionList.cs @@ -26,10 +26,7 @@ public class ConvertMongoToLcmOptionList public Dictionary PossibilitiesByKey { get; protected set; } - #if false // Once we allow LanguageForge to create optionlist items with "canonical" values (parts of speech, semantic domains, etc.), uncomment this version of the constructor public ConvertMongoToLcmOptionList(IRepository possRepo, LfOptionList lfOptionList, ILogger logger, ICmPossibilityList parentList, int wsForKeys, CanonicalOptionListSource canonicalSource = null) - #endif - public ConvertMongoToLcmOptionList(IRepository possRepo, LfOptionList lfOptionList, ILogger logger, CanonicalOptionListSource canonicalSource = null) { _possRepo = possRepo; _logger = logger; @@ -77,7 +74,6 @@ public ICmPossibility FromStringKey(string key) return null; } - #if false // Once we allow LanguageForge to create optionlist items with "canonical" values (parts of speech, semantic domains, etc.), uncomment this block public ICmPossibility CreateFromCanonicalItem(CanonicalItem item) { if (item.Parent != null) @@ -91,7 +87,15 @@ public ICmPossibility CreateFromCanonicalItem(CanonicalItem item) PossibilitiesByKey[item.Key] = poss; return poss; } - #endif + + public ICmPossibility FindOrCreateFromCanonicalItem(CanonicalItem item) + { + ICmPossibility poss = LookupByCanonicalItem(item); + if (poss == null) { + poss = CreateFromCanonicalItem(item); + } + return poss; + } public ICmPossibility FromStringField(LfStringField keyField) { diff --git a/src/LfMerge.Core/LanguageForge/Infrastructure/LanguageForgeProxy.cs b/src/LfMerge.Core/LanguageForge/Infrastructure/LanguageForgeProxy.cs index 7e0cdb8e..045f281c 100644 --- a/src/LfMerge.Core/LanguageForge/Infrastructure/LanguageForgeProxy.cs +++ b/src/LfMerge.Core/LanguageForge/Infrastructure/LanguageForgeProxy.cs @@ -27,6 +27,20 @@ public string UpdateCustomFieldViews(string projectCode, List c return RunClass(className, methodName, parameters, isTest); } + public string UpdateCustomFieldViewsNonProxied(string projectCode, List customFieldSpecs) { + return UpdateCustomFieldViewsNonProxied(projectCode, customFieldSpecs, false); + } + + public string UpdateCustomFieldViewsNonProxied(string projectCode, List customFieldSpecs, bool isTest) + { + const string className = "Api\\Model\\Languageforge\\Lexicon\\Command\\LexProjectCommands"; + const string methodName = "updateCustomFieldViews"; + var parameters = new List(); + parameters.Add(projectCode); + parameters.Add(customFieldSpecs); + return RunClass(className, methodName, parameters, isTest); + } + public string ListUsers() { const string className = "Api\\Model\\Command\\UserCommands"; diff --git a/src/LfMerge.Core/MagicStrings.cs b/src/LfMerge.Core/MagicStrings.cs index cfb8f598..1b541029 100644 --- a/src/LfMerge.Core/MagicStrings.cs +++ b/src/LfMerge.Core/MagicStrings.cs @@ -91,6 +91,10 @@ static MagicStrings() public const string LanguageCodeForGenDateFields = "qaa-Qaad"; public const string LanguageCodeForIntFields = "qaa-Zmth"; + // Custom fields that we reserve for LF use in Send/Receive FLEx projects + public const string LcmCustomFieldNameForLfTags = "LF_Tags"; + public const string LcmCustomFieldGuidForLfTags = "6d9b3052-195b-46cb-a300-e8598e59fed5"; + // FW strings public const string FwFixitAppName = "FixFwData.exe"; From 9d4a2933a94f7b2bf9ecfce152a8eb446cc00a90 Mon Sep 17 00:00:00 2001 From: Robin Munn Date: Fri, 19 Nov 2021 11:18:58 +0700 Subject: [PATCH 05/17] Send/Receive can now create canonical custom field For handling LF tags, and possibly other LF fields in the future, we can now create a canonical custom field that references a CmPossibilityList created from canonical values embedded in the resource fork of the app. --- .../CanonicalSources/CanonicalLfTagSource.cs | 6 +-- .../CanonicalOptionListSource.cs | 4 ++ .../ConvertMongoToLcmLexicon.cs | 45 ++++++++++--------- .../ConvertMongoToLcmOptionList.cs | 25 +++++++++-- src/LfMerge.Core/MagicStrings.cs | 2 +- 5 files changed, 54 insertions(+), 28 deletions(-) diff --git a/src/LfMerge.Core/DataConverters/CanonicalSources/CanonicalLfTagSource.cs b/src/LfMerge.Core/DataConverters/CanonicalSources/CanonicalLfTagSource.cs index 370ed6b9..62201893 100644 --- a/src/LfMerge.Core/DataConverters/CanonicalSources/CanonicalLfTagSource.cs +++ b/src/LfMerge.Core/DataConverters/CanonicalSources/CanonicalLfTagSource.cs @@ -25,12 +25,12 @@ public ICmPossibilityList EnsureLcmPossibilityListExists(FwServiceLocatorCache s } var repo = serviceLocator.GetInstance(); var listFactory = serviceLocator.GetInstance(); - var guid = new System.Guid(MagicStrings.LcmCustomFieldGuidForLfTags); + var guid = new System.Guid(MagicStrings.LcmOptionListGuidForLfTags); ICmPossibilityList possList; - if (!repo.TryGetObject(guid, out possList) { + if (!repo.TryGetObject(guid, out possList)) { possList = listFactory.CreateUnowned(guid, MagicStrings.LcmCustomFieldNameForLfTags, wsForKeys); } - var converter = new ConvertMongoToLcmOptionList(serviceLocator.GetInstance(), null, null, possList, wsEn, this); + var converter = new ConvertMongoToLcmOptionList(serviceLocator.GetInstance(), null, null, possList, wsForKeys, this); foreach (KeyValuePair item in this.byKey) { converter.FindOrCreateFromCanonicalItem(item.Value); diff --git a/src/LfMerge.Core/DataConverters/CanonicalSources/CanonicalOptionListSource.cs b/src/LfMerge.Core/DataConverters/CanonicalSources/CanonicalOptionListSource.cs index 2371447e..4985f50a 100644 --- a/src/LfMerge.Core/DataConverters/CanonicalSources/CanonicalOptionListSource.cs +++ b/src/LfMerge.Core/DataConverters/CanonicalSources/CanonicalOptionListSource.cs @@ -73,6 +73,10 @@ public bool TryGetByKey(string key, out CanonicalItem result) return (result != null); } + public Dictionary.ValueCollection ValuesByGuid => this.byGuid.Values; + public Dictionary.ValueCollection ValuesByKey => this.byKey.Values; + public int Count => this.byGuid.Count; + // Descendants will override this to specify the generic type T. // E.g., LoadCanonicalData(); public abstract void LoadCanonicalData(); diff --git a/src/LfMerge.Core/DataConverters/ConvertMongoToLcmLexicon.cs b/src/LfMerge.Core/DataConverters/ConvertMongoToLcmLexicon.cs index 9d4b1bbc..fc8df1f0 100644 --- a/src/LfMerge.Core/DataConverters/ConvertMongoToLcmLexicon.cs +++ b/src/LfMerge.Core/DataConverters/ConvertMongoToLcmLexicon.cs @@ -75,6 +75,8 @@ public ConvertMongoToLcmLexicon(LfMergeSettings settings, ILfProject lfproject, //_analysisWritingSystems = ServiceLocator.LanguageProject.CurrentAnalysisWritingSystems; //_vernacularWritingSystems = ServiceLocator.LanguageProject.CurrentVernacularWritingSystems; + _wsEn = ServiceLocator.WritingSystemFactory.GetWsFromStr("en"); + ListConverters = new Dictionary(); ListConverters[GrammarListCode] = PrepareOptionListConverter(GrammarListCode); ListConverters[SemDomListCode] = PrepareOptionListConverter(SemDomListCode); @@ -85,8 +87,7 @@ public ConvertMongoToLcmLexicon(LfMergeSettings settings, ILfProject lfproject, ListConverters[AnthroCodeListCode] = PrepareOptionListConverter(AnthroCodeListCode); ListConverters[StatusListCode] = PrepareOptionListConverter(StatusListCode); ListConverters[LfTagsListCode] = PrepareOptionListConverterFromCanonicalSource(LfTagsListCode); - - _wsEn = ServiceLocator.WritingSystemFactory.GetWsFromStr("en"); + int lfTagsFieldId = EnsureCustomFieldExists(new System.Guid(MagicStrings.LcmOptionListGuidForLfTags), MagicStrings.LcmCustomFieldNameForLfTags); // Once we allow LanguageForge to create optionlist items with "canonical" values (parts of speech, semantic domains, etc.), replace the code block // above with this one (that provides TWO parameters to PrepareOptionListConverter) @@ -119,31 +120,35 @@ private ConvertMongoToLcmOptionList PrepareOptionListConverter(string listCode) private ConvertMongoToLcmOptionList PrepareOptionListConverterFromCanonicalSource(string listCode) { - // lf-tags custom field needs special handling // 1. Check if parent list for LF Tags already exists in LCM // 2. Create it if it doesn't, using canonical source data - CanonicalLfTagSource canonicalSource = (CanonicalLfTagSource)CanonicalOptionListSource.Create(listCode); - // TODO: That's hard-coded for LfTags list. Handle it way better in the future by making it able to handle other lists. - // I.e., move the method up into the parent, and skip it for lists that we shouldn't necessarily create (semantic domains, grammatical categories) - ICmPossibilityList possList = canonicalSource?.EnsureLcmPossibilityListExists( + var canonicalSource = CanonicalOptionListSource.Create(listCode); + var converter = new ConvertMongoToLcmOptionList(GetInstance(), + null, Logger, null, _wsEn, canonicalSource); + ICmPossibilityList parentList = converter.EnsureLcmPossibilityListExists( + canonicalSource, ServiceLocator, - new System.Guid(MagicStrings.LcmCustomFieldGuidForLfTags), - MagicStrings.LcmCustomFieldNameForLfTags, - _wsEn + new System.Guid(MagicStrings.LcmOptionListGuidForLfTags), + MagicStrings.LcmCustomFieldNameForLfTags ); - // 3. Check if custom field already exists in LCM - // 4. Create it if it doesn't, using parent list that is now guaranteed to exist - - // TODO: Implement using some of the following methods: - // int AddCustomField(string className, string fieldName, CellarPropertyType fieldType, int destinationClass, string fieldHelp, int fieldWs, Guid fieldListRoot); - // bool FieldExists(int classId, string fieldName, bool includeBaseClasses); - // bool FieldExists(string className, string fieldName, bool includeBaseClasses); - // bool FieldExists(int flid); + return converter; + } - return new ConvertMongoToLcmOptionList(GetInstance(), - null, Logger, possList, _wsEn, CanonicalOptionListSource.Create(listCode)); + private int EnsureCustomFieldExists(Guid parentListGuid, string name) + { + // 1. Check if custom field already exists in LCM + // 2. Create it if it doesn't, using parent list that is now guaranteed to exist + + var mdc = ServiceLocator.MetaDataCache; + int flid = 0; + if (mdc.FieldExists("LexEntry", name, false)) { + flid = mdc.GetFieldId("LexEntry", name, false); + } else { + flid = mdc.AddCustomField("LexEntry", name, SIL.LCModel.Core.Cellar.CellarPropertyType.ReferenceCollection, CmPossibilityTags.kClassId, "Internal Language Forge field - do not edit", _wsEn, parentListGuid); + } + return flid; } // Once we allow LanguageForge to create optionlist items with "canonical" values (parts of speech, semantic domains, etc.), replace the function diff --git a/src/LfMerge.Core/DataConverters/ConvertMongoToLcmOptionList.cs b/src/LfMerge.Core/DataConverters/ConvertMongoToLcmOptionList.cs index a74b8c11..3153128f 100644 --- a/src/LfMerge.Core/DataConverters/ConvertMongoToLcmOptionList.cs +++ b/src/LfMerge.Core/DataConverters/ConvertMongoToLcmOptionList.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Linq; using LfMerge.Core.DataConverters.CanonicalSources; +using LfMerge.Core.FieldWorks; using LfMerge.Core.LanguageForge.Model; using LfMerge.Core.Logging; using SIL.LCModel; @@ -19,10 +20,8 @@ public class ConvertMongoToLcmOptionList protected LfOptionList _lfOptionList; protected ILogger _logger; protected CanonicalOptionListSource _canonicalSource; - #if false // Once we allow LanguageForge to create optionlist items with "canonical" values (parts of speech, semantic domains, etc.), uncomment this block protected int _wsForKeys; protected ICmPossibilityList _parentList; - #endif public Dictionary PossibilitiesByKey { get; protected set; } @@ -30,10 +29,8 @@ public ConvertMongoToLcmOptionList(IRepository possRepo, LfOptio { _possRepo = possRepo; _logger = logger; - #if false // Once we allow LanguageForge to create optionlist items with "canonical" values (parts of speech, semantic domains, etc.), uncomment this block _parentList = parentList; _wsForKeys = wsForKeys; - #endif _canonicalSource = canonicalSource; RebuildLookupTables(lfOptionList); } @@ -97,6 +94,26 @@ public ICmPossibility FindOrCreateFromCanonicalItem(CanonicalItem item) return poss; } + public ICmPossibilityList EnsureLcmPossibilityListExists(CanonicalOptionListSource canonicalSource, FwServiceLocatorCache serviceLocator, System.Guid parentListGuid, string listName) { + if (canonicalSource.Count == 0) { + canonicalSource.LoadCanonicalData(); + } + var repo = serviceLocator.GetInstance(); + var listFactory = serviceLocator.GetInstance(); + var guid = new System.Guid(MagicStrings.LcmOptionListGuidForLfTags); + if (_parentList == null) { + if (!repo.TryGetObject(guid, out _parentList)) { + _parentList = listFactory.CreateUnowned(guid, MagicStrings.LcmCustomFieldNameForLfTags, _wsForKeys); + } + } + foreach (var item in canonicalSource.ValuesByKey) + { + this.FindOrCreateFromCanonicalItem(item); + } + // TODO: Write acceptance test that verifies that a CmPossibilityList with the right GUID gets created and populated. + return _parentList; // TODO: May not be necessary now + } + public ICmPossibility FromStringField(LfStringField keyField) { if (keyField == null) diff --git a/src/LfMerge.Core/MagicStrings.cs b/src/LfMerge.Core/MagicStrings.cs index 1b541029..350b8c7c 100644 --- a/src/LfMerge.Core/MagicStrings.cs +++ b/src/LfMerge.Core/MagicStrings.cs @@ -93,7 +93,7 @@ static MagicStrings() // Custom fields that we reserve for LF use in Send/Receive FLEx projects public const string LcmCustomFieldNameForLfTags = "LF_Tags"; - public const string LcmCustomFieldGuidForLfTags = "6d9b3052-195b-46cb-a300-e8598e59fed5"; + public const string LcmOptionListGuidForLfTags = "6d9b3052-195b-46cb-a300-e8598e59fed5"; // FW strings public const string FwFixitAppName = "FixFwData.exe"; From ca342d813f4698fb828373d476c185f3a57cdef5 Mon Sep 17 00:00:00 2001 From: Robin Munn Date: Fri, 19 Nov 2021 13:01:34 +0700 Subject: [PATCH 06/17] Skip custom LF Tags field in FW -> LF direction --- .../DataConverters/ConvertLcmToMongoCustomFieldTests.cs | 6 +++--- src/LfMerge.Core.Tests/Lcm/RoundTripBase.cs | 2 +- .../DataConverters/ConvertLcmToMongoCustomField.cs | 6 ++++-- .../DataConverters/ConvertLcmToMongoLexicon.cs | 7 ++++--- 4 files changed, 12 insertions(+), 9 deletions(-) diff --git a/src/LfMerge.Core.Tests/Lcm/DataConverters/ConvertLcmToMongoCustomFieldTests.cs b/src/LfMerge.Core.Tests/Lcm/DataConverters/ConvertLcmToMongoCustomFieldTests.cs index 88825fc5..7b70df52 100644 --- a/src/LfMerge.Core.Tests/Lcm/DataConverters/ConvertLcmToMongoCustomFieldTests.cs +++ b/src/LfMerge.Core.Tests/Lcm/DataConverters/ConvertLcmToMongoCustomFieldTests.cs @@ -24,7 +24,7 @@ public void GetCustomFieldForThisCmObject_ShouldGetSingleLineAll() Assert.That(entry, Is.Not.Null); // Exercise - var customDataDocument = converter.GetCustomFieldsForThisCmObject(entry, "entry", _listConverters); + var customDataDocument = converter.GetCustomFieldsForThisCmObject(entry, "entry", _listConverters, Array.Empty()); // Verify English and french values Assert.That(customDataDocument[0]["customField_entry_Cust_Single_Line_All"].AsBsonDocument.GetElement(0).Value["value"].ToString(), @@ -47,7 +47,7 @@ public void GetCustomFieldForThisCmObject_ShouldGetMultiListRef() // Exercise var senses = entry.SensesOS.ToArray(); - var customDataDocument = converter.GetCustomFieldsForThisCmObject(senses[0], "senses", _listConverters); + var customDataDocument = converter.GetCustomFieldsForThisCmObject(senses[0], "senses", _listConverters, Array.Empty()); // Verify. (Note, in the fwdata file, the custom item labels are in reverse order) Assert.That(customDataDocument[0].AsBsonDocument["customField_senses_Cust_Multi_ListRef"]["values"][1].ToString(), @@ -69,7 +69,7 @@ public void GetCustomFieldsForThisCmObject_ShouldGetCustomFieldSettings() new TestLogger(TestContext.CurrentContext.Test.Name)); // Exercise - var customDataDocument = converter.GetCustomFieldsForThisCmObject(entry, "entry", _listConverters); + var customDataDocument = converter.GetCustomFieldsForThisCmObject(entry, "entry", _listConverters, Array.Empty()); // Verify var expectedCustomFieldNames = new List diff --git a/src/LfMerge.Core.Tests/Lcm/RoundTripBase.cs b/src/LfMerge.Core.Tests/Lcm/RoundTripBase.cs index 3c07062e..f9e9051c 100644 --- a/src/LfMerge.Core.Tests/Lcm/RoundTripBase.cs +++ b/src/LfMerge.Core.Tests/Lcm/RoundTripBase.cs @@ -167,7 +167,7 @@ protected BsonDocument GetCustomFieldValues(LcmCache cache, ICmObject obj, strin { // The objectType parameter is used in the names of the custom fields (and nowhere else). var convertCustomField = new ConvertLcmToMongoCustomField(cache, _servLoc, new LfMerge.Core.Logging.NullLogger()); - return convertCustomField.GetCustomFieldsForThisCmObject(obj, objectType, _listConverters); + return convertCustomField.GetCustomFieldsForThisCmObject(obj, objectType, _listConverters, Array.Empty()); } protected IDictionary GetFieldValuesByName(LcmCache cache, ICmObject obj) diff --git a/src/LfMerge.Core/DataConverters/ConvertLcmToMongoCustomField.cs b/src/LfMerge.Core/DataConverters/ConvertLcmToMongoCustomField.cs index 731d5e4c..4e86a312 100644 --- a/src/LfMerge.Core/DataConverters/ConvertLcmToMongoCustomField.cs +++ b/src/LfMerge.Core/DataConverters/ConvertLcmToMongoCustomField.cs @@ -243,13 +243,15 @@ Dictionary lfCustomFieldTypes /// Either "entry", "senses", or "examples" /// Dictionary of ConvertLcmToMongoOptionList instances, keyed by list code public BsonDocument GetCustomFieldsForThisCmObject(ICmObject cmObj, string objectType, - IDictionary listConverters) + IDictionary listConverters, + string[] fieldNamesToSkip) { if (cmObj == null) return null; List customFieldIds = new List( LcmMetaData.GetFields(cmObj.ClassID, false, (int)CellarPropertyTypeFilter.All) - .Where(flid => cache.GetIsCustomField(flid))); + .Where(flid => cache.GetIsCustomField(flid)) + .Where(flid => !fieldNamesToSkip.Contains(LcmMetaData.GetFieldName(flid)))); var customFieldData = new BsonDocument(); var customFieldGuids = new BsonDocument(); diff --git a/src/LfMerge.Core/DataConverters/ConvertLcmToMongoLexicon.cs b/src/LfMerge.Core/DataConverters/ConvertLcmToMongoLexicon.cs index 6fe54d31..b5c9b685 100644 --- a/src/LfMerge.Core/DataConverters/ConvertLcmToMongoLexicon.cs +++ b/src/LfMerge.Core/DataConverters/ConvertLcmToMongoLexicon.cs @@ -310,7 +310,8 @@ private LfLexEntry LcmLexEntryToLfLexEntry(ILexEntry LcmEntry) lfEntry.Senses.AddRange(LcmEntry.SensesOS.Select(LcmSenseToLfSense)); lfEntry.SummaryDefinition = ToMultiText(LcmEntry.SummaryDefinition); - BsonDocument customFieldsAndGuids = _convertCustomField.GetCustomFieldsForThisCmObject(LcmEntry, "entry", ListConverters); + var customFieldNamesToSkip = new string[] { MagicStrings.LcmCustomFieldNameForLfTags }; + BsonDocument customFieldsAndGuids = _convertCustomField.GetCustomFieldsForThisCmObject(LcmEntry, "entry", ListConverters, customFieldNamesToSkip); BsonDocument customFieldsBson = customFieldsAndGuids["customFields"].AsBsonDocument; BsonDocument customFieldGuids = customFieldsAndGuids["customFieldGuids"].AsBsonDocument; @@ -498,7 +499,7 @@ private LfSense LcmSenseToLfSense(ILexSense lcmSense) lcmSense.PublishIn; */ - BsonDocument customFieldsAndGuids = _convertCustomField.GetCustomFieldsForThisCmObject(lcmSense, "senses", ListConverters); + BsonDocument customFieldsAndGuids = _convertCustomField.GetCustomFieldsForThisCmObject(lcmSense, "senses", ListConverters, Array.Empty()); BsonDocument customFieldsBson = customFieldsAndGuids["customFields"].AsBsonDocument; BsonDocument customFieldGuids = customFieldsAndGuids["customFieldGuids"].AsBsonDocument; @@ -549,7 +550,7 @@ private LfExample LcmExampleToLfExample(ILexExampleSentence LcmExample) lfExample.TranslationGuid = translation.Guid; } - BsonDocument customFieldsAndGuids = _convertCustomField.GetCustomFieldsForThisCmObject(LcmExample, "examples", ListConverters); + BsonDocument customFieldsAndGuids = _convertCustomField.GetCustomFieldsForThisCmObject(LcmExample, "examples", ListConverters, Array.Empty()); BsonDocument customFieldsBson = customFieldsAndGuids["customFields"].AsBsonDocument; BsonDocument customFieldGuids = customFieldsAndGuids["customFieldGuids"].AsBsonDocument; From 0839c2a179c0596c667885ec2c0da322d0e1805d Mon Sep 17 00:00:00 2001 From: Robin Munn Date: Fri, 19 Nov 2021 13:50:17 +0700 Subject: [PATCH 07/17] Convert LF Tags custom field in FW -> LF direction Still to do: LF -> FW direction --- .../ConvertLcmToMongoLexicon.cs | 27 ++++++++++++++++++- .../LanguageForge/Model/LfLexEntry.cs | 1 + 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/src/LfMerge.Core/DataConverters/ConvertLcmToMongoLexicon.cs b/src/LfMerge.Core/DataConverters/ConvertLcmToMongoLexicon.cs index b5c9b685..d7e0042a 100644 --- a/src/LfMerge.Core/DataConverters/ConvertLcmToMongoLexicon.cs +++ b/src/LfMerge.Core/DataConverters/ConvertLcmToMongoLexicon.cs @@ -11,6 +11,7 @@ using LfMerge.Core.MongoConnector; using MongoDB.Bson; using SIL.LCModel; +using SIL.LCModel.Application; using SIL.LCModel.Core.KernelInterfaces; using SIL.Progress; @@ -84,7 +85,13 @@ public ConvertLcmToMongoLexicon(ILfProject lfProject, ILogger logger, IMongoConn ListConverters[SenseTypeListCode] = ConvertOptionListFromLcm(LfProject, SenseTypeListCode, ServiceLocator.LanguageProject.LexDbOA.SenseTypesOA); ListConverters[AnthroCodeListCode] = ConvertOptionListFromLcm(LfProject, AnthroCodeListCode, ServiceLocator.LanguageProject.AnthroListOA); ListConverters[StatusListCode] = ConvertOptionListFromLcm(LfProject, StatusListCode, ServiceLocator.LanguageProject.StatusOA); - // ListConverters[LfTagsListCode] = ConvertOptionListFromLcm(LfProject, StatusListCode, null); // Don't do this after all. This one needs special handling. + + // Custom field "LF Tags" in LCM needs special handling + var lfTagsListGuid = new System.Guid(MagicStrings.LcmOptionListGuidForLfTags); + var possibilityListRepo = ServiceLocator.GetInstance(); + if (possibilityListRepo.TryGetObject(lfTagsListGuid, out var possibilityList)) { + ListConverters[LfTagsListCode] = ConvertOptionListFromLcm(LfProject, LfTagsListCode, possibilityList, updateMongoList: false); + } _convertCustomField = new ConvertLcmToMongoCustomField(Cache, ServiceLocator, logger); foreach (KeyValuePair pair in _convertCustomField.GetCustomFieldParentLists()) @@ -173,6 +180,22 @@ private T GetInstance() where T : class return ServiceLocator.GetInstance(); } + // TODO: This is fine for entries, but it should eventually be more generic so it can handle senses and examples + private LfStringArrayField StringArrayFieldFromCustomField(string listCode, ILexEntry entry, string fieldName) { + var result = Array.Empty(); + int flid = Cache.MetaDataCacheAccessor.GetFieldId("LexEntry", fieldName, false); + if (flid != 0) { + ISilDataAccessManaged data = (ISilDataAccessManaged)Cache.DomainDataByFlid; + int[] hvos = data.VecProp(entry.Hvo, flid); + var possibilities = hvos + .Where(hvo => data.get_IsValidObject(hvo)) + .Select(hvo => Cache.GetAtomicPropObject(hvo)) + .OfType(); + return ToStringArrayField(listCode, possibilities); + } + return null; + } + private LfMultiText ToMultiText(IMultiAccessorBase LcmMultiString) { if (LcmMultiString == null) return null; @@ -310,6 +333,8 @@ private LfLexEntry LcmLexEntryToLfLexEntry(ILexEntry LcmEntry) lfEntry.Senses.AddRange(LcmEntry.SensesOS.Select(LcmSenseToLfSense)); lfEntry.SummaryDefinition = ToMultiText(LcmEntry.SummaryDefinition); + lfEntry.Tags = StringArrayFieldFromCustomField(LfTagsListCode, LcmEntry, MagicStrings.LcmCustomFieldNameForLfTags); + var customFieldNamesToSkip = new string[] { MagicStrings.LcmCustomFieldNameForLfTags }; BsonDocument customFieldsAndGuids = _convertCustomField.GetCustomFieldsForThisCmObject(LcmEntry, "entry", ListConverters, customFieldNamesToSkip); BsonDocument customFieldsBson = customFieldsAndGuids["customFields"].AsBsonDocument; diff --git a/src/LfMerge.Core/LanguageForge/Model/LfLexEntry.cs b/src/LfMerge.Core/LanguageForge/Model/LfLexEntry.cs index 41aa85b0..9af9216d 100644 --- a/src/LfMerge.Core/LanguageForge/Model/LfLexEntry.cs +++ b/src/LfMerge.Core/LanguageForge/Model/LfLexEntry.cs @@ -43,6 +43,7 @@ public class LfLexEntry : LfFieldBase, IHasNullableGuid [BsonRepresentation(BsonType.String)] public Guid PronunciationGuid { get; set; } public LfMultiText SummaryDefinition { get; set; } + public LfStringArrayField Tags { get; set; } public LfMultiText Tone { get; set; } public LfLexEntry() From 0e8e059bfaf4614b8166d0b882be5c4565db5485 Mon Sep 17 00:00:00 2001 From: Robin Munn Date: Fri, 19 Nov 2021 14:01:37 +0700 Subject: [PATCH 08/17] Nicer API for skipping certain custom fields --- .../DataConverters/ConvertLcmToMongoCustomFieldTests.cs | 6 +++--- src/LfMerge.Core.Tests/Lcm/RoundTripBase.cs | 2 +- .../DataConverters/ConvertLcmToMongoCustomField.cs | 2 +- .../DataConverters/ConvertLcmToMongoLexicon.cs | 7 +++---- 4 files changed, 8 insertions(+), 9 deletions(-) diff --git a/src/LfMerge.Core.Tests/Lcm/DataConverters/ConvertLcmToMongoCustomFieldTests.cs b/src/LfMerge.Core.Tests/Lcm/DataConverters/ConvertLcmToMongoCustomFieldTests.cs index 7b70df52..88825fc5 100644 --- a/src/LfMerge.Core.Tests/Lcm/DataConverters/ConvertLcmToMongoCustomFieldTests.cs +++ b/src/LfMerge.Core.Tests/Lcm/DataConverters/ConvertLcmToMongoCustomFieldTests.cs @@ -24,7 +24,7 @@ public void GetCustomFieldForThisCmObject_ShouldGetSingleLineAll() Assert.That(entry, Is.Not.Null); // Exercise - var customDataDocument = converter.GetCustomFieldsForThisCmObject(entry, "entry", _listConverters, Array.Empty()); + var customDataDocument = converter.GetCustomFieldsForThisCmObject(entry, "entry", _listConverters); // Verify English and french values Assert.That(customDataDocument[0]["customField_entry_Cust_Single_Line_All"].AsBsonDocument.GetElement(0).Value["value"].ToString(), @@ -47,7 +47,7 @@ public void GetCustomFieldForThisCmObject_ShouldGetMultiListRef() // Exercise var senses = entry.SensesOS.ToArray(); - var customDataDocument = converter.GetCustomFieldsForThisCmObject(senses[0], "senses", _listConverters, Array.Empty()); + var customDataDocument = converter.GetCustomFieldsForThisCmObject(senses[0], "senses", _listConverters); // Verify. (Note, in the fwdata file, the custom item labels are in reverse order) Assert.That(customDataDocument[0].AsBsonDocument["customField_senses_Cust_Multi_ListRef"]["values"][1].ToString(), @@ -69,7 +69,7 @@ public void GetCustomFieldsForThisCmObject_ShouldGetCustomFieldSettings() new TestLogger(TestContext.CurrentContext.Test.Name)); // Exercise - var customDataDocument = converter.GetCustomFieldsForThisCmObject(entry, "entry", _listConverters, Array.Empty()); + var customDataDocument = converter.GetCustomFieldsForThisCmObject(entry, "entry", _listConverters); // Verify var expectedCustomFieldNames = new List diff --git a/src/LfMerge.Core.Tests/Lcm/RoundTripBase.cs b/src/LfMerge.Core.Tests/Lcm/RoundTripBase.cs index f9e9051c..3c07062e 100644 --- a/src/LfMerge.Core.Tests/Lcm/RoundTripBase.cs +++ b/src/LfMerge.Core.Tests/Lcm/RoundTripBase.cs @@ -167,7 +167,7 @@ protected BsonDocument GetCustomFieldValues(LcmCache cache, ICmObject obj, strin { // The objectType parameter is used in the names of the custom fields (and nowhere else). var convertCustomField = new ConvertLcmToMongoCustomField(cache, _servLoc, new LfMerge.Core.Logging.NullLogger()); - return convertCustomField.GetCustomFieldsForThisCmObject(obj, objectType, _listConverters, Array.Empty()); + return convertCustomField.GetCustomFieldsForThisCmObject(obj, objectType, _listConverters); } protected IDictionary GetFieldValuesByName(LcmCache cache, ICmObject obj) diff --git a/src/LfMerge.Core/DataConverters/ConvertLcmToMongoCustomField.cs b/src/LfMerge.Core/DataConverters/ConvertLcmToMongoCustomField.cs index 4e86a312..2fbbe775 100644 --- a/src/LfMerge.Core/DataConverters/ConvertLcmToMongoCustomField.cs +++ b/src/LfMerge.Core/DataConverters/ConvertLcmToMongoCustomField.cs @@ -244,7 +244,7 @@ Dictionary lfCustomFieldTypes /// Dictionary of ConvertLcmToMongoOptionList instances, keyed by list code public BsonDocument GetCustomFieldsForThisCmObject(ICmObject cmObj, string objectType, IDictionary listConverters, - string[] fieldNamesToSkip) + params string[] fieldNamesToSkip) { if (cmObj == null) return null; diff --git a/src/LfMerge.Core/DataConverters/ConvertLcmToMongoLexicon.cs b/src/LfMerge.Core/DataConverters/ConvertLcmToMongoLexicon.cs index d7e0042a..8cf60df5 100644 --- a/src/LfMerge.Core/DataConverters/ConvertLcmToMongoLexicon.cs +++ b/src/LfMerge.Core/DataConverters/ConvertLcmToMongoLexicon.cs @@ -335,8 +335,7 @@ private LfLexEntry LcmLexEntryToLfLexEntry(ILexEntry LcmEntry) lfEntry.Tags = StringArrayFieldFromCustomField(LfTagsListCode, LcmEntry, MagicStrings.LcmCustomFieldNameForLfTags); - var customFieldNamesToSkip = new string[] { MagicStrings.LcmCustomFieldNameForLfTags }; - BsonDocument customFieldsAndGuids = _convertCustomField.GetCustomFieldsForThisCmObject(LcmEntry, "entry", ListConverters, customFieldNamesToSkip); + BsonDocument customFieldsAndGuids = _convertCustomField.GetCustomFieldsForThisCmObject(LcmEntry, "entry", ListConverters, MagicStrings.LcmCustomFieldNameForLfTags); BsonDocument customFieldsBson = customFieldsAndGuids["customFields"].AsBsonDocument; BsonDocument customFieldGuids = customFieldsAndGuids["customFieldGuids"].AsBsonDocument; @@ -524,7 +523,7 @@ private LfSense LcmSenseToLfSense(ILexSense lcmSense) lcmSense.PublishIn; */ - BsonDocument customFieldsAndGuids = _convertCustomField.GetCustomFieldsForThisCmObject(lcmSense, "senses", ListConverters, Array.Empty()); + BsonDocument customFieldsAndGuids = _convertCustomField.GetCustomFieldsForThisCmObject(lcmSense, "senses", ListConverters); BsonDocument customFieldsBson = customFieldsAndGuids["customFields"].AsBsonDocument; BsonDocument customFieldGuids = customFieldsAndGuids["customFieldGuids"].AsBsonDocument; @@ -575,7 +574,7 @@ private LfExample LcmExampleToLfExample(ILexExampleSentence LcmExample) lfExample.TranslationGuid = translation.Guid; } - BsonDocument customFieldsAndGuids = _convertCustomField.GetCustomFieldsForThisCmObject(LcmExample, "examples", ListConverters, Array.Empty()); + BsonDocument customFieldsAndGuids = _convertCustomField.GetCustomFieldsForThisCmObject(LcmExample, "examples", ListConverters); BsonDocument customFieldsBson = customFieldsAndGuids["customFields"].AsBsonDocument; BsonDocument customFieldGuids = customFieldsAndGuids["customFieldGuids"].AsBsonDocument; From 24f19197d8ea17d8a328ad5e98e377b1a39e1299 Mon Sep 17 00:00:00 2001 From: Robin Munn Date: Fri, 19 Nov 2021 15:29:29 +0700 Subject: [PATCH 09/17] Refactor custom field logic into helper function This will allow us to re-use this logic in the code that sets the LF Tags custom field in the LF -> FW direction. --- .../ConvertMongoToLcmCustomField.cs | 34 ++---------------- .../DataConverters/ConvertUtilities.cs | 36 +++++++++++++++++++ 2 files changed, 39 insertions(+), 31 deletions(-) diff --git a/src/LfMerge.Core/DataConverters/ConvertMongoToLcmCustomField.cs b/src/LfMerge.Core/DataConverters/ConvertMongoToLcmCustomField.cs index a8de39d0..41d786eb 100644 --- a/src/LfMerge.Core/DataConverters/ConvertMongoToLcmCustomField.cs +++ b/src/LfMerge.Core/DataConverters/ConvertMongoToLcmCustomField.cs @@ -321,39 +321,11 @@ public bool SetCustomFieldData(int hvo, int flid, BsonValue value, BsonValue gui // String.Join(", ", keysFromLF.AsEnumerable()), // String.Join(", ", fieldObjs.Select(poss => poss.AbbrAndName)) // ); - // Step 2: Remove any objects from the "old" list that weren't in the "new" list - // We have to look them up by HVO because that's the only public API available in LCM - // Following logic inspired by XmlImportData.CopyCustomFieldData in FieldWorks source + + // We have to replace objects by HVO because that's the only public API available in LCM int[] oldHvosArray = data.VecProp(hvo, flid); int[] newHvosArray = fieldObjs.Select(poss => poss.Hvo).ToArray(); - // Shortcut check - if (oldHvosArray.SequenceEqual(newHvosArray)) - { - // Nothing to do, so return now so that we don't cause unnecessary changes and commits in Mercurial - return false; - } - HashSet newHvos = new HashSet(newHvosArray); - HashSet combinedHvos = new HashSet(); - // Loop backwards so deleting items won't mess up indices of subsequent deletions - for (int idx = oldHvosArray.Length - 1; idx >= 0; idx--) - { - int oldHvo = oldHvosArray[idx]; - if (newHvos.Contains(oldHvo)) - combinedHvos.Add(oldHvo); - else - data.Replace(hvo, flid, idx, idx + 1, null, 0); // Important to pass *both* null *and* 0 here to remove items - } - - // Step 3: Add any objects from the "new" list that weren't in the "old" list - foreach (int newHvo in newHvosArray) - { - if (combinedHvos.Contains(newHvo)) - continue; - // This item was added in the new list - data.Replace(hvo, flid, combinedHvos.Count, combinedHvos.Count, new int[] { newHvo }, 1); - combinedHvos.Add(newHvo); - } - return true; + return ConvertUtilities.ReplaceHvosInCustomField(hvo, flid, data, oldHvosArray, newHvosArray); } case CellarPropertyType.String: diff --git a/src/LfMerge.Core/DataConverters/ConvertUtilities.cs b/src/LfMerge.Core/DataConverters/ConvertUtilities.cs index 35c367ae..c911e53a 100644 --- a/src/LfMerge.Core/DataConverters/ConvertUtilities.cs +++ b/src/LfMerge.Core/DataConverters/ConvertUtilities.cs @@ -6,6 +6,7 @@ using LfMerge.Core.LanguageForge.Model; using MongoDB.Bson; using SIL.LCModel; +using SIL.LCModel.Application; using SIL.LCModel.Core.KernelInterfaces; using SIL.LCModel.Core.WritingSystems; @@ -39,6 +40,41 @@ public static LfParagraph LcmParaToLfPara(IStTxtPara lcmPara, ILgWritingSystemFa return lfPara; } + public static bool ReplaceHvosInCustomField(int hvo, int flid, ISilDataAccessManaged data, int[] oldArray, int[] newArray) + { + // Shortcut check + if (oldArray.SequenceEqual(newArray)) + { + // Nothing to do, so return now so that we don't cause unnecessary changes and commits in Mercurial + return false; + } + // HashSets for O(1) lookup. Might be overkill, but better safe than sorry + var newHvos = new HashSet(newArray); + var combinedHvos = new HashSet(); + + // Step 1: Remove any objects from the "old" list that weren't in the "new" list + // Loop backwards so deleting items won't mess up indices of subsequent deletions + for (int idx = oldArray.Length - 1; idx >= 0; idx--) + { + int oldHvo = oldArray[idx]; + if (newHvos.Contains(oldHvo)) + combinedHvos.Add(oldHvo); + else + data.Replace(hvo, flid, idx, idx + 1, null, 0); // Important to pass *both* null *and* 0 here to remove items + } + + // Step 2: Add any objects from the "new" list that weren't in the "old" list + foreach (int newHvo in newArray) + { + if (combinedHvos.Contains(newHvo)) + continue; + // This item was added in the new list + data.Replace(hvo, flid, combinedHvos.Count, combinedHvos.Count, new int[] { newHvo }, 1); + combinedHvos.Add(newHvo); + } + return true; + } + /// /// Return a name suitable for logging from an entry /// From 3a918ec49c912f45d8ebf8aec0be1acdef70a7e7 Mon Sep 17 00:00:00 2001 From: Robin Munn Date: Fri, 19 Nov 2021 15:31:00 +0700 Subject: [PATCH 10/17] Convert LF Tags custom field in LF -> FW direction --- .../ConvertMongoToLcmCustomField.cs | 5 +++-- .../DataConverters/ConvertMongoToLcmLexicon.cs | 18 ++++++++++++++++-- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/src/LfMerge.Core/DataConverters/ConvertMongoToLcmCustomField.cs b/src/LfMerge.Core/DataConverters/ConvertMongoToLcmCustomField.cs index 41d786eb..9e25d499 100644 --- a/src/LfMerge.Core/DataConverters/ConvertMongoToLcmCustomField.cs +++ b/src/LfMerge.Core/DataConverters/ConvertMongoToLcmCustomField.cs @@ -356,13 +356,14 @@ public bool SetCustomFieldData(int hvo, int flid, BsonValue value, BsonValue gui } } - public void SetCustomFieldsForThisCmObject(ICmObject cmObj, string objectType, BsonDocument customFieldValues, BsonDocument customFieldGuids) + public void SetCustomFieldsForThisCmObject(ICmObject cmObj, string objectType, BsonDocument customFieldValues, BsonDocument customFieldGuids, params int[] customFieldIdsToSkip) { if (customFieldValues == null) return; IEnumerable customFieldIds = lcmMetaData.GetFields(cmObj.ClassID, false, (int)CellarPropertyTypeFilter.All) - .Where(flid => cache.GetIsCustomField(flid)); + .Where(flid => cache.GetIsCustomField(flid)) + .Where(flid => !customFieldIdsToSkip.Contains(flid)); var remainingFieldNames = new HashSet(customFieldValues.Select(elem => elem.Name)); foreach (int flid in customFieldIds) diff --git a/src/LfMerge.Core/DataConverters/ConvertMongoToLcmLexicon.cs b/src/LfMerge.Core/DataConverters/ConvertMongoToLcmLexicon.cs index fc8df1f0..5b499cb8 100644 --- a/src/LfMerge.Core/DataConverters/ConvertMongoToLcmLexicon.cs +++ b/src/LfMerge.Core/DataConverters/ConvertMongoToLcmLexicon.cs @@ -12,6 +12,7 @@ using LfMerge.Core.Reporting; using LfMerge.Core.Settings; using SIL.LCModel; +using SIL.LCModel.Application; using SIL.LCModel.Core.KernelInterfaces; using SIL.LCModel.Core.WritingSystems; using SIL.LCModel.DomainServices; @@ -38,6 +39,7 @@ public class ConvertMongoToLcmLexicon private int _wsEn; private ConvertMongoToLcmCustomField _convertCustomField; + private int _lfTagsFieldId; // Shorter names to use in this class since MagicStrings.LfOptionListCodeForGrammaticalInfo // (etc.) are real mouthfuls @@ -87,7 +89,7 @@ public ConvertMongoToLcmLexicon(LfMergeSettings settings, ILfProject lfproject, ListConverters[AnthroCodeListCode] = PrepareOptionListConverter(AnthroCodeListCode); ListConverters[StatusListCode] = PrepareOptionListConverter(StatusListCode); ListConverters[LfTagsListCode] = PrepareOptionListConverterFromCanonicalSource(LfTagsListCode); - int lfTagsFieldId = EnsureCustomFieldExists(new System.Guid(MagicStrings.LcmOptionListGuidForLfTags), MagicStrings.LcmCustomFieldNameForLfTags); + _lfTagsFieldId = EnsureCustomFieldExists(new System.Guid(MagicStrings.LcmOptionListGuidForLfTags), MagicStrings.LcmCustomFieldNameForLfTags); // Once we allow LanguageForge to create optionlist items with "canonical" values (parts of speech, semantic domains, etc.), replace the code block // above with this one (that provides TWO parameters to PrepareOptionListConverter) @@ -610,8 +612,10 @@ private void LfLexEntryToLcmLexEntry(LfLexEntry lfEntry) // ListConverters[AnthroCodeListCode].UpdatePossibilitiesFromStringArray(LcmSense.AnthroCodesRC, // lfSense.AnthropologyCategories); + SetLcmCustomFieldFromLfStringArrayField(LcmEntry, _lfTagsFieldId, lfEntry.Tags); + _convertCustomField.SetCustomFieldsForThisCmObject(LcmEntry, "entry", lfEntry.CustomFields, - lfEntry.CustomFieldGuids); + lfEntry.CustomFieldGuids, _lfTagsFieldId); // If we got this far, we either created or modified this entry if (createdEntry) @@ -823,6 +827,16 @@ Action convertAction } } + private void SetLcmCustomFieldFromLfStringArrayField(ILexEntry lcmEntry, int flid, LfStringArrayField keys) + { + if (keys == null || keys.Values == null) return; + ISilDataAccessManaged data = (ISilDataAccessManaged)Cache.DomainDataByFlid; + int[] oldHvos = data.VecProp(lcmEntry.Hvo, flid); + var possibilities = ListConverters[LfTagsListCode].FromStringArrayField(keys); + int[] newHvos = possibilities.Select(poss => poss.Hvo).ToArray(); + ConvertUtilities.ReplaceHvosInCustomField(lcmEntry.Hvo, flid, data, oldHvos, newHvos); + } + private Guid GuidFromLiftId(string liftId) { Guid result; From 0c5b5409b9d0aa0d19423b4fb31f34587e7b6e98 Mon Sep 17 00:00:00 2001 From: Robin Munn Date: Mon, 22 Nov 2021 11:57:16 +0700 Subject: [PATCH 11/17] Ignore extra fields in project config --- src/LfMerge.Core/LanguageForge/Config/LfProjectConfig.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/LfMerge.Core/LanguageForge/Config/LfProjectConfig.cs b/src/LfMerge.Core/LanguageForge/Config/LfProjectConfig.cs index 1b203147..f45575d0 100644 --- a/src/LfMerge.Core/LanguageForge/Config/LfProjectConfig.cs +++ b/src/LfMerge.Core/LanguageForge/Config/LfProjectConfig.cs @@ -2,6 +2,7 @@ // This software is licensed under the MIT license (http://opensource.org/licenses/MIT) using MongoDB.Bson; +using MongoDB.Bson.Serialization.Attributes; namespace LfMerge.Core.LanguageForge.Config { @@ -15,6 +16,8 @@ public class LfProjectConfig : ILfProjectConfig public LfConfigFieldList Entry { get; set; } public BsonDocument RoleViews { get; set; } public BsonDocument UserViews { get; set; } + [BsonExtraElements] + public BsonDocument OtherConfig { get; set; } } } From 41c07c4478e383227c52f5e2f7b51522b82819d0 Mon Sep 17 00:00:00 2001 From: Robin Munn Date: Mon, 22 Nov 2021 12:35:19 +0700 Subject: [PATCH 12/17] Really ignore extra fields --- src/LfMerge.Core/MongoConnector/MongoRegistrar.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/LfMerge.Core/MongoConnector/MongoRegistrar.cs b/src/LfMerge.Core/MongoConnector/MongoRegistrar.cs index dab7b7ee..19e59012 100644 --- a/src/LfMerge.Core/MongoConnector/MongoRegistrar.cs +++ b/src/LfMerge.Core/MongoConnector/MongoRegistrar.cs @@ -34,7 +34,7 @@ public void RegisterClassIgnoreExtraFields(Type type) { BsonClassMap cm = new BsonClassMap(type); cm.AutoMap(); - //cm.SetIgnoreExtraElements(true); // Let's see if this is the default + cm.SetIgnoreExtraElements(true); // Let's see if this is the default BsonClassMap.RegisterClassMap(cm); //BsonSerializer.RegisterDiscriminatorConvention(type, new ScalarDiscriminatorConvention("type")); } From 907a2364dff2f532dead4e554caaee7687d62e0a Mon Sep 17 00:00:00 2001 From: Robin Munn Date: Mon, 22 Nov 2021 13:07:12 +0700 Subject: [PATCH 13/17] Try logging some extra stuff --- src/LfMerge.Core/MongoConnector/MongoRegistrar.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/LfMerge.Core/MongoConnector/MongoRegistrar.cs b/src/LfMerge.Core/MongoConnector/MongoRegistrar.cs index 19e59012..e404acf1 100644 --- a/src/LfMerge.Core/MongoConnector/MongoRegistrar.cs +++ b/src/LfMerge.Core/MongoConnector/MongoRegistrar.cs @@ -35,6 +35,7 @@ public void RegisterClassIgnoreExtraFields(Type type) BsonClassMap cm = new BsonClassMap(type); cm.AutoMap(); cm.SetIgnoreExtraElements(true); // Let's see if this is the default + MainClass.Logger.Error("Registed class mappings ignoring extra elements"); BsonClassMap.RegisterClassMap(cm); //BsonSerializer.RegisterDiscriminatorConvention(type, new ScalarDiscriminatorConvention("type")); } From 12096c55ffd870af8b9f92cb40b7aedbab432051 Mon Sep 17 00:00:00 2001 From: Robin Munn Date: Mon, 22 Nov 2021 13:44:19 +0700 Subject: [PATCH 14/17] Try again --- .../LanguageForge/Config/MongoRegistrarForLfConfig.cs | 1 + src/LfMerge/Program.cs | 2 ++ 2 files changed, 3 insertions(+) diff --git a/src/LfMerge.Core/LanguageForge/Config/MongoRegistrarForLfConfig.cs b/src/LfMerge.Core/LanguageForge/Config/MongoRegistrarForLfConfig.cs index b6708cb8..67e5227f 100644 --- a/src/LfMerge.Core/LanguageForge/Config/MongoRegistrarForLfConfig.cs +++ b/src/LfMerge.Core/LanguageForge/Config/MongoRegistrarForLfConfig.cs @@ -16,6 +16,7 @@ public override void RegisterClassMappings() { RegisterClassMapsForDerivedClassesOf(typeof(LfConfigFieldBase)); RegisterClassIgnoreExtraFields(typeof(LfProjectConfig)); + MainClass.Logger.Error("Registered class mappings for LfProjectConfig"); RegisterClassIgnoreExtraFields(typeof(MongoProjectRecord)); } } diff --git a/src/LfMerge/Program.cs b/src/LfMerge/Program.cs index 24109a59..e4f453bf 100644 --- a/src/LfMerge/Program.cs +++ b/src/LfMerge/Program.cs @@ -27,6 +27,7 @@ public static int Main(string[] args) var options = Options.ParseCommandLineArgs(args); if (options == null) return (int)ErrorCode.InvalidOptions; + MainClass.Logger.Error("Starting LfMerge"); // initialize the SLDR Sldr.Initialize(); @@ -59,6 +60,7 @@ public static int Main(string[] args) return (int)ErrorCode.GeneralError; MongoConnection.Initialize(); + MainClass.Logger.Error("Registered class mappings ignoring extra elements"); differentModelVersion = RunAction(options.ProjectCode, options.CurrentAction); } From d70794c0e891671f0bbbb787e95ed236ca9d176a Mon Sep 17 00:00:00 2001 From: Robin Munn Date: Mon, 22 Nov 2021 15:19:15 +0700 Subject: [PATCH 15/17] Fix incorrect XML in canonical tags file Missed a closing tag. --- data/canonical-lf-tags.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/canonical-lf-tags.xml b/data/canonical-lf-tags.xml index 63bfed79..b52d9cdd 100644 --- a/data/canonical-lf-tags.xml +++ b/data/canonical-lf-tags.xml @@ -234,4 +234,4 @@ - + From b85f076c4b131b17c1338ee6d89adcdd454c16a4 Mon Sep 17 00:00:00 2001 From: Robin Munn Date: Mon, 22 Nov 2021 16:11:33 +0700 Subject: [PATCH 16/17] Need active unit of work for LF Tags custom field Can't create a custom field outside a Unit Of Work in FW. --- .../DataConverters/ConvertMongoToLcmLexicon.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/LfMerge.Core/DataConverters/ConvertMongoToLcmLexicon.cs b/src/LfMerge.Core/DataConverters/ConvertMongoToLcmLexicon.cs index 5b499cb8..81e94a4e 100644 --- a/src/LfMerge.Core/DataConverters/ConvertMongoToLcmLexicon.cs +++ b/src/LfMerge.Core/DataConverters/ConvertMongoToLcmLexicon.cs @@ -88,8 +88,7 @@ public ConvertMongoToLcmLexicon(LfMergeSettings settings, ILfProject lfproject, ListConverters[SenseTypeListCode] = PrepareOptionListConverter(SenseTypeListCode); ListConverters[AnthroCodeListCode] = PrepareOptionListConverter(AnthroCodeListCode); ListConverters[StatusListCode] = PrepareOptionListConverter(StatusListCode); - ListConverters[LfTagsListCode] = PrepareOptionListConverterFromCanonicalSource(LfTagsListCode); - _lfTagsFieldId = EnsureCustomFieldExists(new System.Guid(MagicStrings.LcmOptionListGuidForLfTags), MagicStrings.LcmCustomFieldNameForLfTags); + _lfTagsFieldId = 0; // Once we allow LanguageForge to create optionlist items with "canonical" values (parts of speech, semantic domains, etc.), replace the code block // above with this one (that provides TWO parameters to PrepareOptionListConverter) @@ -181,6 +180,10 @@ public void RunConversion() IEnumerable lexicon = GetLexicon(LfProject); UndoableUnitOfWorkHelper.DoUsingNewOrCurrentUOW("undo", "redo", Cache.ActionHandlerAccessor, () => { + // Can't run this in the constructor, as we need a unit of work for EnsureCustomFieldExists + ListConverters[LfTagsListCode] = PrepareOptionListConverterFromCanonicalSource(LfTagsListCode); + _lfTagsFieldId = EnsureCustomFieldExists(new System.Guid(MagicStrings.LcmOptionListGuidForLfTags), MagicStrings.LcmCustomFieldNameForLfTags); + #if false // Once we allow LanguageForge to create optionlist items with "canonical" values (parts of speech, semantic domains, etc.), uncomment this block foreach (ConvertMongoToLcmOptionList converter in ListConverters.Values) { From 765bae68c962533b9cfcebf556c71c98d3f5e0dc Mon Sep 17 00:00:00 2001 From: Robin Munn Date: Mon, 22 Nov 2021 16:26:46 +0700 Subject: [PATCH 17/17] Temp debugging message --- .../DataConverters/CanonicalSources/CanonicalItem.cs | 5 +++++ .../DataConverters/ConvertMongoToLcmOptionList.cs | 1 + 2 files changed, 6 insertions(+) diff --git a/src/LfMerge.Core/DataConverters/CanonicalSources/CanonicalItem.cs b/src/LfMerge.Core/DataConverters/CanonicalSources/CanonicalItem.cs index 4a2e7b74..9b495a2a 100644 --- a/src/LfMerge.Core/DataConverters/CanonicalSources/CanonicalItem.cs +++ b/src/LfMerge.Core/DataConverters/CanonicalSources/CanonicalItem.cs @@ -51,6 +51,11 @@ public CanonicalItem() ExtraData = new Dictionary(); } + public override string ToString() + { + return $"{Key} ({GuidStr})"; + } + /// /// Given an XmlReader positioned on this node's XML representation, populate its names, abbrevs, etc. from the XML. /// After running PopulateFromXml, the reader should be positioned just past this node's closing element. diff --git a/src/LfMerge.Core/DataConverters/ConvertMongoToLcmOptionList.cs b/src/LfMerge.Core/DataConverters/ConvertMongoToLcmOptionList.cs index 3153128f..a2a7ce0c 100644 --- a/src/LfMerge.Core/DataConverters/ConvertMongoToLcmOptionList.cs +++ b/src/LfMerge.Core/DataConverters/ConvertMongoToLcmOptionList.cs @@ -79,6 +79,7 @@ public ICmPossibility CreateFromCanonicalItem(CanonicalItem item) // and populate the parent if the parent didn't exist already). FromStringKey(item.Parent.Key); } + MainClass.Logger.Error($"Creating from canonical item with ws {_wsForKeys} from item {item.ToString()}"); ICmPossibility poss = _parentList.FindOrCreatePossibility(item.ORCDelimitedKey, _wsForKeys); item.PopulatePossibility(poss); PossibilitiesByKey[item.Key] = poss;