Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -1983,6 +1983,86 @@ public void LiftExportRanges_PartOfSpeechCatalogIdIsExported()
AssertThatXmlIn.Dom(xdocRangeFile).HasAtLeastOneMatchForXpath("//range[@id='grammatical-info']/range-element/trait[@name='catalog-source-id']");
}

///--------------------------------------------------------------------------------------
/// <summary>
/// LT-22697: FLEx holds names decomposed and normalizes them on export. A
/// range-element id must be normalized like the parent attribute naming it and like
/// its own label, or a consumer comparing raw strings cannot resolve the reference.
/// </summary>
///--------------------------------------------------------------------------------------
[Test]
public void LiftExportRanges_PartOfSpeechIdIsNormalizedLikeItsParentAndLabel()
{
const string ksDecomposed = "Comple\u0301ments"; // e + U+0301
const string ksComposed = "Compl\u00e9ments"; // U+00E9
IPartOfSpeech parentPos = null;
IPartOfSpeech childPos = null;
NonUndoableUnitOfWorkHelper.Do(m_cache.ActionHandlerAccessor, () =>
{
var posFactory = m_cache.ServiceLocator.GetInstance<IPartOfSpeechFactory>();
parentPos = posFactory.Create();
m_cache.LangProject.PartsOfSpeechOA.PossibilitiesOS.Add(parentPos);
parentPos.Name.set_String(m_cache.DefaultAnalWs, ksDecomposed);
childPos = posFactory.Create();
parentPos.SubPossibilitiesOS.Add(childPos);
childPos.Name.set_String(m_cache.DefaultAnalWs, "Comple\u0301ment du lieu");
});
var xdocRangeFile = new XmlDocument();
using (var w = new StringWriter())
{
// SUT
new LiftExporter(m_cache).ExportLiftRanges(w);
xdocRangeFile.LoadXml(w.ToString());
}

var parentElement = xdocRangeFile.SelectSingleNode(string.Format(
"//range[@id='grammatical-info']/range-element[@guid='{0}']", parentPos.Guid));
var childElement = xdocRangeFile.SelectSingleNode(string.Format(
"//range[@id='grammatical-info']/range-element[@guid='{0}']", childPos.Guid));
Assert.That(parentElement, Is.Not.Null);
Assert.That(childElement, Is.Not.Null);
var sId = parentElement.Attributes["id"].Value;
Assert.That(sId, Is.EqualTo(ksComposed),
"the id must be normalized, not the decomposed form held in memory");
Assert.That(childElement.Attributes["parent"].Value, Is.EqualTo(sId),
"the parent attribute must match the id it names");
Assert.That(parentElement.SelectSingleNode("label/form/text").InnerText, Is.EqualTo(sId),
"the label must match the id of its own element");
}

///--------------------------------------------------------------------------------------
/// <summary>
/// LT-22697: the morph-type id was written raw, so a name holding a markup character

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The FW team is working on a new policy to keep our comments more concise. The Jira number alone is a sufficient comment.

/// left the whole ranges document unparseable.
/// </summary>
///--------------------------------------------------------------------------------------
[Test]
public void LiftExportRanges_MorphTypeIdWithMarkupCharacterIsEscaped()
{
const string ksName = "prefix & suffix";
IMoMorphType morphType = null;
NonUndoableUnitOfWorkHelper.Do(m_cache.ActionHandlerAccessor, () =>
{
morphType = m_cache.ServiceLocator.GetInstance<IMoMorphTypeFactory>().Create();
m_cache.LangProject.LexDbOA.MorphTypesOA.PossibilitiesOS.Add(morphType);
morphType.Name.set_String(m_cache.DefaultAnalWs, ksName);
});
var xdocRangeFile = new XmlDocument();
using (var w = new StringWriter())
{
// SUT
new LiftExporter(m_cache).ExportLiftRanges(w);
Assert.That(() => xdocRangeFile.LoadXml(w.ToString()), Throws.Nothing,
"an unescaped id leaves the whole ranges document unparseable");
}

var element = xdocRangeFile.SelectSingleNode(string.Format(
"//range[@id='morph-type']/range-element[@guid='{0}']", morphType.Guid));
Assert.That(element, Is.Not.Null);
Assert.That(element.Attributes["id"].Value, Is.EqualTo(ksName),
"the parsed id must be the name as stored, escaping undone");
}

