From 96c8b25ec1676df2903313ef46f7ce7a532bf863 Mon Sep 17 00:00:00 2001 From: John Maxwell Date: Fri, 26 Sep 2025 09:27:00 -0700 Subject: [PATCH 01/18] Fix LT-10590: Import IsTranslated --- Src/LexText/Interlinear/BIRDInterlinearImporter.cs | 3 +++ .../ITextDllTests/BIRDFormatImportTests.cs | 12 ++++++++++++ 2 files changed, 15 insertions(+) diff --git a/Src/LexText/Interlinear/BIRDInterlinearImporter.cs b/Src/LexText/Interlinear/BIRDInterlinearImporter.cs index 7bef0eba84..0f12ba5095 100644 --- a/Src/LexText/Interlinear/BIRDInterlinearImporter.cs +++ b/Src/LexText/Interlinear/BIRDInterlinearImporter.cs @@ -1156,6 +1156,9 @@ private static void SetTextMetaAndMergeMedia(LcmCache cache, Interlineartext int case "comment": newText.Description.set_String(GetWsEngine(wsFactory, item.lang).Handle, item.Value); break; + case "is-translated": + newText.IsTranslated = (item.Value.ToLower() == "true"); + break; } } } diff --git a/Src/LexText/Interlinear/ITextDllTests/BIRDFormatImportTests.cs b/Src/LexText/Interlinear/ITextDllTests/BIRDFormatImportTests.cs index dc0e205ba1..a509afc2b1 100644 --- a/Src/LexText/Interlinear/ITextDllTests/BIRDFormatImportTests.cs +++ b/Src/LexText/Interlinear/ITextDllTests/BIRDFormatImportTests.cs @@ -761,10 +761,15 @@ public void OneOfEachElementTypeTest() { string title = "atrocious"; string abbr = "atroc"; + string source = "source"; + string description = "description"; //an interliner text example xml string string xml = "" + "" + title + "" + "" + abbr + "" + + "" + source + "" + + "" + description + "" + + "true" + "" + "1 Musical" + "origem: mary poppins" + @@ -785,6 +790,13 @@ public void OneOfEachElementTypeTest() Assert.True(imported.Name.get_String(Cache.WritingSystemFactory.get_Engine("en").Handle).Text.Equals(title)); //The title abbreviation imported Assert.True(imported.Abbreviation.get_String(Cache.WritingSystemFactory.get_Engine("en").Handle).Text.Equals(abbr)); + //The source imported + Assert.True(imported.Source.get_String(Cache.WritingSystemFactory.get_Engine("en").Handle).Text.Equals(source)); + //The description imported + Assert.True(imported.Description.get_String(Cache.WritingSystemFactory.get_Engine("en").Handle).Text.Equals(description)); + //The isTranslated imported + Assert.True(imported.IsTranslated); + //The DateCreated imported } } } From b9a295372533b18a9d97504d59977895f8bdc460 Mon Sep 17 00:00:00 2001 From: John Maxwell Date: Mon, 29 Sep 2025 20:31:28 -0700 Subject: [PATCH 02/18] Export IsTranslated; Import and Export DateCreated, DateModified --- .../Interlinear/BIRDInterlinearImporter.cs | 6 ++++++ .../ITextDllTests/BIRDFormatImportTests.cs | 10 +++++++++- .../ITextDllTests/InterlinearExporterTests.cs | 18 ++++++++++++++++++ Src/LexText/Interlinear/InterlinearExporter.cs | 17 +++++++++++++++++ 4 files changed, 50 insertions(+), 1 deletion(-) diff --git a/Src/LexText/Interlinear/BIRDInterlinearImporter.cs b/Src/LexText/Interlinear/BIRDInterlinearImporter.cs index 0f12ba5095..e808596c8b 100644 --- a/Src/LexText/Interlinear/BIRDInterlinearImporter.cs +++ b/Src/LexText/Interlinear/BIRDInterlinearImporter.cs @@ -1159,6 +1159,12 @@ private static void SetTextMetaAndMergeMedia(LcmCache cache, Interlineartext int case "is-translated": newText.IsTranslated = (item.Value.ToLower() == "true"); break; + case "date-created": + newText.DateCreated = DateTime.Parse(item.Value, null, System.Globalization.DateTimeStyles.AssumeUniversal); + break; + case "date-modified": + newText.DateModified = DateTime.Parse(item.Value, null, System.Globalization.DateTimeStyles.AssumeUniversal); + break; } } } diff --git a/Src/LexText/Interlinear/ITextDllTests/BIRDFormatImportTests.cs b/Src/LexText/Interlinear/ITextDllTests/BIRDFormatImportTests.cs index a509afc2b1..7c174ac759 100644 --- a/Src/LexText/Interlinear/ITextDllTests/BIRDFormatImportTests.cs +++ b/Src/LexText/Interlinear/ITextDllTests/BIRDFormatImportTests.cs @@ -763,6 +763,8 @@ public void OneOfEachElementTypeTest() string abbr = "atroc"; string source = "source"; string description = "description"; + string dateCreated = "2006-08-23 19:31:09.500"; + string dateModified = "2006-09-14 13:46:01.247"; //an interliner text example xml string string xml = "" + "" + title + "" + @@ -770,6 +772,8 @@ public void OneOfEachElementTypeTest() "" + source + "" + "" + description + "" + "true" + + "" + dateCreated + "" + + "" + dateModified + "" + "" + "1 Musical" + "origem: mary poppins" + @@ -796,7 +800,11 @@ public void OneOfEachElementTypeTest() Assert.True(imported.Description.get_String(Cache.WritingSystemFactory.get_Engine("en").Handle).Text.Equals(description)); //The isTranslated imported Assert.True(imported.IsTranslated); - //The DateCreated imported + //The Dates imported + string importedDateCreated = imported.DateCreated.ToLCMTimeFormatWithMillisString(); + Assert.True(importedDateCreated.Equals(dateCreated)); + string importedDateModified = imported.DateModified.ToLCMTimeFormatWithMillisString(); + Assert.True(importedDateModified.Equals(dateModified)); } } } diff --git a/Src/LexText/Interlinear/ITextDllTests/InterlinearExporterTests.cs b/Src/LexText/Interlinear/ITextDllTests/InterlinearExporterTests.cs index d3fb24c376..29f7bb54c0 100644 --- a/Src/LexText/Interlinear/ITextDllTests/InterlinearExporterTests.cs +++ b/Src/LexText/Interlinear/ITextDllTests/InterlinearExporterTests.cs @@ -1111,6 +1111,24 @@ public void ValidateMultipleComments() AssertThatXmlIn.Dom(exportedDoc).HasSpecifiedNumberOfMatchesForXpath("//interlinear-text/item[@type=\"comment\"]", 2); } + [Test] + public void ValidateIsTranslated() + { + m_text1.IsTranslated = true; + XmlDocument exportedDoc = ExportToXml(); + AssertThatXmlIn.Dom(exportedDoc).HasSpecifiedNumberOfMatchesForXpath("//interlinear-text/item[@type=\"is-translated\"]", 1); + } + + [Test] + public void ValidateDates() + { + m_text1.DateCreated = DateTime.Now; + m_text1.DateModified = DateTime.Now; + XmlDocument exportedDoc = ExportToXml(); + AssertThatXmlIn.Dom(exportedDoc).HasSpecifiedNumberOfMatchesForXpath("//interlinear-text/item[@type=\"date-created\"]", 1); + AssertThatXmlIn.Dom(exportedDoc).HasSpecifiedNumberOfMatchesForXpath("//interlinear-text/item[@type=\"date-modified\"]", 1); + } + /// /// Create two paragraphs with two identical sentences. The first paragraph has real analyses, the second has only guesses. /// Validate that the guids for each paragraph, and each phrase and word annotation are unique. diff --git a/Src/LexText/Interlinear/InterlinearExporter.cs b/Src/LexText/Interlinear/InterlinearExporter.cs index e0524ff75c..aa5470df72 100644 --- a/Src/LexText/Interlinear/InterlinearExporter.cs +++ b/Src/LexText/Interlinear/InterlinearExporter.cs @@ -13,6 +13,8 @@ using SIL.LCModel; using SIL.LCModel.DomainServices; using SIL.LCModel.Infrastructure; +using SIL.LCModel.Core.Text; +using SIL.LCModel.Utils; namespace SIL.FieldWorks.IText { @@ -40,6 +42,9 @@ public class InterlinearExporter : CollectorEnv List pendingSources = new List(); private List pendingAbbreviations = new List(); List pendingComments = new List(); + bool pendingIsTranslated = false; + DateTime pendingDateCreated = DateTime.MinValue; + DateTime pendingDateModified = DateTime.MinValue; int m_flidStTextTitle; int m_flidStTextSource; InterlinVc m_vc = null; @@ -592,6 +597,15 @@ public override void AddObjVecItems(int tag, IVwViewConstructor vc, int frag) var hystericalRaisens = desc; WritePendingItem("comment", ref hystericalRaisens); } + if (pendingIsTranslated) + { + var hystericalRaisens = TsStringUtils.MakeString("true", m_cache.WritingSystemFactory.UserWs); + WritePendingItem("is-translated", ref hystericalRaisens); + } + ITsString dateCreated = TsStringUtils.MakeString(pendingDateCreated.ToLCMTimeFormatWithMillisString(), m_cache.WritingSystemFactory.UserWs); + WritePendingItem("date-created", ref dateCreated); + ITsString dateModified = TsStringUtils.MakeString(pendingDateModified.ToLCMTimeFormatWithMillisString(), m_cache.WritingSystemFactory.UserWs); + WritePendingItem("date-modified", ref dateModified); m_writer.WriteStartElement("paragraphs"); break; case InterlinVc.kfragParaSegment: @@ -785,6 +799,9 @@ private void SetTextTitleAndMetadata(IStText txt) { pendingComments.Add(text.Description.get_String(writingSystemId)); } + pendingIsTranslated = text.IsTranslated; + pendingDateCreated = text.DateCreated; + pendingDateModified = text.DateModified; } else if (TextSource.IsScriptureText(txt)) { From d539e969ab8b252cb6f43fecb74c9ca1bc6d9ce9 Mon Sep 17 00:00:00 2001 From: John Maxwell Date: Tue, 30 Sep 2025 08:14:44 -0700 Subject: [PATCH 03/18] Rename is-translated to text-is-translation --- Src/LexText/Interlinear/BIRDInterlinearImporter.cs | 2 +- Src/LexText/Interlinear/ITextDllTests/BIRDFormatImportTests.cs | 2 +- .../Interlinear/ITextDllTests/InterlinearExporterTests.cs | 2 +- Src/LexText/Interlinear/InterlinearExporter.cs | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Src/LexText/Interlinear/BIRDInterlinearImporter.cs b/Src/LexText/Interlinear/BIRDInterlinearImporter.cs index e808596c8b..adcdcb204e 100644 --- a/Src/LexText/Interlinear/BIRDInterlinearImporter.cs +++ b/Src/LexText/Interlinear/BIRDInterlinearImporter.cs @@ -1156,7 +1156,7 @@ private static void SetTextMetaAndMergeMedia(LcmCache cache, Interlineartext int case "comment": newText.Description.set_String(GetWsEngine(wsFactory, item.lang).Handle, item.Value); break; - case "is-translated": + case "text-is-translation": newText.IsTranslated = (item.Value.ToLower() == "true"); break; case "date-created": diff --git a/Src/LexText/Interlinear/ITextDllTests/BIRDFormatImportTests.cs b/Src/LexText/Interlinear/ITextDllTests/BIRDFormatImportTests.cs index 7c174ac759..e51d397e21 100644 --- a/Src/LexText/Interlinear/ITextDllTests/BIRDFormatImportTests.cs +++ b/Src/LexText/Interlinear/ITextDllTests/BIRDFormatImportTests.cs @@ -771,7 +771,7 @@ public void OneOfEachElementTypeTest() "" + abbr + "" + "" + source + "" + "" + description + "" + - "true" + + "true" + "" + dateCreated + "" + "" + dateModified + "" + "" + diff --git a/Src/LexText/Interlinear/ITextDllTests/InterlinearExporterTests.cs b/Src/LexText/Interlinear/ITextDllTests/InterlinearExporterTests.cs index 29f7bb54c0..32d8496f62 100644 --- a/Src/LexText/Interlinear/ITextDllTests/InterlinearExporterTests.cs +++ b/Src/LexText/Interlinear/ITextDllTests/InterlinearExporterTests.cs @@ -1116,7 +1116,7 @@ public void ValidateIsTranslated() { m_text1.IsTranslated = true; XmlDocument exportedDoc = ExportToXml(); - AssertThatXmlIn.Dom(exportedDoc).HasSpecifiedNumberOfMatchesForXpath("//interlinear-text/item[@type=\"is-translated\"]", 1); + AssertThatXmlIn.Dom(exportedDoc).HasSpecifiedNumberOfMatchesForXpath("//interlinear-text/item[@type=\"text-is-translation\"]", 1); } [Test] diff --git a/Src/LexText/Interlinear/InterlinearExporter.cs b/Src/LexText/Interlinear/InterlinearExporter.cs index aa5470df72..b68c63436c 100644 --- a/Src/LexText/Interlinear/InterlinearExporter.cs +++ b/Src/LexText/Interlinear/InterlinearExporter.cs @@ -600,7 +600,7 @@ public override void AddObjVecItems(int tag, IVwViewConstructor vc, int frag) if (pendingIsTranslated) { var hystericalRaisens = TsStringUtils.MakeString("true", m_cache.WritingSystemFactory.UserWs); - WritePendingItem("is-translated", ref hystericalRaisens); + WritePendingItem("text-is-translation", ref hystericalRaisens); } ITsString dateCreated = TsStringUtils.MakeString(pendingDateCreated.ToLCMTimeFormatWithMillisString(), m_cache.WritingSystemFactory.UserWs); WritePendingItem("date-created", ref dateCreated); From 7e7a6b023bcbf3cf100a2e049aab7ad599628ede Mon Sep 17 00:00:00 2001 From: John Maxwell Date: Tue, 30 Sep 2025 09:01:03 -0700 Subject: [PATCH 04/18] Make date-created and date-modified optional for tests --- .../ITextDllTests/InterlinearExporterTests.cs | 8 ++++++++ Src/LexText/Interlinear/InterlinearExporter.cs | 14 ++++++++++---- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/Src/LexText/Interlinear/ITextDllTests/InterlinearExporterTests.cs b/Src/LexText/Interlinear/ITextDllTests/InterlinearExporterTests.cs index 32d8496f62..e903cfa27d 100644 --- a/Src/LexText/Interlinear/ITextDllTests/InterlinearExporterTests.cs +++ b/Src/LexText/Interlinear/ITextDllTests/InterlinearExporterTests.cs @@ -545,6 +545,8 @@ public void ExportVariantTypeInformation_LT9374_xml2OO_multipleWss() freeVarType.ReverseAbbr.SetAnalysisDefaultWritingSystem("fr. var."); pa.SetVariantOf(0, 1, leGo, freeVarType); pa.ReparseParagraph(); + m_text1.DateCreated = DateTime.MinValue; + m_text1.DateModified = DateTime.MinValue; exportedDoc = ExportToXml(); //validate export xml against schema @@ -762,6 +764,8 @@ public void ExportIrrInflVariantTypeInformation_LT7581_glsAppend_xml2OO_multiple IStTxtPara para1 = m_text1.ContentsOA.ParagraphsOS[1] as IStTxtPara; ParagraphAnnotator pa = new ParagraphAnnotator(para1); pa.ReparseParagraph(); + m_text1.DateCreated = DateTime.MinValue; + m_text1.DateModified = DateTime.MinValue; var exportedDoc = ExportToXml(); string formLexEntry = "go"; @@ -827,6 +831,8 @@ public void ExportIrrInflVariantTypeInformation_LT7581_glsAppend_varianttypes_xm IStTxtPara para1 = m_text1.ContentsOA.ParagraphsOS[1] as IStTxtPara; ParagraphAnnotator pa = new ParagraphAnnotator(para1); pa.ReparseParagraph(); + m_text1.DateCreated = DateTime.MinValue; + m_text1.DateModified = DateTime.MinValue; var exportedDoc = ExportToXml(); string formLexEntry = "go"; @@ -888,6 +894,8 @@ public void ExportIrrInflVariantTypeInformation_LT7581_glsAppend_varianttypes_xm IStTxtPara para1 = m_text1.ContentsOA.ParagraphsOS[1] as IStTxtPara; ParagraphAnnotator pa = new ParagraphAnnotator(para1); pa.ReparseParagraph(); + m_text1.DateCreated = DateTime.MinValue; + m_text1.DateModified = DateTime.MinValue; var exportedDoc = ExportToXml(); string formLexEntry = "go"; diff --git a/Src/LexText/Interlinear/InterlinearExporter.cs b/Src/LexText/Interlinear/InterlinearExporter.cs index b68c63436c..e7dbc76dd6 100644 --- a/Src/LexText/Interlinear/InterlinearExporter.cs +++ b/Src/LexText/Interlinear/InterlinearExporter.cs @@ -602,10 +602,16 @@ public override void AddObjVecItems(int tag, IVwViewConstructor vc, int frag) var hystericalRaisens = TsStringUtils.MakeString("true", m_cache.WritingSystemFactory.UserWs); WritePendingItem("text-is-translation", ref hystericalRaisens); } - ITsString dateCreated = TsStringUtils.MakeString(pendingDateCreated.ToLCMTimeFormatWithMillisString(), m_cache.WritingSystemFactory.UserWs); - WritePendingItem("date-created", ref dateCreated); - ITsString dateModified = TsStringUtils.MakeString(pendingDateModified.ToLCMTimeFormatWithMillisString(), m_cache.WritingSystemFactory.UserWs); - WritePendingItem("date-modified", ref dateModified); + if (pendingDateCreated != DateTime.MinValue) + { + ITsString dateCreated = TsStringUtils.MakeString(pendingDateCreated.ToLCMTimeFormatWithMillisString(), m_cache.WritingSystemFactory.UserWs); + WritePendingItem("date-created", ref dateCreated); + } + if (pendingDateModified != DateTime.MinValue) + { + ITsString dateModified = TsStringUtils.MakeString(pendingDateModified.ToLCMTimeFormatWithMillisString(), m_cache.WritingSystemFactory.UserWs); + WritePendingItem("date-modified", ref dateModified); + } m_writer.WriteStartElement("paragraphs"); break; case InterlinVc.kfragParaSegment: From 648ca90f4e10bfaffe36604529940b6eef384858 Mon Sep 17 00:00:00 2001 From: John Maxwell Date: Wed, 1 Oct 2025 14:25:30 -0700 Subject: [PATCH 05/18] Add links and records to xsd; Add InterlinearRecords --- .../Interlinear/FlexInterlinear.xsd | 28 +++- .../FlexInterlinModel/FlexInterlinear.cs | 152 ++++++++++++++++++ Src/LexText/Interlinear/ITextDll.csproj | 1 + Src/LexText/Interlinear/InterlinearRecords.cs | 89 ++++++++++ 4 files changed, 269 insertions(+), 1 deletion(-) create mode 100644 Src/LexText/Interlinear/InterlinearRecords.cs diff --git a/DistFiles/Language Explorer/Export Templates/Interlinear/FlexInterlinear.xsd b/DistFiles/Language Explorer/Export Templates/Interlinear/FlexInterlinear.xsd index 53179901c6..6765543b87 100644 --- a/DistFiles/Language Explorer/Export Templates/Interlinear/FlexInterlinear.xsd +++ b/DistFiles/Language Explorer/Export Templates/Interlinear/FlexInterlinear.xsd @@ -1,4 +1,4 @@ - + @@ -7,6 +7,23 @@ + + + + + + + + + + + + + + + + + @@ -131,6 +148,15 @@ + + + + + + + + + diff --git a/Src/LexText/Interlinear/FlexInterlinModel/FlexInterlinear.cs b/Src/LexText/Interlinear/FlexInterlinModel/FlexInterlinear.cs index bd8f863236..f48e8773b9 100644 --- a/Src/LexText/Interlinear/FlexInterlinModel/FlexInterlinear.cs +++ b/Src/LexText/Interlinear/FlexInterlinModel/FlexInterlinear.cs @@ -1,5 +1,6 @@ //------------------------------------------------------------------------------ // +// Generated by: xsd FlexInterlinear.xsd /classes in Visual Studio's Developer Command Prompt // This code was generated by a tool - however, it has been heavily massaged, since the tool is kind of broken -NaylorJ // Runtime Version:2.0.50727.5446 // @@ -15,6 +16,7 @@ using System.Diagnostics; using System.Xml.Serialization; +using static System.Windows.Forms.LinkLabel; // // This source code was auto-generated by xsd, Version=2.0.50727.1432. and modified by Jason Naylor Version=1.0 @@ -109,6 +111,10 @@ public partial class Interlineartext private item[] itemField; + private link[] linkField; + + private Record[] recordsField; + private Paragraph[] paragraphsField; private Languages languagesField; @@ -137,6 +143,152 @@ public item[] Items } } + /// + [System.Xml.Serialization.XmlElementAttribute("link", IsNullable = true)] + public link[] Links + { + get + { + return this.linkField; + } + set + { + this.linkField = value; + } + } + + /// + [System.Xml.Serialization.XmlArrayAttribute(Form = System.Xml.Schema.XmlSchemaForm.Unqualified)] + [System.Xml.Serialization.XmlArrayItemAttribute("record", Form = System.Xml.Schema.XmlSchemaForm.Unqualified, IsNullable = false)] + public Record[] Records + { + get + { + return this.recordsField; + } + set + { + this.recordsField = value; + } + } + + /// + [System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "4.8.3928.0")] + [System.SerializableAttribute()] + [System.Diagnostics.DebuggerStepThroughAttribute()] + [System.ComponentModel.DesignerCategoryAttribute("code")] + [System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true)] + [System.Xml.Serialization.XmlRootAttribute(Namespace = "", IsNullable = true)] + public partial class link + { + + private string typeField; + + private string valueField; + + /// + [System.Xml.Serialization.XmlAttributeAttribute()] + public string type + { + get + { + return this.typeField; + } + set + { + this.typeField = value; + } + } + + /// + [System.Xml.Serialization.XmlTextAttribute()] + public string Value + { + get + { + return this.valueField; + } + set + { + this.valueField = value; + } + } + } + + /// + [System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "4.8.3928.0")] + [System.SerializableAttribute()] + [System.Diagnostics.DebuggerStepThroughAttribute()] + [System.ComponentModel.DesignerCategoryAttribute("code")] + [System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true)] + public partial class Record + { + + private item[] itemField; + + private link[] linkField; + + private string guidField; + + private string typeField; + + /// + [System.Xml.Serialization.XmlElementAttribute("item", IsNullable = true)] + public item[] item + { + get + { + return this.itemField; + } + set + { + this.itemField = value; + } + } + + /// + [System.Xml.Serialization.XmlElementAttribute("link", IsNullable = true)] + public link[] link + { + get + { + return this.linkField; + } + set + { + this.linkField = value; + } + } + + /// + [System.Xml.Serialization.XmlAttributeAttribute()] + public string guid + { + get + { + return this.guidField; + } + set + { + this.guidField = value; + } + } + + /// + [System.Xml.Serialization.XmlAttributeAttribute()] + public string type + { + get + { + return this.typeField; + } + set + { + this.typeField = value; + } + } + } + /// [System.Xml.Serialization.XmlArrayAttribute(Form = System.Xml.Schema.XmlSchemaForm.Unqualified)] [System.Xml.Serialization.XmlArrayItemAttribute("paragraph", Form = System.Xml.Schema.XmlSchemaForm.Unqualified, IsNullable = false)] diff --git a/Src/LexText/Interlinear/ITextDll.csproj b/Src/LexText/Interlinear/ITextDll.csproj index ee00d22841..22f10a31b6 100644 --- a/Src/LexText/Interlinear/ITextDll.csproj +++ b/Src/LexText/Interlinear/ITextDll.csproj @@ -460,6 +460,7 @@ InterlinDocForAnalysis.cs + Form diff --git a/Src/LexText/Interlinear/InterlinearRecords.cs b/Src/LexText/Interlinear/InterlinearRecords.cs new file mode 100644 index 0000000000..c60c918c37 --- /dev/null +++ b/Src/LexText/Interlinear/InterlinearRecords.cs @@ -0,0 +1,89 @@ +using SIL.LCModel; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; + +namespace SIL.FieldWorks.IText +{ + /// + /// InterlinearRecords provides a mapping between type and property names and XML names + /// for records associated with interlinear texts. The standard mapping is used for export, + /// and the inverted mapping is used for import. + /// + internal class InterlinearRecords + { + private Dictionary m_typeMap; + private Dictionary m_invertedTypeMap; + + private readonly Dictionary> m_propertyMaps; + private readonly Dictionary> m_invertedPropertyMaps; + + internal Dictionary TypeMap + { get { return m_typeMap; } } + + internal Dictionary InvertedTypeMap + { get { return m_invertedTypeMap; } } + + internal InterlinearRecords() + { + m_typeMap = new Dictionary + { + { typeof(ICmPossibility), "Possibility" }, + { typeof(IRnGenericRec), "Record" } + }; + m_invertedTypeMap = new Dictionary(); + foreach (Type type in m_typeMap.Keys) + { + m_invertedTypeMap[m_typeMap[type]] = type; + } + + m_propertyMaps = new Dictionary>(); + m_propertyMaps[typeof(ICmPossibility)] = new Dictionary() + { + { "Name", "name" }, + { "Abbreviation", "abbreviation" }, + { "Description", "description" }, + { "StatusRA", "status" }, + { "DiscusionOA", "discussion" }, + { "ConfidenceRA", "confidence" }, + { "ResearchersRA", "researcher" }, + { "RestrictionsRA", "restriction" }, + }; + m_invertedPropertyMaps = new Dictionary>(); + } + + internal Dictionary GetPropertyMap(Type type) + { + return m_propertyMaps[type]; + } + + internal Dictionary GetInvertedPropertyMap(string type) + { + if (m_invertedPropertyMaps.ContainsKey(type)) + return m_invertedPropertyMaps[type]; + + Dictionary propertyMap = GetPropertyMap(InvertedTypeMap[type]); + Dictionary invertedPropertyMap = new Dictionary(); + foreach(string name in propertyMap.Keys) + { + invertedPropertyMap[propertyMap[name]] = name; + } + m_invertedPropertyMaps.Add(type, propertyMap); + return invertedPropertyMap; + } + + internal string HyphenCase(string name) + { + string newName = ""; + for (int i = 0; i < name.Length; i++) + { + newName += (i > 0 && char.IsUpper(name[i])) ? ("-" + char.ToLower(name[i])) : name[i].ToString(); + } + return newName; + } + + } +} From 20eb7cc56297636c6ed3c2cd57fd9c7b28d17c44 Mon Sep 17 00:00:00 2001 From: John Maxwell Date: Thu, 2 Oct 2025 09:46:16 -0700 Subject: [PATCH 06/18] Add code to export genres as records --- .../ITextDllTests/InterlinearExporterTests.cs | 13 ++ .../Interlinear/InterlinearExporter.cs | 120 +++++++++++++++--- Src/LexText/Interlinear/InterlinearRecords.cs | 32 ++--- 3 files changed, 133 insertions(+), 32 deletions(-) diff --git a/Src/LexText/Interlinear/ITextDllTests/InterlinearExporterTests.cs b/Src/LexText/Interlinear/ITextDllTests/InterlinearExporterTests.cs index e903cfa27d..602fb8e417 100644 --- a/Src/LexText/Interlinear/ITextDllTests/InterlinearExporterTests.cs +++ b/Src/LexText/Interlinear/ITextDllTests/InterlinearExporterTests.cs @@ -1119,6 +1119,19 @@ public void ValidateMultipleComments() AssertThatXmlIn.Dom(exportedDoc).HasSpecifiedNumberOfMatchesForXpath("//interlinear-text/item[@type=\"comment\"]", 2); } + [Test] + public void ValidateMultipleGenres() + { + Cache.LanguageProject.GenreListOA = Cache.ServiceLocator.GetInstance().Create(); + var genre1 = Cache.LanguageProject.GenreListOA.FindOrCreatePossibility("genre1", Cache.DefaultAnalWs); + var genre2 = Cache.LanguageProject.GenreListOA.FindOrCreatePossibility("genre2", Cache.DefaultAnalWs); + m_text1.GenresRC.Add(genre1); + m_text1.GenresRC.Add(genre2); + XmlDocument exportedDoc = ExportToXml(); + AssertThatXmlIn.Dom(exportedDoc).HasSpecifiedNumberOfMatchesForXpath("//interlinear-text/link[@type=\"genre\"]", 2); + AssertThatXmlIn.Dom(exportedDoc).HasSpecifiedNumberOfMatchesForXpath("//interlinear-text/records/record[@type=\"Possibility\"]", 2); + } + [Test] public void ValidateIsTranslated() { diff --git a/Src/LexText/Interlinear/InterlinearExporter.cs b/Src/LexText/Interlinear/InterlinearExporter.cs index e7dbc76dd6..22e91d1969 100644 --- a/Src/LexText/Interlinear/InterlinearExporter.cs +++ b/Src/LexText/Interlinear/InterlinearExporter.cs @@ -2,19 +2,20 @@ // This software is licensed under the LGPL, version 2.1 or later // (http://www.gnu.org/licenses/lgpl-2.1.html) -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Xml; -using SIL.LCModel.Core.WritingSystems; -using SIL.LCModel.Core.KernelInterfaces; -using SIL.FieldWorks.Common.ViewsInterfaces; using SIL.FieldWorks.Common.RootSites; +using SIL.FieldWorks.Common.ViewsInterfaces; using SIL.LCModel; +using SIL.LCModel.Core.KernelInterfaces; +using SIL.LCModel.Core.Text; +using SIL.LCModel.Core.WritingSystems; using SIL.LCModel.DomainServices; using SIL.LCModel.Infrastructure; -using SIL.LCModel.Core.Text; using SIL.LCModel.Utils; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Reflection; +using System.Xml; namespace SIL.FieldWorks.IText { @@ -45,6 +46,8 @@ public class InterlinearExporter : CollectorEnv bool pendingIsTranslated = false; DateTime pendingDateCreated = DateTime.MinValue; DateTime pendingDateModified = DateTime.MinValue; + ILcmReferenceCollection pendingGenres; + Queue pendingRecords = new Queue(); int m_flidStTextTitle; int m_flidStTextSource; InterlinVc m_vc = null; @@ -55,6 +58,7 @@ public class InterlinearExporter : CollectorEnv IMoMorphType m_mmtProclitic; protected WritingSystemManager m_wsManager; protected ICmObjectRepository m_repoObj; + InterlinearRecords m_records; public static InterlinearExporter Create(string mode, LcmCache cache, XmlWriter writer, ICmObject objRoot, InterlinLineChoices lineChoices, InterlinVc vc) @@ -95,6 +99,7 @@ protected InterlinearExporter(LcmCache cache, XmlWriter writer, ICmObject objRoo m_wsManager = m_cache.ServiceLocator.WritingSystemManager; m_repoObj = m_cache.ServiceLocator.GetInstance(); + m_records = new InterlinearRecords(); } public void ExportDisplay() @@ -551,6 +556,82 @@ private void OpenItem(string itemType) m_fItemIsOpen = true; } + /// + /// Write an link to an object. + /// + private void WritePendingLink(string linkType, ICmObject obj) + { + if (obj == null) + return; + m_writer.WriteStartElement("link"); + m_writer.WriteAttributeString("type", linkType); + m_writer.WriteString(obj.Guid.ToString()); + m_writer.WriteEndElement(); + pendingRecords.Enqueue(obj); + } + + /// + /// Write an object out as a record. + /// + private void WritePendingRecord(ICmObject record) + { + Type recordType = record.GetType(); + string typeName = recordType.Name; + if (!m_records.TypeMap.ContainsKey(typeName)) + return; + m_writer.WriteStartElement("record"); + m_writer.WriteAttributeString("type", m_records.TypeMap[typeName]); + m_writer.WriteAttributeString("guid", record.Guid.ToString()); + Dictionary propertyMap = m_records.GetPropertyMap(typeName); + foreach (string propName in propertyMap.Keys) + { + PropertyInfo property = recordType.GetProperty(propName); + object value = property.GetValue(record, null); + WritePendingProperty(propName, value); + } + m_writer.WriteEndElement(); + } + + /// + /// Write property and value, dispatching on value type. + /// + /// + /// + private void WritePendingProperty(string propName, object value) + { + if (value == null) return; + if (value is ICmObject objectValue) + { + WritePendingLink(propName, objectValue); + } + else if (value is ITsString) + { + ITsString hystericalRaisens = (ITsString)value; + WritePendingItem(propName, ref hystericalRaisens); + } + else if (value is ITsMultiString multiString) + { + for (int i = 0; i < multiString.StringCount; i++) + { + ITsString hystericalRaisens = multiString.GetStringFromIndex(i, out int ws); + WritePendingItem(propName, ref hystericalRaisens); + } + } + else if (value is bool boolValue) + { + var hystericalRaisens = TsStringUtils.MakeString(boolValue ? "true" : "false", m_cache.DefaultAnalWs); + WritePendingItem(propName, ref hystericalRaisens); + } + else if (value is DateTime dateTime) + { + if (dateTime != DateTime.MinValue) + { + ITsString dateTimeString = TsStringUtils.MakeString(dateTime.ToLCMTimeFormatWithMillisString(), m_cache.DefaultAnalWs); + WritePendingItem(propName, ref dateTimeString); + } + } + } + /// /// This method (as far as I know) will be first called on the StText object, and then recursively from the /// base implementation for vector items in component objects. @@ -599,18 +680,22 @@ public override void AddObjVecItems(int tag, IVwViewConstructor vc, int frag) } if (pendingIsTranslated) { - var hystericalRaisens = TsStringUtils.MakeString("true", m_cache.WritingSystemFactory.UserWs); - WritePendingItem("text-is-translation", ref hystericalRaisens); + WritePendingProperty("text-is-translation", true); } - if (pendingDateCreated != DateTime.MinValue) + WritePendingProperty("date-created", pendingDateCreated); + WritePendingProperty("date-modified", pendingDateModified); + foreach (var genre in pendingGenres) { - ITsString dateCreated = TsStringUtils.MakeString(pendingDateCreated.ToLCMTimeFormatWithMillisString(), m_cache.WritingSystemFactory.UserWs); - WritePendingItem("date-created", ref dateCreated); + WritePendingLink("genre", genre); } - if (pendingDateModified != DateTime.MinValue) + if (pendingRecords.Count > 0) { - ITsString dateModified = TsStringUtils.MakeString(pendingDateModified.ToLCMTimeFormatWithMillisString(), m_cache.WritingSystemFactory.UserWs); - WritePendingItem("date-modified", ref dateModified); + m_writer.WriteStartElement("records"); + while (pendingRecords.Count > 0) + { + WritePendingRecord(pendingRecords.Dequeue()); + } + m_writer.WriteEndElement(); } m_writer.WriteStartElement("paragraphs"); break; @@ -808,6 +893,9 @@ private void SetTextTitleAndMetadata(IStText txt) pendingIsTranslated = text.IsTranslated; pendingDateCreated = text.DateCreated; pendingDateModified = text.DateModified; + pendingGenres = text.GenresRC; + if (text.AssociatedNotebookRecord != null) + pendingRecords.Enqueue(text.AssociatedNotebookRecord); } else if (TextSource.IsScriptureText(txt)) { diff --git a/Src/LexText/Interlinear/InterlinearRecords.cs b/Src/LexText/Interlinear/InterlinearRecords.cs index c60c918c37..64521de72e 100644 --- a/Src/LexText/Interlinear/InterlinearRecords.cs +++ b/Src/LexText/Interlinear/InterlinearRecords.cs @@ -15,47 +15,47 @@ namespace SIL.FieldWorks.IText /// internal class InterlinearRecords { - private Dictionary m_typeMap; - private Dictionary m_invertedTypeMap; + private Dictionary m_typeMap; + private Dictionary m_invertedTypeMap; - private readonly Dictionary> m_propertyMaps; + private readonly Dictionary> m_propertyMaps; private readonly Dictionary> m_invertedPropertyMaps; - internal Dictionary TypeMap + internal Dictionary TypeMap { get { return m_typeMap; } } - internal Dictionary InvertedTypeMap + internal Dictionary InvertedTypeMap { get { return m_invertedTypeMap; } } internal InterlinearRecords() { - m_typeMap = new Dictionary + m_typeMap = new Dictionary { - { typeof(ICmPossibility), "Possibility" }, - { typeof(IRnGenericRec), "Record" } + { "CmPossibility", "Possibility" }, + { "RnGenericRec", "Record" } }; - m_invertedTypeMap = new Dictionary(); - foreach (Type type in m_typeMap.Keys) + m_invertedTypeMap = new Dictionary(); + foreach (string type in m_typeMap.Keys) { m_invertedTypeMap[m_typeMap[type]] = type; } - m_propertyMaps = new Dictionary>(); - m_propertyMaps[typeof(ICmPossibility)] = new Dictionary() + m_propertyMaps = new Dictionary>(); + m_propertyMaps["CmPossibility"] = new Dictionary() { { "Name", "name" }, { "Abbreviation", "abbreviation" }, { "Description", "description" }, { "StatusRA", "status" }, - { "DiscusionOA", "discussion" }, + { "DiscussionOA", "discussion" }, { "ConfidenceRA", "confidence" }, - { "ResearchersRA", "researcher" }, - { "RestrictionsRA", "restriction" }, + { "ResearchersRC", "researcher" }, + { "RestrictionsRC", "restriction" }, }; m_invertedPropertyMaps = new Dictionary>(); } - internal Dictionary GetPropertyMap(Type type) + internal Dictionary GetPropertyMap(string type) { return m_propertyMaps[type]; } From 4f6f9323a0529ab8a722f23a57ab1ecc9301bd37 Mon Sep 17 00:00:00 2001 From: John Maxwell Date: Fri, 3 Oct 2025 09:54:52 -0700 Subject: [PATCH 07/18] Add ability to import genres --- .../Interlinear/BIRDInterlinearImporter.cs | 196 ++++++++++++++++-- .../FlexInterlinModel/FlexInterlinear.cs | 2 +- .../ITextDllTests/BIRDFormatImportTests.cs | 46 ++++ Src/LexText/Interlinear/InterlinearRecords.cs | 14 +- 4 files changed, 243 insertions(+), 15 deletions(-) diff --git a/Src/LexText/Interlinear/BIRDInterlinearImporter.cs b/Src/LexText/Interlinear/BIRDInterlinearImporter.cs index adcdcb204e..5238bf7949 100644 --- a/Src/LexText/Interlinear/BIRDInterlinearImporter.cs +++ b/Src/LexText/Interlinear/BIRDInterlinearImporter.cs @@ -2,24 +2,24 @@ // This software is licensed under the LGPL, version 2.1 or later // (http://www.gnu.org/licenses/lgpl-2.1.html) -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; -using System.Text; -using System.Windows.Forms; -using SIL.LCModel.Core.Text; -using SIL.LCModel.Core.WritingSystems; -using SIL.LCModel.Core.KernelInterfaces; +using SIL.Extensions; using SIL.FieldWorks.Common.FwUtils; -using SIL.LCModel; -using SIL.LCModel.DomainServices; using SIL.FieldWorks.IText.FlexInterlinModel; +using SIL.LCModel; using SIL.LCModel.Application.ApplicationServices; using SIL.LCModel.Core.Cellar; +using SIL.LCModel.Core.KernelInterfaces; +using SIL.LCModel.Core.Text; +using SIL.LCModel.Core.WritingSystems; +using SIL.LCModel.DomainServices; using SIL.LCModel.Infrastructure; using SIL.LCModel.Utils; -using SIL.Extensions; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Windows.Forms; namespace SIL.FieldWorks.IText { @@ -1169,6 +1169,36 @@ private static void SetTextMetaAndMergeMedia(LcmCache cache, Interlineartext int } } + // Process links and records. + Dictionary> valueProperties = new Dictionary>(); + InterlinearRecords records = new InterlinearRecords(); + + if (interlinText.Links != null) + { + foreach (var link in interlinText.Links) + { + switch (link.type) + { + case "genre": + SaveLink(interlinText.guid.ToString(), link.type, link.Value, valueProperties); + break; + } + } + } + if (interlinText.records != null) + { + foreach (var record in interlinText.records) + { + SaveLinks(record, valueProperties); + } + foreach (var record in interlinText.records) + { + CreateRecord(record, valueProperties, records, cache); + } + } + CreateLinks(valueProperties, records, cache); + + if (interlinText.mediafiles != null) { if (newText.MediaFilesOA == null) @@ -1203,5 +1233,147 @@ private static void SetTextMetaAndMergeMedia(LcmCache cache, Interlineartext int } } } + + private class Property + { + public string Object { get; set; } + + public string PropName { get; set; } + + } + + /// + /// Save a link from object to value in valueProperties. + /// + private static void SaveLink(string obj, string propName, string value, Dictionary> valueProperties) + { + if (!valueProperties.ContainsKey(value)) + valueProperties[value] = new List(); + valueProperties[value].Add(new Property {Object = obj, PropName = propName}); + } + + /// + /// Save all links from record to values in valueProperties. + /// + private static void SaveLinks(Interlineartext.Record record, Dictionary> valueProperties) + { + if (record.link != null) + { + for (int i = 0; i < record.link.Length; i++) + { + SaveLink(record.guid, record.link[i].type, record.link[i].Value, valueProperties); + } + } + } + + /// + /// Create record and fill in item properties. + /// + private static void CreateRecord(Interlineartext.Record record, Dictionary> valueProperties, InterlinearRecords records, LcmCache cache) + { + if (!valueProperties.Keys.Contains(record.guid)) + return; + Guid guid = new Guid(record.guid); + ICmObjectRepository repository = cache.ServiceLocator.GetInstance(); + if (!repository.TryGetObject(guid, out ICmObject obj)) + { + obj = CreateRecordObject(record, valueProperties[record.guid], cache); + Type objType = obj.GetType(); + /// Set item properties. + Dictionary propertyMap = records.GetInvertedPropertyMap(record.type); + foreach (var item in record.item) + { + PropertyInfo propInfo = objType.GetProperty(propertyMap[item.type]); + Type valueType = propInfo.PropertyType; + object value = null; + if (valueType.Name == "IMultiUnicode") + { + int ws = GetWsEngine(cache.WritingSystemFactory, item.lang).Handle; + value = TsStringUtils.MakeString(item.Value, ws); + } + SetProperty(obj, propertyMap[item.type], value); + } + } + } + + /// + /// Create record. + /// + private static ICmObject CreateRecordObject(Interlineartext.Record record, List properties, LcmCache cache) + { + foreach (var property in properties) + { + switch (property.PropName) + { + case "genre": + ICmPossibility genre = cache.ServiceLocator.GetInstance().Create(new Guid(record.guid)); + cache.LanguageProject.GenreListOA.PossibilitiesOS.Add(genre); + return genre; + } + } + return null; + } + + /// + /// Create links. + /// + private static void CreateLinks(Dictionary> valueProperties, InterlinearRecords records, LcmCache cache) + { + foreach (var valGuid in valueProperties.Keys) + { + foreach (var property in valueProperties[valGuid]) + { + CreateLink(property.Object, property.PropName, valGuid, records, cache); + } + } + } + + /// + /// Create given link. + /// + private static void CreateLink(string objGuid, string propName, string valueGuid, InterlinearRecords records, LcmCache cache) + { + ICmObjectRepository repository = cache.ServiceLocator.GetInstance(); + ICmObject obj = repository.GetObject(new Guid(objGuid)); + ICmObject value = repository.GetObject(new Guid(valueGuid)); + Dictionary propertyMap = records.InvertMap(records.GetPropertyMap(obj.GetType().ToString())); + SetProperty(obj, propertyMap[propName], value); + } + + /// + /// Set object property to value. + private static void SetProperty(ICmObject obj, string propName, object value) + { + PropertyInfo propInfo = obj.GetType().GetProperty(propName); + Type valueType = propInfo.PropertyType; + if (value == null) + return; + if (valueType.Name == "IMultiUnicode") + { + IMultiUnicode currentValue = (IMultiUnicode)propInfo.GetValue(obj, null); + if (value is ITsString itsString) + { + currentValue.set_String(itsString.get_WritingSystemAt(0), itsString.Text); + return; + } + currentValue.CopyAlternatives((IMultiUnicode)value); + } + else if (valueType.IsInstanceOfType(value)) + { + propInfo.SetValue(obj, value); + return; + } + else if (valueType.Name == "ILcmReferenceCollection`1") + { + ILcmReferenceCollection currentValue = (ILcmReferenceCollection)propInfo.GetValue(obj, null); + currentValue.Add((ICmPossibility)value); + return; + } + else + { + throw new Exception(); + } + } + } } \ No newline at end of file diff --git a/Src/LexText/Interlinear/FlexInterlinModel/FlexInterlinear.cs b/Src/LexText/Interlinear/FlexInterlinModel/FlexInterlinear.cs index f48e8773b9..7564e439b8 100644 --- a/Src/LexText/Interlinear/FlexInterlinModel/FlexInterlinear.cs +++ b/Src/LexText/Interlinear/FlexInterlinModel/FlexInterlinear.cs @@ -160,7 +160,7 @@ public link[] Links /// [System.Xml.Serialization.XmlArrayAttribute(Form = System.Xml.Schema.XmlSchemaForm.Unqualified)] [System.Xml.Serialization.XmlArrayItemAttribute("record", Form = System.Xml.Schema.XmlSchemaForm.Unqualified, IsNullable = false)] - public Record[] Records + public Record[] records { get { diff --git a/Src/LexText/Interlinear/ITextDllTests/BIRDFormatImportTests.cs b/Src/LexText/Interlinear/ITextDllTests/BIRDFormatImportTests.cs index e51d397e21..2407105f89 100644 --- a/Src/LexText/Interlinear/ITextDllTests/BIRDFormatImportTests.cs +++ b/Src/LexText/Interlinear/ITextDllTests/BIRDFormatImportTests.cs @@ -809,6 +809,52 @@ public void OneOfEachElementTypeTest() } } + [Test] + public void TestGenres() + { + string title = "atrocious"; + string textGuid = "a122d9bb-2d43-4e4c-b74f-6fe44d1c6cb3"; + string genre1Guid = "b405f3c0-58e1-4492-8a40-e955774a6912"; + string genre2Guid = "45e6f056-98ac-45d6-858e-59450993f269"; + string genre1Name = "genre1"; + string genre2Name = "genre2"; + //an interliner text example xml string + string xml = "" + + "" + title + "" + + "" + genre1Guid + "" + + "" + genre2Guid + "" + + "" + + "" + genre1Name + "" + + "" + genre2Name + "" + + "" + + "" + + "1 Musical" + + "origem: mary poppins" + + "supercalifragilisticexpialidocious" + + "absurdo" + + ""; + + NonUndoableUnitOfWorkHelper.Do(Cache.ActionHandlerAccessor, () => + { + Cache.LanguageProject.GenreListOA = Cache.ServiceLocator.GetInstance().Create(); + }); + LinguaLinksImport li = new LinguaLinksImport(Cache, null, null); + LCModel.IText text = null; + using (var stream = new MemoryStream(Encoding.ASCII.GetBytes(xml.ToCharArray()))) + { + li.ImportInterlinear(new DummyProgressDlg(), stream, 0, ref text); + using (var firstEntry = Cache.LanguageProject.Texts.GetEnumerator()) + { + firstEntry.MoveNext(); + var imported = firstEntry.Current; + Assert.AreEqual(2, imported.GenresRC.Count); + Assert.AreEqual(genre1Guid, imported.GenresRC.First().Guid.ToString()); + Assert.AreEqual(genre1Name, imported.GenresRC.First().Name.BestAnalysisAlternative.Text); + Assert.AreEqual(2, imported.Cache.LanguageProject.GenreListOA.PossibilitiesOS.Count); + } + } + } + [Test] public void TestSpacesAroundPunct() { diff --git a/Src/LexText/Interlinear/InterlinearRecords.cs b/Src/LexText/Interlinear/InterlinearRecords.cs index 64521de72e..cd3710a5d5 100644 --- a/Src/LexText/Interlinear/InterlinearRecords.cs +++ b/Src/LexText/Interlinear/InterlinearRecords.cs @@ -52,6 +52,10 @@ internal InterlinearRecords() { "ResearchersRC", "researcher" }, { "RestrictionsRC", "restriction" }, }; + m_propertyMaps["SIL.LCModel.DomainImpl.Text"] = new Dictionary() + { + { "GenresRC", "genre" }, + }; m_invertedPropertyMaps = new Dictionary>(); } @@ -66,12 +70,18 @@ internal Dictionary GetInvertedPropertyMap(string type) return m_invertedPropertyMaps[type]; Dictionary propertyMap = GetPropertyMap(InvertedTypeMap[type]); + Dictionary invertedPropertyMap = InvertMap(propertyMap); + m_invertedPropertyMaps.Add(type, invertedPropertyMap); + return invertedPropertyMap; + } + + internal Dictionary InvertMap(Dictionary propertyMap) + { Dictionary invertedPropertyMap = new Dictionary(); - foreach(string name in propertyMap.Keys) + foreach (string name in propertyMap.Keys) { invertedPropertyMap[propertyMap[name]] = name; } - m_invertedPropertyMaps.Add(type, propertyMap); return invertedPropertyMap; } From fe286f18bff8d8b9e3c05c2f3f811df5ce743a08 Mon Sep 17 00:00:00 2001 From: John Maxwell Date: Mon, 6 Oct 2025 09:03:21 -0700 Subject: [PATCH 08/18] Inverted -> Xml; Add OwningPossibility --- .../Interlinear/BIRDInterlinearImporter.cs | 101 ++++++++++++------ .../ITextDllTests/BIRDFormatImportTests.cs | 15 ++- .../Interlinear/InterlinearExporter.cs | 16 +-- Src/LexText/Interlinear/InterlinearRecords.cs | 54 +++++----- 4 files changed, 119 insertions(+), 67 deletions(-) diff --git a/Src/LexText/Interlinear/BIRDInterlinearImporter.cs b/Src/LexText/Interlinear/BIRDInterlinearImporter.cs index 5238bf7949..c795f19713 100644 --- a/Src/LexText/Interlinear/BIRDInterlinearImporter.cs +++ b/Src/LexText/Interlinear/BIRDInterlinearImporter.cs @@ -1277,21 +1277,23 @@ private static void CreateRecord(Interlineartext.Record record, Dictionary(); if (!repository.TryGetObject(guid, out ICmObject obj)) { - obj = CreateRecordObject(record, valueProperties[record.guid], cache); + obj = CreateRecordObject(record, valueProperties, records, cache); Type objType = obj.GetType(); /// Set item properties. - Dictionary propertyMap = records.GetInvertedPropertyMap(record.type); + Dictionary xmlPropertyMap = records.GetXmlPropertyMap(record.type); foreach (var item in record.item) { - PropertyInfo propInfo = objType.GetProperty(propertyMap[item.type]); - Type valueType = propInfo.PropertyType; + object value = null; - if (valueType.Name == "IMultiUnicode") + PropertyInfo propInfo = objType.GetProperty(xmlPropertyMap[item.type]); + object currentValue = propInfo.GetValue(obj, null); + if (currentValue is IMultiUnicode) { + // value is an ITsString. int ws = GetWsEngine(cache.WritingSystemFactory, item.lang).Handle; value = TsStringUtils.MakeString(item.Value, ws); } - SetProperty(obj, propertyMap[item.type], value); + SetPropertyValue(obj, xmlPropertyMap[item.type], value); } } } @@ -1299,19 +1301,41 @@ private static void CreateRecord(Interlineartext.Record record, Dictionary /// Create record. /// - private static ICmObject CreateRecordObject(Interlineartext.Record record, List properties, LcmCache cache) + private static ICmObject CreateRecordObject(Interlineartext.Record record, Dictionary> valueProperties, InterlinearRecords records, LcmCache cache) { - foreach (var property in properties) + string recordType = records.XmlTypeMap[record.type]; + switch (recordType) + { + case "CmPossibility": + ICmPossibility possibility = cache.ServiceLocator.GetInstance().Create(new Guid(record.guid)); + AddPossibilityToOwner(possibility, record.guid, valueProperties, cache); + return possibility; + } + return null; + } + + private static void AddPossibilityToOwner(ICmPossibility possibility, string guid, Dictionary> valueProperties, LcmCache cache) + { + // Determine the type of possibility based on the property that points to it. + foreach (var property in valueProperties[guid]) { switch (property.PropName) { case "genre": - ICmPossibility genre = cache.ServiceLocator.GetInstance().Create(new Guid(record.guid)); - cache.LanguageProject.GenreListOA.PossibilitiesOS.Add(genre); - return genre; + cache.LanguageProject.GenreListOA.PossibilitiesOS.Add(possibility); + return; + } + } + // Determine the type of the possibility based on the type of the child. + foreach (var property in valueProperties[guid]) + { + switch (property.PropName) + { + case "parent": + AddPossibilityToOwner(possibility, property.Object, valueProperties, cache); + return; } } - return null; } /// @@ -1336,38 +1360,55 @@ private static void CreateLink(string objGuid, string propName, string valueGuid ICmObjectRepository repository = cache.ServiceLocator.GetInstance(); ICmObject obj = repository.GetObject(new Guid(objGuid)); ICmObject value = repository.GetObject(new Guid(valueGuid)); - Dictionary propertyMap = records.InvertMap(records.GetPropertyMap(obj.GetType().ToString())); - SetProperty(obj, propertyMap[propName], value); + Dictionary xmlPropertyMap = records.InvertMap(records.GetPropertyMap(obj.GetType().Name)); + SetPropertyValue(obj, xmlPropertyMap[propName], value); } /// /// Set object property to value. - private static void SetProperty(ICmObject obj, string propName, object value) + private static void SetPropertyValue(ICmObject obj, string propName, object value) { - PropertyInfo propInfo = obj.GetType().GetProperty(propName); - Type valueType = propInfo.PropertyType; if (value == null) return; - if (valueType.Name == "IMultiUnicode") + if (propName == "OwningPossibility") { - IMultiUnicode currentValue = (IMultiUnicode)propInfo.GetValue(obj, null); - if (value is ITsString itsString) + // We store OwningPossibility but set SubPossibilitiesOS. + SetPropertyValue((ICmObject)value, "SubPossibilitiesOS", obj); + return; + } + PropertyInfo propInfo = obj.GetType().GetProperty(propName); + object currentValue = propInfo.GetValue(obj, null); + if (value.GetType().IsInstanceOfType(currentValue.GetType())) + { + propInfo.SetValue(obj, value); + } + else if (currentValue is ILcmOwningSequence possibilitySequence) + { + if (value is ICmPossibility possibility) { - currentValue.set_String(itsString.get_WritingSystemAt(0), itsString.Text); - return; + possibilitySequence.Add(possibility); } - currentValue.CopyAlternatives((IMultiUnicode)value); } - else if (valueType.IsInstanceOfType(value)) + else if (currentValue is ILcmReferenceCollection possibilityCollection) { - propInfo.SetValue(obj, value); - return; + if (value is ICmPossibility possibility) + { + possibilityCollection.Add(possibility); + } } - else if (valueType.Name == "ILcmReferenceCollection`1") + else if (currentValue is IMultiString multiString) { - ILcmReferenceCollection currentValue = (ILcmReferenceCollection)propInfo.GetValue(obj, null); - currentValue.Add((ICmPossibility)value); - return; + if (value is ITsString itsString) + { + multiString.set_String(itsString.get_WritingSystemAt(0), itsString); + } + } + else if (currentValue is IMultiUnicode multiUnicode) + { + if (value is ITsString itsString) + { + multiUnicode.set_String(itsString.get_WritingSystemAt(0), itsString.Text); + } } else { diff --git a/Src/LexText/Interlinear/ITextDllTests/BIRDFormatImportTests.cs b/Src/LexText/Interlinear/ITextDllTests/BIRDFormatImportTests.cs index 2407105f89..863834e977 100644 --- a/Src/LexText/Interlinear/ITextDllTests/BIRDFormatImportTests.cs +++ b/Src/LexText/Interlinear/ITextDllTests/BIRDFormatImportTests.cs @@ -816,16 +816,20 @@ public void TestGenres() string textGuid = "a122d9bb-2d43-4e4c-b74f-6fe44d1c6cb3"; string genre1Guid = "b405f3c0-58e1-4492-8a40-e955774a6912"; string genre2Guid = "45e6f056-98ac-45d6-858e-59450993f269"; + string genre3Guid = "45e6f056-98ac-45d6-858f-59450993f269"; string genre1Name = "genre1"; string genre2Name = "genre2"; + string genre3Name = "genre3"; //an interliner text example xml string string xml = "" + "" + title + "" + "" + genre1Guid + "" + - "" + genre2Guid + "" + + "" + genre3Guid + "" + "" + "" + genre1Name + "" + "" + genre2Name + "" + + "" + genre3Name + "" + + "" + genre2Guid + "" + "" + "" + "" + "1 Musical" + @@ -850,7 +854,16 @@ public void TestGenres() Assert.AreEqual(2, imported.GenresRC.Count); Assert.AreEqual(genre1Guid, imported.GenresRC.First().Guid.ToString()); Assert.AreEqual(genre1Name, imported.GenresRC.First().Name.BestAnalysisAlternative.Text); + Assert.AreEqual(genre3Guid, imported.GenresRC.Last().Guid.ToString()); + Assert.AreEqual(genre3Name, imported.GenresRC.Last().Name.BestAnalysisAlternative.Text); Assert.AreEqual(2, imported.Cache.LanguageProject.GenreListOA.PossibilitiesOS.Count); + ILcmOwningSequence genres = imported.Cache.LanguageProject.GenreListOA.PossibilitiesOS; + Assert.AreEqual(genre1Guid, genres.First().Guid.ToString()); + Assert.AreEqual(genre1Name, genres.First().Name.BestAnalysisAlternative.Text); + Assert.AreEqual(genre2Guid, genres.Last().Guid.ToString()); + Assert.AreEqual(genre2Name, genres.Last().Name.BestAnalysisAlternative.Text); + Assert.AreEqual(genre3Guid, genres.Last().SubPossibilitiesOS.First().Guid.ToString()); + Assert.AreEqual(genre3Name, genres.Last().SubPossibilitiesOS.First().Name.BestAnalysisAlternative.Text); } } } diff --git a/Src/LexText/Interlinear/InterlinearExporter.cs b/Src/LexText/Interlinear/InterlinearExporter.cs index 22e91d1969..cb50aea35b 100644 --- a/Src/LexText/Interlinear/InterlinearExporter.cs +++ b/Src/LexText/Interlinear/InterlinearExporter.cs @@ -587,7 +587,7 @@ private void WritePendingRecord(ICmObject record) { PropertyInfo property = recordType.GetProperty(propName); object value = property.GetValue(record, null); - WritePendingProperty(propName, value); + WritePendingProperty(propertyMap[propName], value); } m_writer.WriteEndElement(); } @@ -595,39 +595,39 @@ private void WritePendingRecord(ICmObject record) /// /// Write property and value, dispatching on value type. /// - /// + /// /// - private void WritePendingProperty(string propName, object value) + private void WritePendingProperty(string propType, object value) { if (value == null) return; if (value is ICmObject objectValue) { - WritePendingLink(propName, objectValue); + WritePendingLink(propType, objectValue); } else if (value is ITsString) { ITsString hystericalRaisens = (ITsString)value; - WritePendingItem(propName, ref hystericalRaisens); + WritePendingItem(propType, ref hystericalRaisens); } else if (value is ITsMultiString multiString) { for (int i = 0; i < multiString.StringCount; i++) { ITsString hystericalRaisens = multiString.GetStringFromIndex(i, out int ws); - WritePendingItem(propName, ref hystericalRaisens); + WritePendingItem(propType, ref hystericalRaisens); } } else if (value is bool boolValue) { var hystericalRaisens = TsStringUtils.MakeString(boolValue ? "true" : "false", m_cache.DefaultAnalWs); - WritePendingItem(propName, ref hystericalRaisens); + WritePendingItem(propType, ref hystericalRaisens); } else if (value is DateTime dateTime) { if (dateTime != DateTime.MinValue) { ITsString dateTimeString = TsStringUtils.MakeString(dateTime.ToLCMTimeFormatWithMillisString(), m_cache.DefaultAnalWs); - WritePendingItem(propName, ref dateTimeString); + WritePendingItem(propType, ref dateTimeString); } } } diff --git a/Src/LexText/Interlinear/InterlinearRecords.cs b/Src/LexText/Interlinear/InterlinearRecords.cs index cd3710a5d5..1a57894e5e 100644 --- a/Src/LexText/Interlinear/InterlinearRecords.cs +++ b/Src/LexText/Interlinear/InterlinearRecords.cs @@ -16,16 +16,16 @@ namespace SIL.FieldWorks.IText internal class InterlinearRecords { private Dictionary m_typeMap; - private Dictionary m_invertedTypeMap; + private Dictionary m_xmlTypeMap; private readonly Dictionary> m_propertyMaps; - private readonly Dictionary> m_invertedPropertyMaps; + private readonly Dictionary> m_xmlPropertyMaps; internal Dictionary TypeMap { get { return m_typeMap; } } - internal Dictionary InvertedTypeMap - { get { return m_invertedTypeMap; } } + internal Dictionary XmlTypeMap + { get { return m_xmlTypeMap; } } internal InterlinearRecords() { @@ -34,29 +34,27 @@ internal InterlinearRecords() { "CmPossibility", "Possibility" }, { "RnGenericRec", "Record" } }; - m_invertedTypeMap = new Dictionary(); + m_xmlTypeMap = new Dictionary(); foreach (string type in m_typeMap.Keys) { - m_invertedTypeMap[m_typeMap[type]] = type; + m_xmlTypeMap[m_typeMap[type]] = type; } - m_propertyMaps = new Dictionary>(); - m_propertyMaps["CmPossibility"] = new Dictionary() + m_propertyMaps = new Dictionary> { - { "Name", "name" }, - { "Abbreviation", "abbreviation" }, - { "Description", "description" }, - { "StatusRA", "status" }, - { "DiscussionOA", "discussion" }, - { "ConfidenceRA", "confidence" }, - { "ResearchersRC", "researcher" }, - { "RestrictionsRC", "restriction" }, + ["CmPossibility"] = new Dictionary() + { + { "Name", "name" }, + { "Abbreviation", "abbreviation" }, + { "Description", "description" }, + { "OwningPossibility", "parent" }, + }, + ["Text"] = new Dictionary() + { + { "GenresRC", "genre" }, + } }; - m_propertyMaps["SIL.LCModel.DomainImpl.Text"] = new Dictionary() - { - { "GenresRC", "genre" }, - }; - m_invertedPropertyMaps = new Dictionary>(); + m_xmlPropertyMaps = new Dictionary>(); } internal Dictionary GetPropertyMap(string type) @@ -64,15 +62,15 @@ internal Dictionary GetPropertyMap(string type) return m_propertyMaps[type]; } - internal Dictionary GetInvertedPropertyMap(string type) + internal Dictionary GetXmlPropertyMap(string type) { - if (m_invertedPropertyMaps.ContainsKey(type)) - return m_invertedPropertyMaps[type]; + if (m_xmlPropertyMaps.ContainsKey(type)) + return m_xmlPropertyMaps[type]; - Dictionary propertyMap = GetPropertyMap(InvertedTypeMap[type]); - Dictionary invertedPropertyMap = InvertMap(propertyMap); - m_invertedPropertyMaps.Add(type, invertedPropertyMap); - return invertedPropertyMap; + Dictionary propertyMap = GetPropertyMap(XmlTypeMap[type]); + Dictionary xmlPropertyMap = InvertMap(propertyMap); + m_xmlPropertyMaps.Add(type, xmlPropertyMap); + return xmlPropertyMap; } internal Dictionary InvertMap(Dictionary propertyMap) From b5de17b3a98c73dcb2f962543bcd3f7ee248af6d Mon Sep 17 00:00:00 2001 From: John Maxwell Date: Mon, 6 Oct 2025 11:57:58 -0700 Subject: [PATCH 09/18] Replace link type with guid attribute in item type --- .../Interlinear/FlexInterlinear.xsd | 27 +++--- .../Interlinear/BIRDInterlinearImporter.cs | 25 ++--- .../FlexInterlinModel/FlexInterlinear.cs | 91 ++++--------------- .../ITextDllTests/BIRDFormatImportTests.cs | 22 +++-- .../ITextDllTests/InterlinearExporterTests.cs | 2 +- .../Interlinear/InterlinearExporter.cs | 18 +++- 6 files changed, 67 insertions(+), 118 deletions(-) diff --git a/DistFiles/Language Explorer/Export Templates/Interlinear/FlexInterlinear.xsd b/DistFiles/Language Explorer/Export Templates/Interlinear/FlexInterlinear.xsd index 6765543b87..f70b84e36b 100644 --- a/DistFiles/Language Explorer/Export Templates/Interlinear/FlexInterlinear.xsd +++ b/DistFiles/Language Explorer/Export Templates/Interlinear/FlexInterlinear.xsd @@ -7,7 +7,6 @@ - @@ -15,10 +14,9 @@ - - + @@ -143,20 +141,12 @@ + - - - - - - - - - @@ -179,7 +169,18 @@ - + + + + + + + + + + + + diff --git a/Src/LexText/Interlinear/BIRDInterlinearImporter.cs b/Src/LexText/Interlinear/BIRDInterlinearImporter.cs index c795f19713..eaf4f2868a 100644 --- a/Src/LexText/Interlinear/BIRDInterlinearImporter.cs +++ b/Src/LexText/Interlinear/BIRDInterlinearImporter.cs @@ -1138,6 +1138,8 @@ private static void UpgradeToWordGloss(Word word, ref IAnalysis wordForm) private static void SetTextMetaAndMergeMedia(LcmCache cache, Interlineartext interlinText, ILgWritingSystemFactory wsFactory, LCModel.IText newText, bool merging) { + Dictionary> valueProperties = new Dictionary>(); + if (interlinText.Items != null) // apparently it is null if there are no items. { foreach (var item in interlinText.Items) @@ -1165,26 +1167,16 @@ private static void SetTextMetaAndMergeMedia(LcmCache cache, Interlineartext int case "date-modified": newText.DateModified = DateTime.Parse(item.Value, null, System.Globalization.DateTimeStyles.AssumeUniversal); break; + case "genre": + SaveLink(interlinText.guid.ToString(), item.type, item.guid, valueProperties); + break; } } } // Process links and records. - Dictionary> valueProperties = new Dictionary>(); InterlinearRecords records = new InterlinearRecords(); - if (interlinText.Links != null) - { - foreach (var link in interlinText.Links) - { - switch (link.type) - { - case "genre": - SaveLink(interlinText.guid.ToString(), link.type, link.Value, valueProperties); - break; - } - } - } if (interlinText.records != null) { foreach (var record in interlinText.records) @@ -1257,11 +1249,12 @@ private static void SaveLink(string obj, string propName, string value, Dictiona /// private static void SaveLinks(Interlineartext.Record record, Dictionary> valueProperties) { - if (record.link != null) + if (record.item != null) { - for (int i = 0; i < record.link.Length; i++) + for (int i = 0; i < record.item.Length; i++) { - SaveLink(record.guid, record.link[i].type, record.link[i].Value, valueProperties); + if (record.item[i].guid != null) + SaveLink(record.guid, record.item[i].type, record.item[i].guid, valueProperties); } } } diff --git a/Src/LexText/Interlinear/FlexInterlinModel/FlexInterlinear.cs b/Src/LexText/Interlinear/FlexInterlinModel/FlexInterlinear.cs index 7564e439b8..6408721f95 100644 --- a/Src/LexText/Interlinear/FlexInterlinModel/FlexInterlinear.cs +++ b/Src/LexText/Interlinear/FlexInterlinModel/FlexInterlinear.cs @@ -111,8 +111,6 @@ public partial class Interlineartext private item[] itemField; - private link[] linkField; - private Record[] recordsField; private Paragraph[] paragraphsField; @@ -143,20 +141,6 @@ public item[] Items } } - /// - [System.Xml.Serialization.XmlElementAttribute("link", IsNullable = true)] - public link[] Links - { - get - { - return this.linkField; - } - set - { - this.linkField = value; - } - } - /// [System.Xml.Serialization.XmlArrayAttribute(Form = System.Xml.Schema.XmlSchemaForm.Unqualified)] [System.Xml.Serialization.XmlArrayItemAttribute("record", Form = System.Xml.Schema.XmlSchemaForm.Unqualified, IsNullable = false)] @@ -172,49 +156,6 @@ public Record[] records } } - /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "4.8.3928.0")] - [System.SerializableAttribute()] - [System.Diagnostics.DebuggerStepThroughAttribute()] - [System.ComponentModel.DesignerCategoryAttribute("code")] - [System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true)] - [System.Xml.Serialization.XmlRootAttribute(Namespace = "", IsNullable = true)] - public partial class link - { - - private string typeField; - - private string valueField; - - /// - [System.Xml.Serialization.XmlAttributeAttribute()] - public string type - { - get - { - return this.typeField; - } - set - { - this.typeField = value; - } - } - - /// - [System.Xml.Serialization.XmlTextAttribute()] - public string Value - { - get - { - return this.valueField; - } - set - { - this.valueField = value; - } - } - } - /// [System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "4.8.3928.0")] [System.SerializableAttribute()] @@ -226,8 +167,6 @@ public partial class Record private item[] itemField; - private link[] linkField; - private string guidField; private string typeField; @@ -246,20 +185,6 @@ public item[] item } } - /// - [System.Xml.Serialization.XmlElementAttribute("link", IsNullable = true)] - public link[] link - { - get - { - return this.linkField; - } - set - { - this.linkField = value; - } - } - /// [System.Xml.Serialization.XmlAttributeAttribute()] public string guid @@ -399,6 +324,8 @@ public partial class item private string typeField; + private string guidField; + private string langField; private analysisStatusTypes analysisStatusField; @@ -435,6 +362,20 @@ public string lang } } + /// + [System.Xml.Serialization.XmlAttributeAttribute()] + public string guid + { + get + { + return this.guidField; + } + set + { + this.guidField = value; + } + } + /// [System.Xml.Serialization.XmlAttributeAttribute()] public analysisStatusTypes analysisStatus diff --git a/Src/LexText/Interlinear/ITextDllTests/BIRDFormatImportTests.cs b/Src/LexText/Interlinear/ITextDllTests/BIRDFormatImportTests.cs index 863834e977..3bb92b1530 100644 --- a/Src/LexText/Interlinear/ITextDllTests/BIRDFormatImportTests.cs +++ b/Src/LexText/Interlinear/ITextDllTests/BIRDFormatImportTests.cs @@ -821,16 +821,18 @@ public void TestGenres() string genre2Name = "genre2"; string genre3Name = "genre3"; //an interliner text example xml string - string xml = "" + - "" + title + "" + - "" + genre1Guid + "" + - "" + genre3Guid + "" + - "" + - "" + genre1Name + "" + - "" + genre2Name + "" + - "" + genre3Name + "" + - "" + genre2Guid + "" + "" + - "" + + string xml = "\n" + + "" + title + "\n" + + "" + genre1Name + "\n" + + "" + "\n" + + "\n" + + "" + genre1Name + "\n" + + "" + genre2Name + "\n" + + "" + + "" + genre3Name + "\n" + + "" + genre2Name + "\n" + + "\n" + + "\n" + "" + "1 Musical" + "origem: mary poppins" + diff --git a/Src/LexText/Interlinear/ITextDllTests/InterlinearExporterTests.cs b/Src/LexText/Interlinear/ITextDllTests/InterlinearExporterTests.cs index 602fb8e417..bd3e5c3809 100644 --- a/Src/LexText/Interlinear/ITextDllTests/InterlinearExporterTests.cs +++ b/Src/LexText/Interlinear/ITextDllTests/InterlinearExporterTests.cs @@ -1128,7 +1128,7 @@ public void ValidateMultipleGenres() m_text1.GenresRC.Add(genre1); m_text1.GenresRC.Add(genre2); XmlDocument exportedDoc = ExportToXml(); - AssertThatXmlIn.Dom(exportedDoc).HasSpecifiedNumberOfMatchesForXpath("//interlinear-text/link[@type=\"genre\"]", 2); + AssertThatXmlIn.Dom(exportedDoc).HasSpecifiedNumberOfMatchesForXpath("//interlinear-text/item[@type=\"genre\"]", 2); AssertThatXmlIn.Dom(exportedDoc).HasSpecifiedNumberOfMatchesForXpath("//interlinear-text/records/record[@type=\"Possibility\"]", 2); } diff --git a/Src/LexText/Interlinear/InterlinearExporter.cs b/Src/LexText/Interlinear/InterlinearExporter.cs index cb50aea35b..f9995c35da 100644 --- a/Src/LexText/Interlinear/InterlinearExporter.cs +++ b/Src/LexText/Interlinear/InterlinearExporter.cs @@ -2,6 +2,7 @@ // This software is licensed under the LGPL, version 2.1 or later // (http://www.gnu.org/licenses/lgpl-2.1.html) +using Gecko.WebIDL; using SIL.FieldWorks.Common.RootSites; using SIL.FieldWorks.Common.ViewsInterfaces; using SIL.LCModel; @@ -557,15 +558,26 @@ private void OpenItem(string itemType) } /// - /// Write an link to an object. + /// Write a link to an object. /// private void WritePendingLink(string linkType, ICmObject obj) { if (obj == null) return; - m_writer.WriteStartElement("link"); + m_writer.WriteStartElement("item"); m_writer.WriteAttributeString("type", linkType); - m_writer.WriteString(obj.Guid.ToString()); + m_writer.WriteAttributeString("guid", obj.Guid.ToString()); + // Include name in case the guid isn't defined. + ITsString name; + if (obj is ICmPossibility possibility) + { + name = possibility.Name.BestAnalysisVernacularAlternative; + } + else + { + name = TsStringUtils.EmptyString(m_cache.DefaultAnalWs); + } + WriteLangAndContent(GetWsFromTsString(name), name); m_writer.WriteEndElement(); pendingRecords.Enqueue(obj); } From e5f5a1bc9a59fd7130343513eaf33ffd3ee13455 Mon Sep 17 00:00:00 2001 From: John Maxwell Date: Mon, 6 Oct 2025 16:25:21 -0700 Subject: [PATCH 10/18] Rename to --- .../Interlinear/FlexInterlinear.xsd | 12 ++-- .../Interlinear/BIRDInterlinearImporter.cs | 70 +++++++++---------- .../FlexInterlinModel/FlexInterlinear.cs | 12 ++-- Src/LexText/Interlinear/ITextDll.csproj | 2 +- .../ITextDllTests/BIRDFormatImportTests.cs | 12 ++-- .../ITextDllTests/InterlinearExporterTests.cs | 2 +- .../Interlinear/InterlinearExporter.cs | 41 ++++++----- ...linearRecords.cs => InterlinearObjects.cs} | 10 +-- 8 files changed, 80 insertions(+), 81 deletions(-) rename Src/LexText/Interlinear/{InterlinearRecords.cs => InterlinearObjects.cs} (91%) diff --git a/DistFiles/Language Explorer/Export Templates/Interlinear/FlexInterlinear.xsd b/DistFiles/Language Explorer/Export Templates/Interlinear/FlexInterlinear.xsd index f70b84e36b..3cc6071201 100644 --- a/DistFiles/Language Explorer/Export Templates/Interlinear/FlexInterlinear.xsd +++ b/DistFiles/Language Explorer/Export Templates/Interlinear/FlexInterlinear.xsd @@ -7,16 +7,16 @@ - + - + - + @@ -169,7 +169,7 @@ - + @@ -177,8 +177,8 @@ - - + + diff --git a/Src/LexText/Interlinear/BIRDInterlinearImporter.cs b/Src/LexText/Interlinear/BIRDInterlinearImporter.cs index eaf4f2868a..ec5890e71b 100644 --- a/Src/LexText/Interlinear/BIRDInterlinearImporter.cs +++ b/Src/LexText/Interlinear/BIRDInterlinearImporter.cs @@ -1174,21 +1174,21 @@ private static void SetTextMetaAndMergeMedia(LcmCache cache, Interlineartext int } } - // Process links and records. - InterlinearRecords records = new InterlinearRecords(); + // Process links and objects. + InterlinearObjects objects = new InterlinearObjects(); - if (interlinText.records != null) + if (interlinText.objects != null) { - foreach (var record in interlinText.records) + foreach (var obj in interlinText.objects) { - SaveLinks(record, valueProperties); + SaveLinks(obj, valueProperties); } - foreach (var record in interlinText.records) + foreach (var obj in interlinText.objects) { - CreateRecord(record, valueProperties, records, cache); + CreateFullObject(obj, valueProperties, objects, cache); } } - CreateLinks(valueProperties, records, cache); + CreateLinks(valueProperties, objects, cache); if (interlinText.mediafiles != null) @@ -1245,63 +1245,63 @@ private static void SaveLink(string obj, string propName, string value, Dictiona } /// - /// Save all links from record to values in valueProperties. + /// Save all links from obj to values in valueProperties. /// - private static void SaveLinks(Interlineartext.Record record, Dictionary> valueProperties) + private static void SaveLinks(Interlineartext.Object obj, Dictionary> valueProperties) { - if (record.item != null) + if (obj.item != null) { - for (int i = 0; i < record.item.Length; i++) + for (int i = 0; i < obj.item.Length; i++) { - if (record.item[i].guid != null) - SaveLink(record.guid, record.item[i].type, record.item[i].guid, valueProperties); + if (obj.item[i].guid != null) + SaveLink(obj.guid, obj.item[i].type, obj.item[i].guid, valueProperties); } } } /// - /// Create record and fill in item properties. + /// Create object and fill in item properties. /// - private static void CreateRecord(Interlineartext.Record record, Dictionary> valueProperties, InterlinearRecords records, LcmCache cache) + private static void CreateFullObject(Interlineartext.Object obj, Dictionary> valueProperties, InterlinearObjects objects, LcmCache cache) { - if (!valueProperties.Keys.Contains(record.guid)) + if (!valueProperties.Keys.Contains(obj.guid)) return; - Guid guid = new Guid(record.guid); + Guid guid = new Guid(obj.guid); ICmObjectRepository repository = cache.ServiceLocator.GetInstance(); - if (!repository.TryGetObject(guid, out ICmObject obj)) + if (!repository.TryGetObject(guid, out ICmObject icmObject)) { - obj = CreateRecordObject(record, valueProperties, records, cache); - Type objType = obj.GetType(); + icmObject = CreateObject(obj, valueProperties, objects, cache); + Type objType = icmObject.GetType(); /// Set item properties. - Dictionary xmlPropertyMap = records.GetXmlPropertyMap(record.type); - foreach (var item in record.item) + Dictionary xmlPropertyMap = objects.GetXmlPropertyMap(obj.type); + foreach (var item in obj.item) { object value = null; PropertyInfo propInfo = objType.GetProperty(xmlPropertyMap[item.type]); - object currentValue = propInfo.GetValue(obj, null); + object currentValue = propInfo.GetValue(icmObject, null); if (currentValue is IMultiUnicode) { // value is an ITsString. int ws = GetWsEngine(cache.WritingSystemFactory, item.lang).Handle; value = TsStringUtils.MakeString(item.Value, ws); } - SetPropertyValue(obj, xmlPropertyMap[item.type], value); + SetPropertyValue(icmObject, xmlPropertyMap[item.type], value); } } } /// - /// Create record. + /// Create object. /// - private static ICmObject CreateRecordObject(Interlineartext.Record record, Dictionary> valueProperties, InterlinearRecords records, LcmCache cache) + private static ICmObject CreateObject(Interlineartext.Object obj, Dictionary> valueProperties, InterlinearObjects objects, LcmCache cache) { - string recordType = records.XmlTypeMap[record.type]; - switch (recordType) + string objType = objects.XmlTypeMap[obj.type]; + switch (objType) { case "CmPossibility": - ICmPossibility possibility = cache.ServiceLocator.GetInstance().Create(new Guid(record.guid)); - AddPossibilityToOwner(possibility, record.guid, valueProperties, cache); + ICmPossibility possibility = cache.ServiceLocator.GetInstance().Create(new Guid(obj.guid)); + AddPossibilityToOwner(possibility, obj.guid, valueProperties, cache); return possibility; } return null; @@ -1334,13 +1334,13 @@ private static void AddPossibilityToOwner(ICmPossibility possibility, string gui /// /// Create links. /// - private static void CreateLinks(Dictionary> valueProperties, InterlinearRecords records, LcmCache cache) + private static void CreateLinks(Dictionary> valueProperties, InterlinearObjects objects, LcmCache cache) { foreach (var valGuid in valueProperties.Keys) { foreach (var property in valueProperties[valGuid]) { - CreateLink(property.Object, property.PropName, valGuid, records, cache); + CreateLink(property.Object, property.PropName, valGuid, objects, cache); } } } @@ -1348,12 +1348,12 @@ private static void CreateLinks(Dictionary> valueProperti /// /// Create given link. /// - private static void CreateLink(string objGuid, string propName, string valueGuid, InterlinearRecords records, LcmCache cache) + private static void CreateLink(string objGuid, string propName, string valueGuid, InterlinearObjects objects, LcmCache cache) { ICmObjectRepository repository = cache.ServiceLocator.GetInstance(); ICmObject obj = repository.GetObject(new Guid(objGuid)); ICmObject value = repository.GetObject(new Guid(valueGuid)); - Dictionary xmlPropertyMap = records.InvertMap(records.GetPropertyMap(obj.GetType().Name)); + Dictionary xmlPropertyMap = objects.InvertMap(objects.GetPropertyMap(obj.GetType().Name)); SetPropertyValue(obj, xmlPropertyMap[propName], value); } diff --git a/Src/LexText/Interlinear/FlexInterlinModel/FlexInterlinear.cs b/Src/LexText/Interlinear/FlexInterlinModel/FlexInterlinear.cs index 6408721f95..a48bdb18be 100644 --- a/Src/LexText/Interlinear/FlexInterlinModel/FlexInterlinear.cs +++ b/Src/LexText/Interlinear/FlexInterlinModel/FlexInterlinear.cs @@ -111,7 +111,7 @@ public partial class Interlineartext private item[] itemField; - private Record[] recordsField; + private Object[] objectsField; private Paragraph[] paragraphsField; @@ -143,16 +143,16 @@ public item[] Items /// [System.Xml.Serialization.XmlArrayAttribute(Form = System.Xml.Schema.XmlSchemaForm.Unqualified)] - [System.Xml.Serialization.XmlArrayItemAttribute("record", Form = System.Xml.Schema.XmlSchemaForm.Unqualified, IsNullable = false)] - public Record[] records + [System.Xml.Serialization.XmlArrayItemAttribute("object", Form = System.Xml.Schema.XmlSchemaForm.Unqualified, IsNullable = false)] + public Object[] objects { get { - return this.recordsField; + return this.objectsField; } set { - this.recordsField = value; + this.objectsField = value; } } @@ -162,7 +162,7 @@ public Record[] records [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] [System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true)] - public partial class Record + public partial class Object { private item[] itemField; diff --git a/Src/LexText/Interlinear/ITextDll.csproj b/Src/LexText/Interlinear/ITextDll.csproj index 22f10a31b6..f8bc92779d 100644 --- a/Src/LexText/Interlinear/ITextDll.csproj +++ b/Src/LexText/Interlinear/ITextDll.csproj @@ -460,7 +460,7 @@ InterlinDocForAnalysis.cs - + Form diff --git a/Src/LexText/Interlinear/ITextDllTests/BIRDFormatImportTests.cs b/Src/LexText/Interlinear/ITextDllTests/BIRDFormatImportTests.cs index 3bb92b1530..c9e921941e 100644 --- a/Src/LexText/Interlinear/ITextDllTests/BIRDFormatImportTests.cs +++ b/Src/LexText/Interlinear/ITextDllTests/BIRDFormatImportTests.cs @@ -825,14 +825,14 @@ public void TestGenres() "" + title + "\n" + "" + genre1Name + "\n" + "" + "\n" + - "\n" + - "" + genre1Name + "\n" + - "" + genre2Name + "\n" + - "" + + "\n" + + "" + genre1Name + "\n" + + "" + genre2Name + "\n" + + "" + "" + genre3Name + "\n" + "" + genre2Name + "\n" + - "\n" + - "\n" + + "\n" + + "\n" + "" + "1 Musical" + "origem: mary poppins" + diff --git a/Src/LexText/Interlinear/ITextDllTests/InterlinearExporterTests.cs b/Src/LexText/Interlinear/ITextDllTests/InterlinearExporterTests.cs index bd3e5c3809..459edcf434 100644 --- a/Src/LexText/Interlinear/ITextDllTests/InterlinearExporterTests.cs +++ b/Src/LexText/Interlinear/ITextDllTests/InterlinearExporterTests.cs @@ -1129,7 +1129,7 @@ public void ValidateMultipleGenres() m_text1.GenresRC.Add(genre2); XmlDocument exportedDoc = ExportToXml(); AssertThatXmlIn.Dom(exportedDoc).HasSpecifiedNumberOfMatchesForXpath("//interlinear-text/item[@type=\"genre\"]", 2); - AssertThatXmlIn.Dom(exportedDoc).HasSpecifiedNumberOfMatchesForXpath("//interlinear-text/records/record[@type=\"Possibility\"]", 2); + AssertThatXmlIn.Dom(exportedDoc).HasSpecifiedNumberOfMatchesForXpath("//interlinear-text/objects/object[@type=\"Possibility\"]", 2); } [Test] diff --git a/Src/LexText/Interlinear/InterlinearExporter.cs b/Src/LexText/Interlinear/InterlinearExporter.cs index f9995c35da..62394c2ad8 100644 --- a/Src/LexText/Interlinear/InterlinearExporter.cs +++ b/Src/LexText/Interlinear/InterlinearExporter.cs @@ -2,7 +2,6 @@ // This software is licensed under the LGPL, version 2.1 or later // (http://www.gnu.org/licenses/lgpl-2.1.html) -using Gecko.WebIDL; using SIL.FieldWorks.Common.RootSites; using SIL.FieldWorks.Common.ViewsInterfaces; using SIL.LCModel; @@ -48,7 +47,7 @@ public class InterlinearExporter : CollectorEnv DateTime pendingDateCreated = DateTime.MinValue; DateTime pendingDateModified = DateTime.MinValue; ILcmReferenceCollection pendingGenres; - Queue pendingRecords = new Queue(); + Queue pendingObjects = new Queue(); int m_flidStTextTitle; int m_flidStTextSource; InterlinVc m_vc = null; @@ -59,7 +58,7 @@ public class InterlinearExporter : CollectorEnv IMoMorphType m_mmtProclitic; protected WritingSystemManager m_wsManager; protected ICmObjectRepository m_repoObj; - InterlinearRecords m_records; + InterlinearObjects m_objects; public static InterlinearExporter Create(string mode, LcmCache cache, XmlWriter writer, ICmObject objRoot, InterlinLineChoices lineChoices, InterlinVc vc) @@ -100,7 +99,7 @@ protected InterlinearExporter(LcmCache cache, XmlWriter writer, ICmObject objRoo m_wsManager = m_cache.ServiceLocator.WritingSystemManager; m_repoObj = m_cache.ServiceLocator.GetInstance(); - m_records = new InterlinearRecords(); + m_objects = new InterlinearObjects(); } public void ExportDisplay() @@ -579,26 +578,26 @@ private void WritePendingLink(string linkType, ICmObject obj) } WriteLangAndContent(GetWsFromTsString(name), name); m_writer.WriteEndElement(); - pendingRecords.Enqueue(obj); + pendingObjects.Enqueue(obj); } /// - /// Write an object out as a record. + /// Write an ICmObject as an object. /// - private void WritePendingRecord(ICmObject record) + private void WritePendingObject(ICmObject obj) { - Type recordType = record.GetType(); - string typeName = recordType.Name; - if (!m_records.TypeMap.ContainsKey(typeName)) + Type objType = obj.GetType(); + string typeName = objType.Name; + if (!m_objects.TypeMap.ContainsKey(typeName)) return; - m_writer.WriteStartElement("record"); - m_writer.WriteAttributeString("type", m_records.TypeMap[typeName]); - m_writer.WriteAttributeString("guid", record.Guid.ToString()); - Dictionary propertyMap = m_records.GetPropertyMap(typeName); + m_writer.WriteStartElement("object"); + m_writer.WriteAttributeString("type", m_objects.TypeMap[typeName]); + m_writer.WriteAttributeString("guid", obj.Guid.ToString()); + Dictionary propertyMap = m_objects.GetPropertyMap(typeName); foreach (string propName in propertyMap.Keys) { - PropertyInfo property = recordType.GetProperty(propName); - object value = property.GetValue(record, null); + PropertyInfo property = objType.GetProperty(propName); + object value = property.GetValue(obj, null); WritePendingProperty(propertyMap[propName], value); } m_writer.WriteEndElement(); @@ -700,12 +699,12 @@ public override void AddObjVecItems(int tag, IVwViewConstructor vc, int frag) { WritePendingLink("genre", genre); } - if (pendingRecords.Count > 0) + if (pendingObjects.Count > 0) { - m_writer.WriteStartElement("records"); - while (pendingRecords.Count > 0) + m_writer.WriteStartElement("objects"); + while (pendingObjects.Count > 0) { - WritePendingRecord(pendingRecords.Dequeue()); + WritePendingObject(pendingObjects.Dequeue()); } m_writer.WriteEndElement(); } @@ -907,7 +906,7 @@ private void SetTextTitleAndMetadata(IStText txt) pendingDateModified = text.DateModified; pendingGenres = text.GenresRC; if (text.AssociatedNotebookRecord != null) - pendingRecords.Enqueue(text.AssociatedNotebookRecord); + pendingObjects.Enqueue(text.AssociatedNotebookRecord); } else if (TextSource.IsScriptureText(txt)) { diff --git a/Src/LexText/Interlinear/InterlinearRecords.cs b/Src/LexText/Interlinear/InterlinearObjects.cs similarity index 91% rename from Src/LexText/Interlinear/InterlinearRecords.cs rename to Src/LexText/Interlinear/InterlinearObjects.cs index 1a57894e5e..4fd4220556 100644 --- a/Src/LexText/Interlinear/InterlinearRecords.cs +++ b/Src/LexText/Interlinear/InterlinearObjects.cs @@ -9,11 +9,11 @@ namespace SIL.FieldWorks.IText { /// - /// InterlinearRecords provides a mapping between type and property names and XML names - /// for records associated with interlinear texts. The standard mapping is used for export, + /// InterlinearObjects provides a mapping between type and property names and XML names + /// for objects associated with interlinear texts. The standard mapping is used for export, /// and the inverted mapping is used for import. /// - internal class InterlinearRecords + internal class InterlinearObjects { private Dictionary m_typeMap; private Dictionary m_xmlTypeMap; @@ -27,12 +27,12 @@ internal Dictionary TypeMap internal Dictionary XmlTypeMap { get { return m_xmlTypeMap; } } - internal InterlinearRecords() + internal InterlinearObjects() { m_typeMap = new Dictionary { { "CmPossibility", "Possibility" }, - { "RnGenericRec", "Record" } + { "RnGenericRec", "NotebookRecord" } }; m_xmlTypeMap = new Dictionary(); foreach (string type in m_typeMap.Keys) From 088690c5c44288f0938b7895ff59f1aae03df1e6 Mon Sep 17 00:00:00 2001 From: John Maxwell Date: Wed, 8 Oct 2025 07:47:01 -0700 Subject: [PATCH 11/18] Add notebook records --- .../Interlinear/BIRDInterlinearImporter.cs | 95 ++++++++----------- .../ITextDllTests/BIRDFormatImportTests.cs | 26 +++-- .../Interlinear/InterlinearExporter.cs | 41 +++++++- Src/LexText/Interlinear/InterlinearObjects.cs | 51 +++++++++- 4 files changed, 143 insertions(+), 70 deletions(-) diff --git a/Src/LexText/Interlinear/BIRDInterlinearImporter.cs b/Src/LexText/Interlinear/BIRDInterlinearImporter.cs index ec5890e71b..a7f0de6518 100644 --- a/Src/LexText/Interlinear/BIRDInterlinearImporter.cs +++ b/Src/LexText/Interlinear/BIRDInterlinearImporter.cs @@ -1179,10 +1179,6 @@ private static void SetTextMetaAndMergeMedia(LcmCache cache, Interlineartext int if (interlinText.objects != null) { - foreach (var obj in interlinText.objects) - { - SaveLinks(obj, valueProperties); - } foreach (var obj in interlinText.objects) { CreateFullObject(obj, valueProperties, objects, cache); @@ -1244,41 +1240,54 @@ private static void SaveLink(string obj, string propName, string value, Dictiona valueProperties[value].Add(new Property {Object = obj, PropName = propName}); } - /// - /// Save all links from obj to values in valueProperties. - /// - private static void SaveLinks(Interlineartext.Object obj, Dictionary> valueProperties) - { - if (obj.item != null) - { - for (int i = 0; i < obj.item.Length; i++) - { - if (obj.item[i].guid != null) - SaveLink(obj.guid, obj.item[i].type, obj.item[i].guid, valueProperties); - } - } - } - /// /// Create object and fill in item properties. /// private static void CreateFullObject(Interlineartext.Object obj, Dictionary> valueProperties, InterlinearObjects objects, LcmCache cache) { - if (!valueProperties.Keys.Contains(obj.guid)) - return; Guid guid = new Guid(obj.guid); ICmObjectRepository repository = cache.ServiceLocator.GetInstance(); if (!repository.TryGetObject(guid, out ICmObject icmObject)) { - icmObject = CreateObject(obj, valueProperties, objects, cache); + icmObject = CreateObject(obj, objects, cache); Type objType = icmObject.GetType(); /// Set item properties. Dictionary xmlPropertyMap = objects.GetXmlPropertyMap(obj.type); foreach (var item in obj.item) { - + if (item.guid != null) + { + // Save until all objects are created. + SaveLink(obj.guid, item.type, item.guid, valueProperties); + continue; + } + if (item.type == "owner" && icmObject is ICmPossibility possibility) + { + // Add to possibilities list rooted in LanguageProject. + int ws = GetWsEngine(cache.WritingSystemFactory, item.lang).Handle; + ITsString itsString = TsStringUtils.MakeString(item.Value, ws); + foreach (PropertyInfo langPropInfo in cache.LanguageProject.GetType().GetProperties()) + { + var propValue = langPropInfo.GetValue(cache.LanguageProject); + if (propValue is ICmPossibilityList possibilityList) + { + if (possibilityList.ChooserNameTS.Text == itsString.Text) + { + possibilityList.PossibilitiesOS.Add(possibility); + } + } + } + continue; + } object value = null; - PropertyInfo propInfo = objType.GetProperty(xmlPropertyMap[item.type]); + string propName = null; + if (xmlPropertyMap.ContainsKey(item.type)) + propName = xmlPropertyMap[item.type]; + else if (item.type == "date-created") + propName = "DateCreated"; + else if (item.type == "date-modified") + propName = "DateModified"; + PropertyInfo propInfo = objType.GetProperty(propName); object currentValue = propInfo.GetValue(icmObject, null); if (currentValue is IMultiUnicode) { @@ -1286,7 +1295,7 @@ private static void CreateFullObject(Interlineartext.Object obj, Dictionary /// Create object. /// - private static ICmObject CreateObject(Interlineartext.Object obj, Dictionary> valueProperties, InterlinearObjects objects, LcmCache cache) + private static ICmObject CreateObject(Interlineartext.Object obj, InterlinearObjects objects, LcmCache cache) { string objType = objects.XmlTypeMap[obj.type]; switch (objType) { case "CmPossibility": ICmPossibility possibility = cache.ServiceLocator.GetInstance().Create(new Guid(obj.guid)); - AddPossibilityToOwner(possibility, obj.guid, valueProperties, cache); return possibility; } return null; } - private static void AddPossibilityToOwner(ICmPossibility possibility, string guid, Dictionary> valueProperties, LcmCache cache) - { - // Determine the type of possibility based on the property that points to it. - foreach (var property in valueProperties[guid]) - { - switch (property.PropName) - { - case "genre": - cache.LanguageProject.GenreListOA.PossibilitiesOS.Add(possibility); - return; - } - } - // Determine the type of the possibility based on the type of the child. - foreach (var property in valueProperties[guid]) - { - switch (property.PropName) - { - case "parent": - AddPossibilityToOwner(possibility, property.Object, valueProperties, cache); - return; - } - } - } - /// /// Create links. /// @@ -1348,13 +1332,14 @@ private static void CreateLinks(Dictionary> valueProperti /// /// Create given link. /// - private static void CreateLink(string objGuid, string propName, string valueGuid, InterlinearObjects objects, LcmCache cache) + private static void CreateLink(string objGuid, string xmlPropName, string valueGuid, InterlinearObjects objects, LcmCache cache) { ICmObjectRepository repository = cache.ServiceLocator.GetInstance(); ICmObject obj = repository.GetObject(new Guid(objGuid)); ICmObject value = repository.GetObject(new Guid(valueGuid)); Dictionary xmlPropertyMap = objects.InvertMap(objects.GetPropertyMap(obj.GetType().Name)); - SetPropertyValue(obj, xmlPropertyMap[propName], value); + string propName = (xmlPropName == "owner") ? "Owner" : xmlPropertyMap[xmlPropName]; + SetPropertyValue(obj, propName, value); } /// @@ -1363,9 +1348,9 @@ private static void SetPropertyValue(ICmObject obj, string propName, object valu { if (value == null) return; - if (propName == "OwningPossibility") + if (propName == "Owner") { - // We store OwningPossibility but set SubPossibilitiesOS. + // We store Owner but set SubPossibilitiesOS. SetPropertyValue((ICmObject)value, "SubPossibilitiesOS", obj); return; } diff --git a/Src/LexText/Interlinear/ITextDllTests/BIRDFormatImportTests.cs b/Src/LexText/Interlinear/ITextDllTests/BIRDFormatImportTests.cs index c9e921941e..b64b6be532 100644 --- a/Src/LexText/Interlinear/ITextDllTests/BIRDFormatImportTests.cs +++ b/Src/LexText/Interlinear/ITextDllTests/BIRDFormatImportTests.cs @@ -821,16 +821,22 @@ public void TestGenres() string genre2Name = "genre2"; string genre3Name = "genre3"; //an interliner text example xml string - string xml = "\n" + - "" + title + "\n" + - "" + genre1Name + "\n" + - "" + "\n" + - "\n" + - "" + genre1Name + "\n" + - "" + genre2Name + "\n" + - "" + - "" + genre3Name + "\n" + - "" + genre2Name + "\n" + + string xml = "" + + "" + title + "" + + "" + genre1Name + "" + + "" + "" + + "" + + "" + + "" + genre1Name + "" + + "GenreList" + + "" + + "" + + "" + genre2Name + "" + + "GenreList" + + "" + + "" + + "" + genre3Name + "" + + "" + genre2Name + "" + "\n" + "\n" + "" + diff --git a/Src/LexText/Interlinear/InterlinearExporter.cs b/Src/LexText/Interlinear/InterlinearExporter.cs index 62394c2ad8..0b26226739 100644 --- a/Src/LexText/Interlinear/InterlinearExporter.cs +++ b/Src/LexText/Interlinear/InterlinearExporter.cs @@ -47,6 +47,7 @@ public class InterlinearExporter : CollectorEnv DateTime pendingDateCreated = DateTime.MinValue; DateTime pendingDateModified = DateTime.MinValue; ILcmReferenceCollection pendingGenres; + ICmObject pendingNotebookRecord = null; Queue pendingObjects = new Queue(); int m_flidStTextTitle; int m_flidStTextSource; @@ -600,6 +601,22 @@ private void WritePendingObject(ICmObject obj) object value = property.GetValue(obj, null); WritePendingProperty(propertyMap[propName], value); } + if (obj is ICmMajorObject majorObj) + { + WritePendingProperty("date-created", majorObj.DateCreated); + WritePendingProperty("date-modified", majorObj.DateCreated); + } + if (obj is ICmPossibility) + { + if (obj.Owner is ICmPossibilityList possibilityList) + { + WritePendingProperty("owner", possibilityList.ChooserNameTS); + } + else + { + WritePendingProperty("owner", obj.Owner); + } + } m_writer.WriteEndElement(); } @@ -633,6 +650,11 @@ private void WritePendingProperty(string propType, object value) var hystericalRaisens = TsStringUtils.MakeString(boolValue ? "true" : "false", m_cache.DefaultAnalWs); WritePendingItem(propType, ref hystericalRaisens); } + else if (value is int intValue) + { + var hystericalRaisens = TsStringUtils.MakeString(intValue.ToString(), m_cache.DefaultAnalWs); + WritePendingItem(propType, ref hystericalRaisens); + } else if (value is DateTime dateTime) { if (dateTime != DateTime.MinValue) @@ -641,6 +663,13 @@ private void WritePendingProperty(string propType, object value) WritePendingItem(propType, ref dateTimeString); } } + else if (value is System.Collections.IEnumerable enumerable) + { + foreach (var item in enumerable) + { + WritePendingProperty(propType, item); + } + } } /// @@ -695,16 +724,23 @@ public override void AddObjVecItems(int tag, IVwViewConstructor vc, int frag) } WritePendingProperty("date-created", pendingDateCreated); WritePendingProperty("date-modified", pendingDateModified); + if (pendingNotebookRecord != null) + WritePendingProperty("notebook-record", pendingNotebookRecord); foreach (var genre in pendingGenres) { WritePendingLink("genre", genre); } if (pendingObjects.Count > 0) { + // Write out any objects that were referenced in an item. + HashSet writtenObjects = new HashSet(); m_writer.WriteStartElement("objects"); while (pendingObjects.Count > 0) { - WritePendingObject(pendingObjects.Dequeue()); + ICmObject pendingObject = pendingObjects.Dequeue(); + if (!writtenObjects.Contains(pendingObject)) + WritePendingObject(pendingObject); + writtenObjects.Add(pendingObject); } m_writer.WriteEndElement(); } @@ -905,8 +941,7 @@ private void SetTextTitleAndMetadata(IStText txt) pendingDateCreated = text.DateCreated; pendingDateModified = text.DateModified; pendingGenres = text.GenresRC; - if (text.AssociatedNotebookRecord != null) - pendingObjects.Enqueue(text.AssociatedNotebookRecord); + pendingNotebookRecord = text.AssociatedNotebookRecord; } else if (TextSource.IsScriptureText(txt)) { diff --git a/Src/LexText/Interlinear/InterlinearObjects.cs b/Src/LexText/Interlinear/InterlinearObjects.cs index 4fd4220556..54e0aea849 100644 --- a/Src/LexText/Interlinear/InterlinearObjects.cs +++ b/Src/LexText/Interlinear/InterlinearObjects.cs @@ -31,8 +31,12 @@ internal InterlinearObjects() { m_typeMap = new Dictionary { + { "CmAnthroItem", "AnthroItem" }, + { "CmLocation", "Location" }, + { "CmPerson", "Person" }, { "CmPossibility", "Possibility" }, - { "RnGenericRec", "NotebookRecord" } + { "RnGenericRec", "NotebookRecord" }, + { "RnRoledPartic", "RoledParticipants" }, }; m_xmlTypeMap = new Dictionary(); foreach (string type in m_typeMap.Keys) @@ -42,12 +46,55 @@ internal InterlinearObjects() m_propertyMaps = new Dictionary> { + ["CmAnthroItem"] = new Dictionary() + { + { "Name", "name" }, + { "Abbreviation", "abbreviation" }, + { "Description", "description" }, + { "ConfidenceRA", "confidence" }, + { "ResearchersRC", "researcher" }, + { "RestrictionsRC", "restriction" }, + { "StatusRA", "status" }, + }, + ["CmLocation"] = new Dictionary() + { + { "Name", "name" }, + { "Abbreviation", "abbreviation" }, + { "Description", "description" }, + }, + ["CmPerson"] = new Dictionary() + { + { "Name", "name" }, + { "Abbreviation", "abbreviation" }, + { "Description", "description" }, + { "ConfidenceRA", "confidence" }, + { "PositionsRC", "position" }, + { "RestrictionsRC", "restriction" }, + { "StatusRA", "status" }, + { "EducationRA", "education" }, + { "Gender", "gender" }, + { "IsResearcher", "is-researcher" }, + { "PlacesOfResidenceRC", "place-of-residence" }, + { "PlaceOfBirthRA", "place-of-birth" }, + }, ["CmPossibility"] = new Dictionary() { { "Name", "name" }, { "Abbreviation", "abbreviation" }, { "Description", "description" }, - { "OwningPossibility", "parent" }, + }, + ["RnGenericRec"] = new Dictionary() + { + { "ResearchersRC", "researcher" }, + { "ParticipantsOC", "roled-participants" }, + { "SourcesRC", "source" }, + { "LocationsRC", "location" }, + { "AnthroCodesRC", "anthro-code" }, + }, + ["RnRoledPartic"] = new Dictionary() + { + { "ParticipantsRC", "participant" }, + { "RoleRA", "role" }, }, ["Text"] = new Dictionary() { From c247b4022efaa522141d28613d0878267ae5baed Mon Sep 17 00:00:00 2001 From: John Maxwell Date: Thu, 9 Oct 2025 08:59:04 -0700 Subject: [PATCH 12/18] Add TestMetadataRoundTrip to test import and export --- .../Interlinear/BIRDInterlinearImporter.cs | 85 ++++++++---- .../FlexTextMetadataImport.flextext | 129 ++++++++++++++++++ .../ITextDllTests/ITextDllTests.csproj | 1 + .../ITextDllTests/InterlinearExporterTests.cs | 59 ++++++-- .../Interlinear/InterlinearExporter.cs | 26 +++- Src/LexText/Interlinear/InterlinearObjects.cs | 1 + Src/LexText/Interlinear/LinguaLinksImport.cs | 11 +- 7 files changed, 275 insertions(+), 37 deletions(-) create mode 100644 Src/LexText/Interlinear/ITextDllTests/FlexTextImport/FlexTextMetadataImport.flextext diff --git a/Src/LexText/Interlinear/BIRDInterlinearImporter.cs b/Src/LexText/Interlinear/BIRDInterlinearImporter.cs index a7f0de6518..58b667fb71 100644 --- a/Src/LexText/Interlinear/BIRDInterlinearImporter.cs +++ b/Src/LexText/Interlinear/BIRDInterlinearImporter.cs @@ -87,13 +87,14 @@ private static bool PopulateTextFromBIRDDoc(ref LCModel.IText newText, TextCreat //handle the header(info or meta) information SetTextMetaAndMergeMedia(cache, interlinText, wsFactory, newText, false); + if (newText.ContentsOA == null) + { + // Create ContentsOA even if there are no paragraphs. + newText.ContentsOA = cache.ServiceLocator.GetInstance().Create(); + } //create all the paragraphs foreach (var paragraph in interlinText.paragraphs) { - if (newText.ContentsOA == null) - { - newText.ContentsOA = cache.ServiceLocator.GetInstance().Create(); - } IStTxtPara newTextPara = newText.ContentsOA.AddNewTextPara(""); int offset = 0; if (paragraph.phrases == null) @@ -1170,6 +1171,9 @@ private static void SetTextMetaAndMergeMedia(LcmCache cache, Interlineartext int case "genre": SaveLink(interlinText.guid.ToString(), item.type, item.guid, valueProperties); break; + case "notebook-record": + SaveLink(interlinText.guid.ToString(), item.type, item.guid, valueProperties); + break; } } } @@ -1263,15 +1267,16 @@ private static void CreateFullObject(Interlineartext.Object obj, Dictionary().Create(new Guid(obj.guid)); + case "CmLocation": + return cache.ServiceLocator.GetInstance().Create(new Guid(obj.guid)); + case "CmPerson": + return cache.ServiceLocator.GetInstance().Create(new Guid(obj.guid)); case "CmPossibility": - ICmPossibility possibility = cache.ServiceLocator.GetInstance().Create(new Guid(obj.guid)); - return possibility; + return cache.ServiceLocator.GetInstance().Create(new Guid(obj.guid)); + case "RnGenericRec": + IRnGenericRec record = cache.ServiceLocator.GetInstance().Create(new Guid(obj.guid)); + cache.LanguageProject.ResearchNotebookOA.RecordsOC.Add(record); + return record; + case "RnRoledPartic": + return cache.ServiceLocator.GetInstance().Create(new Guid(obj.guid)); } return null; } @@ -1354,24 +1382,35 @@ private static void SetPropertyValue(ICmObject obj, string propName, object valu SetPropertyValue((ICmObject)value, "SubPossibilitiesOS", obj); return; } + if (propName == "AssociatedNotebookRecord") + { + SetPropertyValue((ICmObject)value, "TextRA", obj); + return; + } PropertyInfo propInfo = obj.GetType().GetProperty(propName); object currentValue = propInfo.GetValue(obj, null); - if (value.GetType().IsInstanceOfType(currentValue.GetType())) + if (currentValue == null) { propInfo.SetValue(obj, value); + return; } - else if (currentValue is ILcmOwningSequence possibilitySequence) + + Type currentValueType = currentValue.GetType(); + if (value.GetType().IsInstanceOfType(currentValueType)) { - if (value is ICmPossibility possibility) - { - possibilitySequence.Add(possibility); - } + propInfo.SetValue(obj, value); } - else if (currentValue is ILcmReferenceCollection possibilityCollection) + else if (currentValueType.IsGenericType && + (currentValueType.GetGenericTypeDefinition().Name == "LcmOwningCollection`1" || + currentValueType.GetGenericTypeDefinition().Name == "LcmOwningSequence`1" || + currentValueType.GetGenericTypeDefinition().Name == "LcmReferenceCollection`1" || + currentValueType.GetGenericTypeDefinition().Name == "LcmReferenceSequence`")) { - if (value is ICmPossibility possibility) + Type itemType = currentValueType.GetGenericArguments()[0]; + if (itemType.IsAssignableFrom(value.GetType())) { - possibilityCollection.Add(possibility); + var addMethod = currentValueType.GetMethod("Add"); + addMethod?.Invoke(currentValue, new[] { value }); } } else if (currentValue is IMultiString multiString) @@ -1390,7 +1429,7 @@ private static void SetPropertyValue(ICmObject obj, string propName, object valu } else { - throw new Exception(); + propInfo.SetValue(obj, value); } } diff --git a/Src/LexText/Interlinear/ITextDllTests/FlexTextImport/FlexTextMetadataImport.flextext b/Src/LexText/Interlinear/ITextDllTests/FlexTextImport/FlexTextMetadataImport.flextext new file mode 100644 index 0000000000..32679005e0 --- /dev/null +++ b/Src/LexText/Interlinear/ITextDllTests/FlexTextImport/FlexTextMetadataImport.flextext @@ -0,0 +1,129 @@ + + + + Derivation Test + Test of derivations + true + 2024-08-27 16:46:12.972 + 2025-09-30 16:04:20.323 + + Paradigm + Prose + + + John Smith + + + John Smith + California + derivations + + + Paradigm + para + A list of well-formed words, typically arranged according to inflection + Word List + + + Prose + pros + Non-metrical arrangement of the text, typically using the grammar of ordinary speech + Metrical Category + + + John Smith + John + senior citizen + Low + volunteer software developer + No restrictions + Graduate + 1 + false + California + Santa Clara + People + + + John Smith + + + John Smith + Arbitrator + + + California + CA + a US state + Locations + + + derivations + Unknown + Unknown + Pending + AnthroList + + + Word List + wd. lst + A list of words used for language research or documentation + GenreList + + + Metrical Category + met. cat + Genre according to metrical form (versus content) + GenreList + + + Low + Low + ConfidenceLevels + + + volunteer software developer + Positions + + + No restrictions + None + Restrictions + + + Graduate + Grad + Education + + + Santa Clara + California + + + Arbitrator + Arbtr + Roles + + + Unknown + Unk + ConfidenceLevels + + + Unknown + Unk + Restrictions + + + Pending + Pend + Status + + + + + + + + + \ No newline at end of file diff --git a/Src/LexText/Interlinear/ITextDllTests/ITextDllTests.csproj b/Src/LexText/Interlinear/ITextDllTests/ITextDllTests.csproj index ba76083fff..d7cf406c08 100644 --- a/Src/LexText/Interlinear/ITextDllTests/ITextDllTests.csproj +++ b/Src/LexText/Interlinear/ITextDllTests/ITextDllTests.csproj @@ -329,6 +329,7 @@ + diff --git a/Src/LexText/Interlinear/ITextDllTests/InterlinearExporterTests.cs b/Src/LexText/Interlinear/ITextDllTests/InterlinearExporterTests.cs index 459edcf434..0c1d2b5e56 100644 --- a/Src/LexText/Interlinear/ITextDllTests/InterlinearExporterTests.cs +++ b/Src/LexText/Interlinear/ITextDllTests/InterlinearExporterTests.cs @@ -2,21 +2,22 @@ // This software is licensed under the LGPL, version 2.1 or later // (http://www.gnu.org/licenses/lgpl-2.1.html) -using System; -using System.Collections; -using System.IO; -using System.Xml; -using System.Xml.Schema; -using System.Xml.Xsl; using NUnit.Framework; -using SIL.LCModel.Core.Text; -using SIL.LCModel.Core.WritingSystems; -using SIL.LCModel.Core.KernelInterfaces; using SIL.FieldWorks.Common.FwUtils; using SIL.LCModel; +using SIL.LCModel.Core.KernelInterfaces; +using SIL.LCModel.Core.Text; +using SIL.LCModel.Core.WritingSystems; using SIL.LCModel.DomainServices; using SIL.PlatformUtilities; using SIL.TestUtilities; +using System; +using System.Collections; +using System.IO; +using System.Text; +using System.Xml; +using System.Xml.Schema; +using System.Xml.Xsl; namespace SIL.FieldWorks.IText { @@ -55,9 +56,14 @@ public void Exit() protected XmlDocument ExportToXml(string mode) { XmlDocument exportedXml = new XmlDocument(); + var settings = new XmlWriterSettings + { + Encoding = System.Text.Encoding.UTF8, + Indent = true + }; using (var vc = new InterlinVc(Cache)) using (var stream = new MemoryStream()) - using (var writer = new XmlTextWriter(stream, System.Text.Encoding.UTF8)) + using (var writer = XmlWriter.Create(stream, settings)) { vc.LineChoices = m_choices; var exporter = InterlinearExporter.Create(mode, Cache, writer, m_text1.ContentsOA, m_choices, vc); @@ -1132,6 +1138,39 @@ public void ValidateMultipleGenres() AssertThatXmlIn.Dom(exportedDoc).HasSpecifiedNumberOfMatchesForXpath("//interlinear-text/objects/object[@type=\"Possibility\"]", 2); } + [Test] + public void TestMetadataRoundTrip() + { + //an interliner text example xml string + string path = Path.Combine(FwDirectoryFinder.SourceDirectory, @"LexText/Interlinear/ITextDllTests/FlexTextImport"); + string file = Path.Combine(path, "FlexTextMetadataImport.flextext"); + XmlDocument doc = new XmlDocument(); + doc.Load(file); + string xml = doc.OuterXml; + ILgWritingSystemFactory wsFactory = Cache.WritingSystemFactory; + + var writingSystem = wsFactory.get_Engine("es"); + Cache.LanguageProject.AddToCurrentAnalysisWritingSystems((CoreWritingSystemDefinition)writingSystem); + writingSystem = wsFactory.get_Engine("en"); + Cache.LanguageProject.AddToCurrentVernacularWritingSystems((CoreWritingSystemDefinition)writingSystem); + Cache.LanguageProject.GenreListOA = Cache.ServiceLocator.GetInstance().Create(); + Cache.LanguageProject.PositionsOA = Cache.ServiceLocator.GetInstance().Create(); + Cache.LanguageProject.RestrictionsOA = Cache.ServiceLocator.GetInstance().Create(); + Cache.LanguageProject.EducationOA = Cache.ServiceLocator.GetInstance().Create(); + Cache.LanguageProject.RolesOA = Cache.ServiceLocator.GetInstance().Create(); + Cache.LanguageProject.StatusOA = Cache.ServiceLocator.GetInstance().Create(); + LinguaLinksImport li = new LinguaLinksImport(Cache, null, null); + LCModel.IText text = null; + using (var stream = new MemoryStream(Encoding.ASCII.GetBytes(xml.ToCharArray()))) + { + li.ImportInterlinear(new DummyProgressDlg(), stream, 0, ref text); + m_text1 = text; + XmlDocument exportedDoc = ExportToXml("elan"); + string exportedXml = exportedDoc.OuterXml; + Assert.That(exportedXml, Is.EqualTo(xml)); + } + } + [Test] public void ValidateIsTranslated() { diff --git a/Src/LexText/Interlinear/InterlinearExporter.cs b/Src/LexText/Interlinear/InterlinearExporter.cs index 0b26226739..58bd2eb19a 100644 --- a/Src/LexText/Interlinear/InterlinearExporter.cs +++ b/Src/LexText/Interlinear/InterlinearExporter.cs @@ -610,7 +610,7 @@ private void WritePendingObject(ICmObject obj) { if (obj.Owner is ICmPossibilityList possibilityList) { - WritePendingProperty("owner", possibilityList.ChooserNameTS); + WritePendingProperty("owner", GetPossibilityListName(possibilityList)); } else { @@ -620,6 +620,30 @@ private void WritePendingObject(ICmObject obj) m_writer.WriteEndElement(); } + private ITsString GetPossibilityListName(ICmPossibilityList possibilityList) + { + foreach (var propInfo in m_cache.LangProject.GetType().GetProperties()) + { + object propValue = null; + try + { + propValue = propInfo.GetValue(m_cache.LangProject, null); + } + catch (Exception e) + { + + } + if (propValue == possibilityList) + { + string name = propInfo.Name; + if (name.EndsWith("OA")) + name = name.Substring(0, name.Length - 2); + return TsStringUtils.MakeString(name, m_cache.DefaultAnalWs); + } + } + return null; + } + /// /// Write property and value, dispatching on value type. /// diff --git a/Src/LexText/Interlinear/InterlinearObjects.cs b/Src/LexText/Interlinear/InterlinearObjects.cs index 54e0aea849..3da9694895 100644 --- a/Src/LexText/Interlinear/InterlinearObjects.cs +++ b/Src/LexText/Interlinear/InterlinearObjects.cs @@ -99,6 +99,7 @@ internal InterlinearObjects() ["Text"] = new Dictionary() { { "GenresRC", "genre" }, + { "AssociatedNotebookRecord", "notebook-record" }, } }; m_xmlPropertyMaps = new Dictionary>(); diff --git a/Src/LexText/Interlinear/LinguaLinksImport.cs b/Src/LexText/Interlinear/LinguaLinksImport.cs index 57e3e76c2a..f8ca71cd4a 100644 --- a/Src/LexText/Interlinear/LinguaLinksImport.cs +++ b/Src/LexText/Interlinear/LinguaLinksImport.cs @@ -27,6 +27,7 @@ using System.Xml.Serialization; using SIL.LCModel.Core.Text; using SIL.LCModel.Core.KernelInterfaces; +using SIL.LCModel.Application; namespace SIL.FieldWorks.IText @@ -290,9 +291,12 @@ public bool ImportInterlinear(ImportInterlinearOptions options, ref LCModel.ITex firstNewText = null; BIRDDocument doc; int initialProgress = progress.Position; + bool canStartUow = ((IActionHandlerExtensions)m_cache.ActionHandlerAccessor).CanStartUow; try { - m_cache.DomainDataByFlid.BeginNonUndoableTask(); + if (canStartUow) + // Don't BeginNonUndoableTask if we are already in an task. + m_cache.DomainDataByFlid.BeginNonUndoableTask(); progress.Message = ITextStrings.ksInterlinImportPhase1of2; var serializer = new XmlSerializer(typeof(BIRDDocument)); doc = (BIRDDocument)serializer.Deserialize(birdData); @@ -346,7 +350,7 @@ public bool ImportInterlinear(ImportInterlinearOptions options, ref LCModel.ITex } if (!continueMerge) break; - progress.Position = initialProgress + allottedProgress/2 + allottedProgress*step/2/doc.interlineartext.Length; + progress.Position = initialProgress + allottedProgress / 2 + allottedProgress * step / 2 / doc.interlineartext.Length; if (firstNewText == null) firstNewText = newText; @@ -363,7 +367,8 @@ public bool ImportInterlinear(ImportInterlinearOptions options, ref LCModel.ITex } finally { - m_cache.DomainDataByFlid.EndNonUndoableTask(); + if (canStartUow) + m_cache.DomainDataByFlid.EndNonUndoableTask(); } return mergeSucceeded; } From 7138f4318074be6a2c45621b65b53e3b64b3d231 Mon Sep 17 00:00:00 2001 From: John Maxwell Date: Thu, 9 Oct 2025 09:56:07 -0700 Subject: [PATCH 13/18] Replace valueProperties with a second pass --- .../Interlinear/BIRDInterlinearImporter.cs | 49 +++++++------------ 1 file changed, 19 insertions(+), 30 deletions(-) diff --git a/Src/LexText/Interlinear/BIRDInterlinearImporter.cs b/Src/LexText/Interlinear/BIRDInterlinearImporter.cs index 58b667fb71..30de5d3816 100644 --- a/Src/LexText/Interlinear/BIRDInterlinearImporter.cs +++ b/Src/LexText/Interlinear/BIRDInterlinearImporter.cs @@ -1139,8 +1139,6 @@ private static void UpgradeToWordGloss(Word word, ref IAnalysis wordForm) private static void SetTextMetaAndMergeMedia(LcmCache cache, Interlineartext interlinText, ILgWritingSystemFactory wsFactory, LCModel.IText newText, bool merging) { - Dictionary> valueProperties = new Dictionary>(); - if (interlinText.Items != null) // apparently it is null if there are no items. { foreach (var item in interlinText.Items) @@ -1168,12 +1166,6 @@ private static void SetTextMetaAndMergeMedia(LcmCache cache, Interlineartext int case "date-modified": newText.DateModified = DateTime.Parse(item.Value, null, System.Globalization.DateTimeStyles.AssumeUniversal); break; - case "genre": - SaveLink(interlinText.guid.ToString(), item.type, item.guid, valueProperties); - break; - case "notebook-record": - SaveLink(interlinText.guid.ToString(), item.type, item.guid, valueProperties); - break; } } } @@ -1185,11 +1177,19 @@ private static void SetTextMetaAndMergeMedia(LcmCache cache, Interlineartext int { foreach (var obj in interlinText.objects) { - CreateFullObject(obj, valueProperties, objects, cache); + CreateFullObject(obj, objects, cache); } } - CreateLinks(valueProperties, objects, cache); + // Create links after all objects have been created. + CreateItemLinks(interlinText.guid, interlinText.Items, objects, cache); + if (interlinText.objects != null) + { + foreach (var obj in interlinText.objects) + { + CreateItemLinks(obj.guid, obj.item, objects, cache); + } + } if (interlinText.mediafiles != null) { @@ -1234,20 +1234,10 @@ private class Property } - /// - /// Save a link from object to value in valueProperties. - /// - private static void SaveLink(string obj, string propName, string value, Dictionary> valueProperties) - { - if (!valueProperties.ContainsKey(value)) - valueProperties[value] = new List(); - valueProperties[value].Add(new Property {Object = obj, PropName = propName}); - } - /// /// Create object and fill in item properties. /// - private static void CreateFullObject(Interlineartext.Object obj, Dictionary> valueProperties, InterlinearObjects objects, LcmCache cache) + private static void CreateFullObject(Interlineartext.Object obj, InterlinearObjects objects, LcmCache cache) { Guid guid = new Guid(obj.guid); ICmObjectRepository repository = cache.ServiceLocator.GetInstance(); @@ -1261,8 +1251,6 @@ private static void CreateFullObject(Interlineartext.Object obj, Dictionary - /// Create links. - /// - private static void CreateLinks(Dictionary> valueProperties, InterlinearObjects objects, LcmCache cache) + private static void CreateItemLinks(string objGuid, item[] items, InterlinearObjects objects, LcmCache cache) { - foreach (var valGuid in valueProperties.Keys) + if (items != null) { - foreach (var property in valueProperties[valGuid]) + foreach (var item in items) { - CreateLink(property.Object, property.PropName, valGuid, objects, cache); + if (item.guid != null) + { + CreateLink(objGuid, item.type, item.guid, objects, cache); + } } } } + /// /// Create given link. /// From fcbeddf5c8d172c82cc164f29b3080dddcb5a1e0 Mon Sep 17 00:00:00 2001 From: John Maxwell Date: Thu, 9 Oct 2025 11:23:51 -0700 Subject: [PATCH 14/18] Use InterlinearObjects "Text" to import and export metadata --- .../Interlinear/BIRDInterlinearImporter.cs | 150 ++++++++---------- .../FlexTextMetadataImport.flextext | 4 +- .../Interlinear/InterlinearExporter.cs | 41 +++-- Src/LexText/Interlinear/InterlinearObjects.cs | 8 +- 4 files changed, 101 insertions(+), 102 deletions(-) diff --git a/Src/LexText/Interlinear/BIRDInterlinearImporter.cs b/Src/LexText/Interlinear/BIRDInterlinearImporter.cs index 30de5d3816..d9c1dadc53 100644 --- a/Src/LexText/Interlinear/BIRDInterlinearImporter.cs +++ b/Src/LexText/Interlinear/BIRDInterlinearImporter.cs @@ -1139,40 +1139,12 @@ private static void UpgradeToWordGloss(Word word, ref IAnalysis wordForm) private static void SetTextMetaAndMergeMedia(LcmCache cache, Interlineartext interlinText, ILgWritingSystemFactory wsFactory, LCModel.IText newText, bool merging) { - if (interlinText.Items != null) // apparently it is null if there are no items. - { - foreach (var item in interlinText.Items) - { - switch (item.type) - { - case "title": - newText.Name.set_String(GetWsEngine(wsFactory, item.lang).Handle, item.Value); - break; - case "title-abbreviation": - newText.Abbreviation.set_String(GetWsEngine(wsFactory, item.lang).Handle, item.Value); - break; - case "source": - newText.Source.set_String(GetWsEngine(wsFactory, item.lang).Handle, item.Value); - break; - case "comment": - newText.Description.set_String(GetWsEngine(wsFactory, item.lang).Handle, item.Value); - break; - case "text-is-translation": - newText.IsTranslated = (item.Value.ToLower() == "true"); - break; - case "date-created": - newText.DateCreated = DateTime.Parse(item.Value, null, System.Globalization.DateTimeStyles.AssumeUniversal); - break; - case "date-modified": - newText.DateModified = DateTime.Parse(item.Value, null, System.Globalization.DateTimeStyles.AssumeUniversal); - break; - } - } - } - - // Process links and objects. InterlinearObjects objects = new InterlinearObjects(); + // Set top-level metadata properties. + SetObjectPropertyValues(newText, interlinText.Items, objects.GetXmlPropertyMap("Text"), cache); + + // Create objects except for links. if (interlinText.objects != null) { foreach (var obj in interlinText.objects) @@ -1244,64 +1216,78 @@ private static void CreateFullObject(Interlineartext.Object obj, InterlinearObje if (!repository.TryGetObject(guid, out ICmObject icmObject)) { icmObject = CreateObject(obj, objects, cache); - Type objType = icmObject.GetType(); - /// Set item properties. Dictionary xmlPropertyMap = objects.GetXmlPropertyMap(obj.type); - foreach (var item in obj.item) + SetObjectPropertyValues(icmObject, obj.item, xmlPropertyMap, cache); + } + } + + private static void SetObjectPropertyValues(ICmObject icmObject, item[] items, Dictionary xmlPropertyMap, LcmCache cache) + { + if (items == null) return; + Type objType = icmObject.GetType(); + /// Set item properties. + foreach (var item in items) + { + if (item.guid != null) { - if (item.guid != null) - { - continue; - } - if (item.type == "owner" && icmObject is ICmPossibility possibility) + continue; + } + if (item.type == "owner" && icmObject is ICmPossibility possibility) + { + // Add possibility to a possibility list rooted in LanguageProject. + foreach (PropertyInfo langPropInfo in cache.LanguageProject.GetType().GetProperties()) { - // Add possibility to a possibility list rooted in LanguageProject. - foreach (PropertyInfo langPropInfo in cache.LanguageProject.GetType().GetProperties()) + string langPropName = langPropInfo.Name; + if (langPropName.EndsWith("OA")) + langPropName = langPropName.Substring(0, langPropName.Length - 2); + if (langPropName == item.Value) { - string langPropName = langPropInfo.Name; - if (langPropName.EndsWith("OA")) - langPropName = langPropName.Substring(0, langPropName.Length - 2); - if (langPropName == item.Value) + var langPropValue = langPropInfo.GetValue(cache.LanguageProject); + if (langPropValue is ICmPossibilityList possibilityList) { - var langPropValue = langPropInfo.GetValue(cache.LanguageProject); - if (langPropValue is ICmPossibilityList possibilityList) - { - possibilityList.PossibilitiesOS.Add(possibility); - } + possibilityList.PossibilitiesOS.Add(possibility); } } - continue; - } - object value = null; - string propName = null; - if (xmlPropertyMap.ContainsKey(item.type)) - propName = xmlPropertyMap[item.type]; - else if (item.type == "date-created") - propName = "DateCreated"; - else if (item.type == "date-modified") - propName = "DateModified"; - PropertyInfo propInfo = objType.GetProperty(propName); - object currentValue = propInfo.GetValue(icmObject, null); - if (currentValue is IMultiString) - { - // value is an ITsString. - int ws = GetWsEngine(cache.WritingSystemFactory, item.lang).Handle; - value = TsStringUtils.MakeString(item.Value, ws); - } - else if (currentValue is IMultiUnicode) - { - // value is an ITsString. - int ws = GetWsEngine(cache.WritingSystemFactory, item.lang).Handle; - value = TsStringUtils.MakeString(item.Value, ws); - } - else if (currentValue is int) - { - int intValue = 0; - if (int.TryParse(item.Value, out intValue)) - value = intValue; } - SetPropertyValue(icmObject, propName, value); + continue; + } + object value = null; + string propName = null; + if (xmlPropertyMap.ContainsKey(item.type)) + propName = xmlPropertyMap[item.type]; + else if (item.type == "date-created") + propName = "DateCreated"; + else if (item.type == "date-modified") + propName = "DateModified"; + PropertyInfo propInfo = objType.GetProperty(propName); + object currentValue = propInfo.GetValue(icmObject, null); + if (currentValue is bool) + { + value = item.Value.ToLower() == "true"; + } + else if (currentValue is DateTime) + { + value = DateTime.Parse(item.Value, null, System.Globalization.DateTimeStyles.AssumeUniversal); + } + else if (currentValue is IMultiString) + { + // value is an ITsString. + int ws = GetWsEngine(cache.WritingSystemFactory, item.lang).Handle; + value = TsStringUtils.MakeString(item.Value, ws); + } + else if (currentValue is IMultiUnicode) + { + // value is an ITsString. + int ws = GetWsEngine(cache.WritingSystemFactory, item.lang).Handle; + value = TsStringUtils.MakeString(item.Value, ws); + } + else if (currentValue is int) + { + int intValue = 0; + if (int.TryParse(item.Value, out intValue)) + value = intValue; } + SetPropertyValue(icmObject, propName, value); } } @@ -1393,7 +1379,7 @@ private static void SetPropertyValue(ICmObject obj, string propName, object valu (currentValueType.GetGenericTypeDefinition().Name == "LcmOwningCollection`1" || currentValueType.GetGenericTypeDefinition().Name == "LcmOwningSequence`1" || currentValueType.GetGenericTypeDefinition().Name == "LcmReferenceCollection`1" || - currentValueType.GetGenericTypeDefinition().Name == "LcmReferenceSequence`")) + currentValueType.GetGenericTypeDefinition().Name == "LcmReferenceSequence`1")) { Type itemType = currentValueType.GetGenericArguments()[0]; if (itemType.IsAssignableFrom(value.GetType())) diff --git a/Src/LexText/Interlinear/ITextDllTests/FlexTextImport/FlexTextMetadataImport.flextext b/Src/LexText/Interlinear/ITextDllTests/FlexTextImport/FlexTextMetadataImport.flextext index 32679005e0..d17175e00c 100644 --- a/Src/LexText/Interlinear/ITextDllTests/FlexTextImport/FlexTextMetadataImport.flextext +++ b/Src/LexText/Interlinear/ITextDllTests/FlexTextImport/FlexTextMetadataImport.flextext @@ -4,11 +4,11 @@ Derivation Test Test of derivations true - 2024-08-27 16:46:12.972 - 2025-09-30 16:04:20.323 Paradigm Prose + 2024-08-27 16:46:12.972 + 2025-09-30 16:04:20.323 John Smith diff --git a/Src/LexText/Interlinear/InterlinearExporter.cs b/Src/LexText/Interlinear/InterlinearExporter.cs index 58bd2eb19a..e086c2776c 100644 --- a/Src/LexText/Interlinear/InterlinearExporter.cs +++ b/Src/LexText/Interlinear/InterlinearExporter.cs @@ -594,17 +594,34 @@ private void WritePendingObject(ICmObject obj) m_writer.WriteStartElement("object"); m_writer.WriteAttributeString("type", m_objects.TypeMap[typeName]); m_writer.WriteAttributeString("guid", obj.Guid.ToString()); + WritePendingObjectProperties(obj, null); + m_writer.WriteEndElement(); + } + + private void WritePendingObjectProperties(ICmObject obj, IList skipXmlProperties) + { + Type objType = obj.GetType(); + string typeName = objType.Name; Dictionary propertyMap = m_objects.GetPropertyMap(typeName); foreach (string propName in propertyMap.Keys) { PropertyInfo property = objType.GetProperty(propName); object value = property.GetValue(obj, null); - WritePendingProperty(propertyMap[propName], value); + if (value == null) + { + continue; + } + string xmlProperty = propertyMap[propName]; + if (skipXmlProperties != null && skipXmlProperties.Contains(xmlProperty)) + { + continue; + } + WritePendingProperty(xmlProperty, value); } if (obj is ICmMajorObject majorObj) { WritePendingProperty("date-created", majorObj.DateCreated); - WritePendingProperty("date-modified", majorObj.DateCreated); + WritePendingProperty("date-modified", majorObj.DateModified); } if (obj is ICmPossibility) { @@ -617,7 +634,6 @@ private void WritePendingObject(ICmObject obj) WritePendingProperty("owner", obj.Owner); } } - m_writer.WriteEndElement(); } private ITsString GetPossibilityListName(ICmPossibilityList possibilityList) @@ -722,16 +738,13 @@ public override void AddObjVecItems(int tag, IVwViewConstructor vc, int frag) { m_writer.WriteAttributeString("guid", text.Guid.ToString()); } + + // The next few properties can come from text or from the display. foreach (var mTssPendingTitle in pendingTitles) { var hystericalRaisens = mTssPendingTitle; WritePendingItem("title", ref hystericalRaisens); } - foreach (var mTssPendingAbbrev in pendingAbbreviations) - { - var hystericalRaisens = mTssPendingAbbrev; - WritePendingItem("title-abbreviation", ref hystericalRaisens); - } foreach(var source in pendingSources) { var hystericalRaisens = source; @@ -742,18 +755,13 @@ public override void AddObjVecItems(int tag, IVwViewConstructor vc, int frag) var hystericalRaisens = desc; WritePendingItem("comment", ref hystericalRaisens); } + // Only write out IsTranslated if it is true. if (pendingIsTranslated) { WritePendingProperty("text-is-translation", true); } - WritePendingProperty("date-created", pendingDateCreated); - WritePendingProperty("date-modified", pendingDateModified); - if (pendingNotebookRecord != null) - WritePendingProperty("notebook-record", pendingNotebookRecord); - foreach (var genre in pendingGenres) - { - WritePendingLink("genre", genre); - } + IList skipXmlProperties = new List() { "title", "source", "comment", "text-is-translation"}; + WritePendingObjectProperties(text, skipXmlProperties); if (pendingObjects.Count > 0) { // Write out any objects that were referenced in an item. @@ -970,7 +978,6 @@ private void SetTextTitleAndMetadata(IStText txt) else if (TextSource.IsScriptureText(txt)) { pendingTitles.Add(txt.ShortNameTSS); - pendingAbbreviations.Add(null); } } } diff --git a/Src/LexText/Interlinear/InterlinearObjects.cs b/Src/LexText/Interlinear/InterlinearObjects.cs index 3da9694895..ce0b280711 100644 --- a/Src/LexText/Interlinear/InterlinearObjects.cs +++ b/Src/LexText/Interlinear/InterlinearObjects.cs @@ -37,6 +37,7 @@ internal InterlinearObjects() { "CmPossibility", "Possibility" }, { "RnGenericRec", "NotebookRecord" }, { "RnRoledPartic", "RoledParticipants" }, + { "Text", "Text" }, }; m_xmlTypeMap = new Dictionary(); foreach (string type in m_typeMap.Keys) @@ -98,8 +99,13 @@ internal InterlinearObjects() }, ["Text"] = new Dictionary() { - { "GenresRC", "genre" }, + { "Abbreviation", "title-abbreviation" }, { "AssociatedNotebookRecord", "notebook-record" }, + { "Description", "comment" }, + { "GenresRC", "genre" }, + { "IsTranslated", "text-is-translation" }, + { "Name", "title" }, + { "Source", "source" }, } }; m_xmlPropertyMaps = new Dictionary>(); From 46ddde1dcdac35998c28bddb1b7a38a96ad3a95e Mon Sep 17 00:00:00 2001 From: John Maxwell Date: Fri, 10 Oct 2025 08:50:05 -0700 Subject: [PATCH 15/18] Don't store possibilities as objects --- .../Interlinear/BIRDInterlinearImporter.cs | 88 ++++++++++++++++- .../ITextDllTests/BIRDFormatImportTests.cs | 24 +---- .../FlexTextMetadataImport.flextext | 94 ------------------- .../ITextDllTests/InterlinearExporterTests.cs | 1 - .../Interlinear/InterlinearExporter.cs | 3 +- Src/LexText/Interlinear/InterlinearObjects.cs | 41 -------- 6 files changed, 91 insertions(+), 160 deletions(-) diff --git a/Src/LexText/Interlinear/BIRDInterlinearImporter.cs b/Src/LexText/Interlinear/BIRDInterlinearImporter.cs index d9c1dadc53..2c8614c000 100644 --- a/Src/LexText/Interlinear/BIRDInterlinearImporter.cs +++ b/Src/LexText/Interlinear/BIRDInterlinearImporter.cs @@ -1325,7 +1325,7 @@ private static void CreateItemLinks(string objGuid, item[] items, InterlinearObj { if (item.guid != null) { - CreateLink(objGuid, item.type, item.guid, objects, cache); + CreateLink(objGuid, item.type, item.guid, item.lang, item.Value, objects, cache); } } } @@ -1335,16 +1335,98 @@ private static void CreateItemLinks(string objGuid, item[] items, InterlinearObj /// /// Create given link. /// - private static void CreateLink(string objGuid, string xmlPropName, string valueGuid, InterlinearObjects objects, LcmCache cache) + private static void CreateLink(string objGuid, string xmlPropName, string valueGuid, string lang, string valueName, InterlinearObjects objects, LcmCache cache) { ICmObjectRepository repository = cache.ServiceLocator.GetInstance(); ICmObject obj = repository.GetObject(new Guid(objGuid)); - ICmObject value = repository.GetObject(new Guid(valueGuid)); Dictionary xmlPropertyMap = objects.InvertMap(objects.GetPropertyMap(obj.GetType().Name)); string propName = (xmlPropName == "owner") ? "Owner" : xmlPropertyMap[xmlPropName]; + ICmObject value = null; + if (!repository.TryGetObject(new Guid(valueGuid), out value)) + { + value = CreateObjectByName(obj, propName, lang, valueName, valueGuid, cache); + } SetPropertyValue(obj, propName, value); } + private static ICmObject CreateObjectByName(ICmObject obj, string propName, string lang, string valueName, string valueGuid, LcmCache cache) + { + ICmPossibilityList possibilityList = null; + string possibilityType = "ICmPossibility"; + switch (propName) + { + case "AnthroCodesRC": + { + possibilityList = cache.LanguageProject.AnthroListOA; + possibilityType = "ICmAnthroItem"; + break; + } + case "GenresRC": + { + possibilityList = cache.LanguageProject.GenreListOA; + break; + } + case "ParticipantsRC": + case "ResearchersRC": + case "Source": + { + possibilityList = cache.LanguageProject.PeopleOA; + possibilityType = "ICmPerson"; + break; + } + case "RoleRA": + { + possibilityList = cache.LanguageProject.RolesOA; + break; + } + case "LocationsRC": + { + possibilityList = cache.LanguageProject.LocationsOA; + possibilityType = "ICmLocation"; + break; + } + } + if (possibilityList == null) + { + return null; + } + // Look for an existing possibility with the given name. + int ws = GetWsEngine(cache.WritingSystemFactory, lang).Handle; + foreach (var candidate in possibilityList.ReallyReallyAllPossibilities) + { + ITsString name = candidate.Name.BestAnalysisAlternative; + if (name.Text == valueName && name.get_WritingSystemAt(0) == ws) + { + // Should we set the candidate's guid to valueGuid? + return candidate; + } + } + // Create a new possibility. + ICmPossibility newPossibility = null; + Guid guid = new Guid(valueGuid); + switch (possibilityType) + { + case "ICmAnthroItem": + newPossibility = cache.ServiceLocator.GetInstance().Create(guid); + break; + case "ICmLocation": + newPossibility = cache.ServiceLocator.GetInstance().Create(guid); + break; + case "ICmPerson": + newPossibility = cache.ServiceLocator.GetInstance().Create(guid); + break; + case "ICmPossibility": + newPossibility = cache.ServiceLocator.GetInstance().Create(guid); + break; + } + if (newPossibility != null) + { + newPossibility.Name.set_String(ws, valueName); + possibilityList.PossibilitiesOS.Add(newPossibility); + } + return newPossibility; + } + /// /// Set object property to value. private static void SetPropertyValue(ICmObject obj, string propName, object value) diff --git a/Src/LexText/Interlinear/ITextDllTests/BIRDFormatImportTests.cs b/Src/LexText/Interlinear/ITextDllTests/BIRDFormatImportTests.cs index b64b6be532..4d1f0f27dd 100644 --- a/Src/LexText/Interlinear/ITextDllTests/BIRDFormatImportTests.cs +++ b/Src/LexText/Interlinear/ITextDllTests/BIRDFormatImportTests.cs @@ -816,28 +816,14 @@ public void TestGenres() string textGuid = "a122d9bb-2d43-4e4c-b74f-6fe44d1c6cb3"; string genre1Guid = "b405f3c0-58e1-4492-8a40-e955774a6912"; string genre2Guid = "45e6f056-98ac-45d6-858e-59450993f269"; - string genre3Guid = "45e6f056-98ac-45d6-858f-59450993f269"; string genre1Name = "genre1"; string genre2Name = "genre2"; - string genre3Name = "genre3"; //an interliner text example xml string string xml = "" + "" + title + "" + "" + genre1Name + "" + - "" + "" + + "" + genre2Name + "" + "" + - "" + - "" + genre1Name + "" + - "GenreList" + - "" + - "" + - "" + genre2Name + "" + - "GenreList" + - "" + - "" + - "" + genre3Name + "" + - "" + genre2Name + "" + - "\n" + "\n" + "" + "1 Musical" + @@ -862,16 +848,14 @@ public void TestGenres() Assert.AreEqual(2, imported.GenresRC.Count); Assert.AreEqual(genre1Guid, imported.GenresRC.First().Guid.ToString()); Assert.AreEqual(genre1Name, imported.GenresRC.First().Name.BestAnalysisAlternative.Text); - Assert.AreEqual(genre3Guid, imported.GenresRC.Last().Guid.ToString()); - Assert.AreEqual(genre3Name, imported.GenresRC.Last().Name.BestAnalysisAlternative.Text); - Assert.AreEqual(2, imported.Cache.LanguageProject.GenreListOA.PossibilitiesOS.Count); + Assert.AreEqual(genre2Guid, imported.GenresRC.Last().Guid.ToString()); + Assert.AreEqual(genre2Name, imported.GenresRC.Last().Name.BestAnalysisAlternative.Text); ILcmOwningSequence genres = imported.Cache.LanguageProject.GenreListOA.PossibilitiesOS; + Assert.AreEqual(2, genres.Count); Assert.AreEqual(genre1Guid, genres.First().Guid.ToString()); Assert.AreEqual(genre1Name, genres.First().Name.BestAnalysisAlternative.Text); Assert.AreEqual(genre2Guid, genres.Last().Guid.ToString()); Assert.AreEqual(genre2Name, genres.Last().Name.BestAnalysisAlternative.Text); - Assert.AreEqual(genre3Guid, genres.Last().SubPossibilitiesOS.First().Guid.ToString()); - Assert.AreEqual(genre3Name, genres.Last().SubPossibilitiesOS.First().Name.BestAnalysisAlternative.Text); } } } diff --git a/Src/LexText/Interlinear/ITextDllTests/FlexTextImport/FlexTextMetadataImport.flextext b/Src/LexText/Interlinear/ITextDllTests/FlexTextImport/FlexTextMetadataImport.flextext index d17175e00c..790d9c0f35 100644 --- a/Src/LexText/Interlinear/ITextDllTests/FlexTextImport/FlexTextMetadataImport.flextext +++ b/Src/LexText/Interlinear/ITextDllTests/FlexTextImport/FlexTextMetadataImport.flextext @@ -18,32 +18,6 @@ California derivations - - Paradigm - para - A list of well-formed words, typically arranged according to inflection - Word List - - - Prose - pros - Non-metrical arrangement of the text, typically using the grammar of ordinary speech - Metrical Category - - - John Smith - John - senior citizen - Low - volunteer software developer - No restrictions - Graduate - 1 - false - California - Santa Clara - People - John Smith @@ -51,74 +25,6 @@ John Smith Arbitrator - - California - CA - a US state - Locations - - - derivations - Unknown - Unknown - Pending - AnthroList - - - Word List - wd. lst - A list of words used for language research or documentation - GenreList - - - Metrical Category - met. cat - Genre according to metrical form (versus content) - GenreList - - - Low - Low - ConfidenceLevels - - - volunteer software developer - Positions - - - No restrictions - None - Restrictions - - - Graduate - Grad - Education - - - Santa Clara - California - - - Arbitrator - Arbtr - Roles - - - Unknown - Unk - ConfidenceLevels - - - Unknown - Unk - Restrictions - - - Pending - Pend - Status - diff --git a/Src/LexText/Interlinear/ITextDllTests/InterlinearExporterTests.cs b/Src/LexText/Interlinear/ITextDllTests/InterlinearExporterTests.cs index 0c1d2b5e56..e75c733bee 100644 --- a/Src/LexText/Interlinear/ITextDllTests/InterlinearExporterTests.cs +++ b/Src/LexText/Interlinear/ITextDllTests/InterlinearExporterTests.cs @@ -1135,7 +1135,6 @@ public void ValidateMultipleGenres() m_text1.GenresRC.Add(genre2); XmlDocument exportedDoc = ExportToXml(); AssertThatXmlIn.Dom(exportedDoc).HasSpecifiedNumberOfMatchesForXpath("//interlinear-text/item[@type=\"genre\"]", 2); - AssertThatXmlIn.Dom(exportedDoc).HasSpecifiedNumberOfMatchesForXpath("//interlinear-text/objects/object[@type=\"Possibility\"]", 2); } [Test] diff --git a/Src/LexText/Interlinear/InterlinearExporter.cs b/Src/LexText/Interlinear/InterlinearExporter.cs index e086c2776c..52d126e49a 100644 --- a/Src/LexText/Interlinear/InterlinearExporter.cs +++ b/Src/LexText/Interlinear/InterlinearExporter.cs @@ -572,14 +572,15 @@ private void WritePendingLink(string linkType, ICmObject obj) if (obj is ICmPossibility possibility) { name = possibility.Name.BestAnalysisVernacularAlternative; + // Don't store possibility as object. } else { name = TsStringUtils.EmptyString(m_cache.DefaultAnalWs); + pendingObjects.Enqueue(obj); } WriteLangAndContent(GetWsFromTsString(name), name); m_writer.WriteEndElement(); - pendingObjects.Enqueue(obj); } /// diff --git a/Src/LexText/Interlinear/InterlinearObjects.cs b/Src/LexText/Interlinear/InterlinearObjects.cs index ce0b280711..028c787ee4 100644 --- a/Src/LexText/Interlinear/InterlinearObjects.cs +++ b/Src/LexText/Interlinear/InterlinearObjects.cs @@ -31,10 +31,6 @@ internal InterlinearObjects() { m_typeMap = new Dictionary { - { "CmAnthroItem", "AnthroItem" }, - { "CmLocation", "Location" }, - { "CmPerson", "Person" }, - { "CmPossibility", "Possibility" }, { "RnGenericRec", "NotebookRecord" }, { "RnRoledPartic", "RoledParticipants" }, { "Text", "Text" }, @@ -47,43 +43,6 @@ internal InterlinearObjects() m_propertyMaps = new Dictionary> { - ["CmAnthroItem"] = new Dictionary() - { - { "Name", "name" }, - { "Abbreviation", "abbreviation" }, - { "Description", "description" }, - { "ConfidenceRA", "confidence" }, - { "ResearchersRC", "researcher" }, - { "RestrictionsRC", "restriction" }, - { "StatusRA", "status" }, - }, - ["CmLocation"] = new Dictionary() - { - { "Name", "name" }, - { "Abbreviation", "abbreviation" }, - { "Description", "description" }, - }, - ["CmPerson"] = new Dictionary() - { - { "Name", "name" }, - { "Abbreviation", "abbreviation" }, - { "Description", "description" }, - { "ConfidenceRA", "confidence" }, - { "PositionsRC", "position" }, - { "RestrictionsRC", "restriction" }, - { "StatusRA", "status" }, - { "EducationRA", "education" }, - { "Gender", "gender" }, - { "IsResearcher", "is-researcher" }, - { "PlacesOfResidenceRC", "place-of-residence" }, - { "PlaceOfBirthRA", "place-of-birth" }, - }, - ["CmPossibility"] = new Dictionary() - { - { "Name", "name" }, - { "Abbreviation", "abbreviation" }, - { "Description", "description" }, - }, ["RnGenericRec"] = new Dictionary() { { "ResearchersRC", "researcher" }, From 8e8cac1914ed9e977df8f8048870d8801cade57d Mon Sep 17 00:00:00 2001 From: John Maxwell Date: Fri, 10 Oct 2025 09:52:20 -0700 Subject: [PATCH 16/18] Cleanup --- .../Export Templates/Interlinear/FlexInterlinear.xsd | 4 +--- Src/LexText/Interlinear/BIRDInterlinearImporter.cs | 8 -------- .../Interlinear/FlexInterlinModel/FlexInterlinear.cs | 1 - .../FlexTextImport/FlexTextMetadataImport.flextext | 8 ++++---- Src/LexText/Interlinear/InterlinearExporter.cs | 8 -------- Src/LexText/Interlinear/InterlinearObjects.cs | 10 ---------- 6 files changed, 5 insertions(+), 34 deletions(-) diff --git a/DistFiles/Language Explorer/Export Templates/Interlinear/FlexInterlinear.xsd b/DistFiles/Language Explorer/Export Templates/Interlinear/FlexInterlinear.xsd index 3cc6071201..ebcefe7114 100644 --- a/DistFiles/Language Explorer/Export Templates/Interlinear/FlexInterlinear.xsd +++ b/DistFiles/Language Explorer/Export Templates/Interlinear/FlexInterlinear.xsd @@ -171,10 +171,8 @@ - - - + diff --git a/Src/LexText/Interlinear/BIRDInterlinearImporter.cs b/Src/LexText/Interlinear/BIRDInterlinearImporter.cs index 2c8614c000..e71052d0c6 100644 --- a/Src/LexText/Interlinear/BIRDInterlinearImporter.cs +++ b/Src/LexText/Interlinear/BIRDInterlinearImporter.cs @@ -1198,14 +1198,6 @@ private static void SetTextMetaAndMergeMedia(LcmCache cache, Interlineartext int } } - private class Property - { - public string Object { get; set; } - - public string PropName { get; set; } - - } - /// /// Create object and fill in item properties. /// diff --git a/Src/LexText/Interlinear/FlexInterlinModel/FlexInterlinear.cs b/Src/LexText/Interlinear/FlexInterlinModel/FlexInterlinear.cs index a48bdb18be..1e44348471 100644 --- a/Src/LexText/Interlinear/FlexInterlinModel/FlexInterlinear.cs +++ b/Src/LexText/Interlinear/FlexInterlinModel/FlexInterlinear.cs @@ -16,7 +16,6 @@ using System.Diagnostics; using System.Xml.Serialization; -using static System.Windows.Forms.LinkLabel; // // This source code was auto-generated by xsd, Version=2.0.50727.1432. and modified by Jason Naylor Version=1.0 diff --git a/Src/LexText/Interlinear/ITextDllTests/FlexTextImport/FlexTextMetadataImport.flextext b/Src/LexText/Interlinear/ITextDllTests/FlexTextImport/FlexTextMetadataImport.flextext index 790d9c0f35..d79c6efa71 100644 --- a/Src/LexText/Interlinear/ITextDllTests/FlexTextImport/FlexTextMetadataImport.flextext +++ b/Src/LexText/Interlinear/ITextDllTests/FlexTextImport/FlexTextMetadataImport.flextext @@ -11,18 +11,18 @@ 2025-09-30 16:04:20.323 - John Smith + John Smith - John Smith + John Smith California derivations - John Smith + John Smith - John Smith + John Smith Arbitrator diff --git a/Src/LexText/Interlinear/InterlinearExporter.cs b/Src/LexText/Interlinear/InterlinearExporter.cs index 52d126e49a..ce55c24d53 100644 --- a/Src/LexText/Interlinear/InterlinearExporter.cs +++ b/Src/LexText/Interlinear/InterlinearExporter.cs @@ -44,10 +44,6 @@ public class InterlinearExporter : CollectorEnv private List pendingAbbreviations = new List(); List pendingComments = new List(); bool pendingIsTranslated = false; - DateTime pendingDateCreated = DateTime.MinValue; - DateTime pendingDateModified = DateTime.MinValue; - ILcmReferenceCollection pendingGenres; - ICmObject pendingNotebookRecord = null; Queue pendingObjects = new Queue(); int m_flidStTextTitle; int m_flidStTextSource; @@ -971,10 +967,6 @@ private void SetTextTitleAndMetadata(IStText txt) pendingComments.Add(text.Description.get_String(writingSystemId)); } pendingIsTranslated = text.IsTranslated; - pendingDateCreated = text.DateCreated; - pendingDateModified = text.DateModified; - pendingGenres = text.GenresRC; - pendingNotebookRecord = text.AssociatedNotebookRecord; } else if (TextSource.IsScriptureText(txt)) { diff --git a/Src/LexText/Interlinear/InterlinearObjects.cs b/Src/LexText/Interlinear/InterlinearObjects.cs index 028c787ee4..585cf4faab 100644 --- a/Src/LexText/Interlinear/InterlinearObjects.cs +++ b/Src/LexText/Interlinear/InterlinearObjects.cs @@ -96,15 +96,5 @@ internal Dictionary InvertMap(Dictionary propert return invertedPropertyMap; } - internal string HyphenCase(string name) - { - string newName = ""; - for (int i = 0; i < name.Length; i++) - { - newName += (i > 0 && char.IsUpper(name[i])) ? ("-" + char.ToLower(name[i])) : name[i].ToString(); - } - return newName; - } - } } From 33d3bcd4c8932af6773a44ef5b021bdc9893e933 Mon Sep 17 00:00:00 2001 From: John Maxwell Date: Mon, 13 Oct 2025 07:43:38 -0700 Subject: [PATCH 17/18] Bump version number --- .../FlexTextImport/FlexTextMetadataImport.flextext | 2 +- Src/LexText/Interlinear/InterlinearExporter.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Src/LexText/Interlinear/ITextDllTests/FlexTextImport/FlexTextMetadataImport.flextext b/Src/LexText/Interlinear/ITextDllTests/FlexTextImport/FlexTextMetadataImport.flextext index d79c6efa71..56452ede7d 100644 --- a/Src/LexText/Interlinear/ITextDllTests/FlexTextImport/FlexTextMetadataImport.flextext +++ b/Src/LexText/Interlinear/ITextDllTests/FlexTextImport/FlexTextMetadataImport.flextext @@ -1,5 +1,5 @@ - + Derivation Test Test of derivations diff --git a/Src/LexText/Interlinear/InterlinearExporter.cs b/Src/LexText/Interlinear/InterlinearExporter.cs index ce55c24d53..593b63e85a 100644 --- a/Src/LexText/Interlinear/InterlinearExporter.cs +++ b/Src/LexText/Interlinear/InterlinearExporter.cs @@ -983,7 +983,7 @@ private void SetTextTitleAndMetadata(IStText txt) /// public class InterlinearExporterForElan : InterlinearExporter { - private const int kDocVersion = 2; + private const int kDocVersion = 3; protected internal InterlinearExporterForElan(LcmCache cache, XmlWriter writer, ICmObject objRoot, InterlinLineChoices lineChoices, InterlinVc vc) : base(cache, writer, objRoot, lineChoices, vc) From 184ef51d61039d818814b2be06fb916424f23da5 Mon Sep 17 00:00:00 2001 From: John Maxwell Date: Thu, 16 Oct 2025 12:54:25 -0700 Subject: [PATCH 18/18] Replace canStartUow with DoUsingNewOrCurrentUOW --- Src/LexText/Interlinear/LinguaLinksImport.cs | 113 +++++++++---------- 1 file changed, 54 insertions(+), 59 deletions(-) diff --git a/Src/LexText/Interlinear/LinguaLinksImport.cs b/Src/LexText/Interlinear/LinguaLinksImport.cs index f8ca71cd4a..cb91451162 100644 --- a/Src/LexText/Interlinear/LinguaLinksImport.cs +++ b/Src/LexText/Interlinear/LinguaLinksImport.cs @@ -291,73 +291,72 @@ public bool ImportInterlinear(ImportInterlinearOptions options, ref LCModel.ITex firstNewText = null; BIRDDocument doc; int initialProgress = progress.Position; - bool canStartUow = ((IActionHandlerExtensions)m_cache.ActionHandlerAccessor).CanStartUow; + LCModel.IText localFirstNewText = firstNewText; try { - if (canStartUow) - // Don't BeginNonUndoableTask if we are already in an task. - m_cache.DomainDataByFlid.BeginNonUndoableTask(); - progress.Message = ITextStrings.ksInterlinImportPhase1of2; - var serializer = new XmlSerializer(typeof(BIRDDocument)); - doc = (BIRDDocument)serializer.Deserialize(birdData); - Normalize(doc); - int version = 0; - if (!string.IsNullOrEmpty(doc.version)) - int.TryParse(doc.version, out version); - progress.Position = initialProgress + allottedProgress / 2; - progress.Message = ITextStrings.ksInterlinImportPhase2of2; - if (doc.interlineartext != null) + NonUndoableUnitOfWorkHelper.DoUsingNewOrCurrentUOW(m_cache.ServiceLocator.GetInstance(), () => { - int step = 0; - foreach (var interlineartext in doc.interlineartext) + progress.Message = ITextStrings.ksInterlinImportPhase1of2; + var serializer = new XmlSerializer(typeof(BIRDDocument)); + doc = (BIRDDocument)serializer.Deserialize(birdData); + Normalize(doc); + int version = 0; + if (!string.IsNullOrEmpty(doc.version)) + int.TryParse(doc.version, out version); + progress.Position = initialProgress + allottedProgress / 2; + progress.Message = ITextStrings.ksInterlinImportPhase2of2; + if (doc.interlineartext != null) { - step++; - ILangProject langProject = m_cache.LangProject; - LCModel.IText newText = null; - if (!String.IsNullOrEmpty(interlineartext.guid)) + int step = 0; + foreach (var interlineartext in doc.interlineartext) { - ICmObject repoObj; - m_cache.ServiceLocator.ObjectRepository.TryGetObject(new Guid(interlineartext.guid), out repoObj); - newText = repoObj as LCModel.IText; - if (newText != null && ShowPossibleMergeDialog(progress) == DialogResult.Yes) + step++; + ILangProject langProject = m_cache.LangProject; + LCModel.IText newText = null; + if (!String.IsNullOrEmpty(interlineartext.guid)) { - continueMerge = MergeTextWithBIRDDoc(ref newText, - new TextCreationParams - { - Cache = m_cache, - InterlinText = interlineartext, - Progress = progress, - ImportOptions = options, - Version = version - }); - } - else if (newText == null) - { - newText = m_cache.ServiceLocator.GetInstance().Create(m_cache, new Guid(interlineartext.guid)); - continueMerge = PopulateTextIfPossible(options, ref newText, interlineartext, progress, version); + ICmObject repoObj; + m_cache.ServiceLocator.ObjectRepository.TryGetObject(new Guid(interlineartext.guid), out repoObj); + newText = repoObj as LCModel.IText; + if (newText != null && ShowPossibleMergeDialog(progress) == DialogResult.Yes) + { + continueMerge = MergeTextWithBIRDDoc(ref newText, + new TextCreationParams + { + Cache = m_cache, + InterlinText = interlineartext, + Progress = progress, + ImportOptions = options, + Version = version + }); + } + else if (newText == null) + { + newText = m_cache.ServiceLocator.GetInstance().Create(m_cache, new Guid(interlineartext.guid)); + continueMerge = PopulateTextIfPossible(options, ref newText, interlineartext, progress, version); + } + else //user said do not merge. + { + //ignore the Guid; we shouldn't create another text with the same guid + newText = m_cache.ServiceLocator.GetInstance().Create(); + continueMerge = PopulateTextIfPossible(options, ref newText, interlineartext, progress, version); + } } - else //user said do not merge. + else { - //ignore the Guid; we shouldn't create another text with the same guid newText = m_cache.ServiceLocator.GetInstance().Create(); continueMerge = PopulateTextIfPossible(options, ref newText, interlineartext, progress, version); } - } - else - { - newText = m_cache.ServiceLocator.GetInstance().Create(); - continueMerge = PopulateTextIfPossible(options, ref newText, interlineartext, progress, version); - } - if (!continueMerge) - break; - progress.Position = initialProgress + allottedProgress / 2 + allottedProgress * step / 2 / doc.interlineartext.Length; - if (firstNewText == null) - firstNewText = newText; + if (!continueMerge) + break; + progress.Position = initialProgress + allottedProgress / 2 + allottedProgress * step / 2 / doc.interlineartext.Length; + if (localFirstNewText == null) + localFirstNewText = newText; + } + mergeSucceeded = continueMerge; } - mergeSucceeded = continueMerge; - - } + }); } catch (Exception e) { @@ -365,11 +364,7 @@ public bool ImportInterlinear(ImportInterlinearOptions options, ref LCModel.ITex Debug.Print(e.Message); Debug.Print(e.StackTrace); } - finally - { - if (canStartUow) - m_cache.DomainDataByFlid.EndNonUndoableTask(); - } + firstNewText = localFirstNewText; return mergeSucceeded; }