private int m_flidLongText;

private void AddStTextCustomFieldAndData()
Expand Down
16 changes: 8 additions & 8 deletions Src/LexText/LexTextControls/LiftExporter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1735,13 +1735,13 @@ private void WritePartOfSpeechRangeElement(TextWriter w, IPartOfSpeech pos)
{
var liftIdOwner = ((IPartOfSpeech)(pos.Owner)).Name.BestAnalysisVernacularAlternative.Text;
w.WriteLine("<range-element id=\"{0}\" guid=\"{1}\" parent=\"{2}\">",
XmlUtils.MakeSafeXmlAttribute(liftId), pos.Guid,
MakeSafeAndNormalizedAttribute(liftId), pos.Guid,
MakeSafeAndNormalizedAttribute(liftIdOwner));
}
else
{
w.WriteLine("<range-element id=\"{0}\" guid=\"{1}\">",
XmlUtils.MakeSafeXmlAttribute(liftId), pos.Guid);
MakeSafeAndNormalizedAttribute(liftId), pos.Guid);
}
WriteAllForms(w, "label", null, "form", pos.Name);
WriteAllForms(w, "abbrev", null, "form", pos.Abbreviation);
Expand Down Expand Up @@ -1772,13 +1772,13 @@ private void WriteLexRefType(TextWriter w, ILexRefType refer)
{
var liftIdOwner = ((ILexRefType)refer.Owner).Name.BestAnalysisVernacularAlternative.Text;
w.WriteLine("<range-element id=\"{0}\" guid=\"{1}\" parent=\"{2}\">",
XmlUtils.MakeSafeXmlAttribute(liftId), refer.Guid,
MakeSafeAndNormalizedAttribute(liftId), refer.Guid,
MakeSafeAndNormalizedAttribute(liftIdOwner));
}
else
{
w.WriteLine("<range-element id=\"{0}\" guid=\"{1}\">",
XmlUtils.MakeSafeXmlAttribute(liftId), refer.Guid);
MakeSafeAndNormalizedAttribute(liftId), refer.Guid);
}
WriteAllForms(w, "label", null, "form", refer.Name);
WriteAllForms(w, "abbrev", null, "form", refer.Abbreviation);
Expand Down Expand Up @@ -2170,19 +2170,19 @@ void WriteMorphTypeRange(TextWriter w)
var liftId = type.Name.get_String(m_wsEn).Text;
if (String.IsNullOrEmpty(liftId))
liftId = type.Name.BestAnalysisVernacularAlternative.Text;
w.WriteLine("<range-element id=\"{0}\" guid=\"{1}\">", liftId, type.Guid);
w.WriteLine("<range-element id=\"{0}\" guid=\"{1}\">", MakeSafeAndNormalizedAttribute(liftId), type.Guid);
WriteAllForms(w, "label", null, "form", type.Name);
WriteAllForms(w, "abbrev", null, "form", type.Abbreviation);
WriteAllForms(w, "description", null, "form", type.Description);
if (type.Prefix != null)
{
w.WriteLine("<trait name=\"leading-symbol\" value=\"{0}\"/>",
XmlUtils.MakeSafeXmlAttribute(type.Prefix));
MakeSafeAndNormalizedAttribute(type.Prefix));
}
if (type.Postfix != null)
{
w.WriteLine("<trait name=\"trailing-symbol\" value=\"{0}\"/>",
XmlUtils.MakeSafeXmlAttribute(type.Postfix));
MakeSafeAndNormalizedAttribute(type.Postfix));
}
w.WriteLine("</range-element>");
}
Expand Down Expand Up @@ -2291,7 +2291,7 @@ void WriteStemNameRanges(TextWriter w)
foreach (var region in stem.RegionsOC)
{
w.WriteLine("<trait name=\"feature-set\" value=\"{0}\"/>",
XmlUtils.MakeSafeXmlAttribute(region.LiftName));
MakeSafeAndNormalizedAttribute(region.LiftName));
}
w.WriteLine("</range-element>");
}
Expand Down
Loading