Skip to content
Merged
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
4 changes: 2 additions & 2 deletions Src/xWorks/ConfiguredLcmGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2676,7 +2676,7 @@ private static IFragment GenerateContentForSimpleString(List<ConfigurableDiction
{
var writingSystem = GetLanguageFromFirstOptionOrAnalysis(nodeList.Last().DictionaryNodeOptions as
DictionaryNodeWritingSystemOptions, settings.Cache);
var cssClassName = settings.StylesGenerator.AddStyles(nodeList).Trim('.');
var cssClassName = settings.StylesGenerator.AddStyles(nodeList, true).Trim('.');
return settings.ContentGenerator.AddProperty(nodeList, settings, cssClassName, false, simpleString, writingSystem);

}
Expand Down Expand Up @@ -3398,7 +3398,7 @@ internal struct SenseInfo
public interface ILcmStylesGenerator
{
void AddGlobalStyles(DictionaryConfigurationModel model, ReadOnlyPropertyTable propertyTable);
string AddStyles(List<ConfigurableDictionaryNode> nodeList);
string AddStyles(List<ConfigurableDictionaryNode> nodeList, bool addSpanBeforeAfter = false);
void Init(ReadOnlyPropertyTable propertyTable);
}

Expand Down
38 changes: 31 additions & 7 deletions Src/xWorks/CssGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -87,8 +87,13 @@ public void AddGlobalStyles(DictionaryConfigurationModel model, ReadOnlyProperty
/// node.Parent's. Where it will differ is when a node has a reference node. Instead
/// of containing 'node.Parent' it follows the path from where the reference is made.
/// </param>
/// <param name="addSpanBeforeAfter">This bool defaults to false but is set to true when the
/// content of a given node is a simple string. In this case, a writing system span will be
/// added to the style in the xhtml, and the before and after styles need "span" added in order to inherit
/// the properties of that writing system span.
/// </param>
/// <returns>The unique node name for the last node in the list.</returns>
public string AddStyles(List<ConfigurableDictionaryNode> nodeList)
public string AddStyles(List<ConfigurableDictionaryNode> nodeList, bool addSpanBeforeAfter = false)
{
lock (_styleDictionary)
{
Expand All @@ -104,7 +109,7 @@ public string AddStyles(List<ConfigurableDictionaryNode> nodeList)

if (!_styleDictionary.ContainsKey(uniqueNodeName))
{
var styleRules = GenerateCssFromConfigurationNode(workingNode, uniqueNodeName, _propertyTable).NonEmpty();
var styleRules = GenerateCssFromConfigurationNode(workingNode, uniqueNodeName, _propertyTable, addSpanBeforeAfter).NonEmpty();
styleRules = styleRules.Distinct().ToList(); // Remove duplicate rules.
AddUniquePathToStyleRules(styleRules, uniqueNodePath);
_styleDictionary[uniqueNodeName] = styleRules;
Expand Down Expand Up @@ -341,7 +346,7 @@ private static void GenerateCssForAudioWs(StyleSheet styleSheet, LcmCache cache)
/// <summary>
/// Generates css rules for a configuration node and adds them to the given stylesheet (recursive).
/// </summary>
private static List<StyleRule> GenerateCssFromConfigurationNode(ConfigurableDictionaryNode configNode, string baseSelection, ReadOnlyPropertyTable propertyTable)
private static List<StyleRule> GenerateCssFromConfigurationNode(ConfigurableDictionaryNode configNode, string baseSelection, ReadOnlyPropertyTable propertyTable, bool addSpanBeforeAfter = false)
{
var cache = propertyTable.GetValue<LcmCache>("cache");
switch (configNode.DictionaryNodeOptions)
Expand Down Expand Up @@ -375,7 +380,7 @@ private static List<StyleRule> GenerateCssFromConfigurationNode(ConfigurableDict
var rule = new StyleRule();

var selectors = GenerateSelectorsFromNode(configNode, ref baseSelection,
cache, propertyTable);
cache, propertyTable, addSpanBeforeAfter);

var wsOptions = configNode.DictionaryNodeOptions as DictionaryNodeWritingSystemOptions;
if (wsOptions != null)
Expand Down Expand Up @@ -828,7 +833,7 @@ private static List<StyleRule> GenerateCssFromPictureOptions(ConfigurableDiction
/// This method will generate before and after rules if the configuration node requires them. It also generates the selector for the node
/// </summary>
private static List<StyleRule> GenerateSelectorsFromNode(ConfigurableDictionaryNode configNode,
ref string baseSelection, LcmCache cache, ReadOnlyPropertyTable propertyTable)
ref string baseSelection, LcmCache cache, ReadOnlyPropertyTable propertyTable, bool addSpanBeforeAfter = false)
{
var rules = new List<StyleRule>();
var fwStyles = FontHeightAdjuster.StyleSheetFromPropertyTable(propertyTable);
Expand Down Expand Up @@ -937,25 +942,44 @@ private static List<StyleRule> GenerateSelectorsFromNode(ConfigurableDictionaryN
if (!string.IsNullOrEmpty(configNode.Before))
{
var dec = new StyleDeclaration();
StyleRule beforeRule;
dec.Add(new Property("content") { Term = new PrimitiveTerm(UnitType.String, SpecialCharacterHandling.MakeSafeCss(configNode.Before)) });
if (fwStyles != null && fwStyles.Styles.Contains(BeforeAfterBetweenStyleName))
dec.Properties.AddRange(GenerateCssStyleFromLcmStyleSheet(BeforeAfterBetweenStyleName, cache.DefaultAnalWs, propertyTable));
var selectorBase = collectionSelector;
if (configNode.FieldDescription == "PicturesOfSenses")
selectorBase += "> div:first-child";
var beforeRule = new StyleRule(dec) { Value = GetBaseSelectionWithSelectors(selectorBase, ":before") };

// The addSpanBeforeAfter argument indicates whether we need to add a span to the before/after and skip the usual selector formatting.
// This is only needed in the case that we have a writing system unaware property that has had a writing system added via "GenerateContentForSimpleString".
if (addSpanBeforeAfter)
{
beforeRule = new StyleRule(dec) { Value = selectorBase + " span:before" };
}
else
beforeRule = new StyleRule(dec) { Value = GetBaseSelectionWithSelectors(selectorBase, ":before") };
rules.Add(beforeRule);
}
if(!string.IsNullOrEmpty(configNode.After))
{
var dec = new StyleDeclaration();
StyleRule afterRule;
dec.Add(new Property("content") { Term = new PrimitiveTerm(UnitType.String, SpecialCharacterHandling.MakeSafeCss(configNode.After)) });
if (fwStyles != null && fwStyles.Styles.Contains(BeforeAfterBetweenStyleName))
dec.Properties.AddRange(GenerateCssStyleFromLcmStyleSheet(BeforeAfterBetweenStyleName, cache.DefaultAnalWs, propertyTable));
var selectorBase = collectionSelector;
if (configNode.FieldDescription == "PicturesOfSenses")
selectorBase += "> div:last-child";
var afterRule = new StyleRule(dec) { Value = GetBaseSelectionWithSelectors(selectorBase, ":after") };

// The addSpanBeforeAfter argument indicates whether we need to add a span to the before/after and skip the usual selector formatting.
// This is only needed in the case that we have a writing system unaware property that has had a writing system added via "GenerateContentForSimpleString".
if (addSpanBeforeAfter)
{
afterRule = new StyleRule(dec) { Value = selectorBase + " span:after" };
}
else
afterRule = new StyleRule(dec) { Value = GetBaseSelectionWithSelectors(selectorBase, ":after") };

rules.Add(afterRule);
}
return rules;
Expand Down
2 changes: 1 addition & 1 deletion Src/xWorks/LcmWordGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1998,7 +1998,7 @@ public void AddGlobalStyles(DictionaryConfigurationModel model, ReadOnlyProperty
/// the situations where a unique style name is generated, because the reference needs to use the
/// unique name.
/// </summary>
public string AddStyles(List<ConfigurableDictionaryNode> nodeList)
public string AddStyles(List<ConfigurableDictionaryNode> nodeList, bool addSpanBeforeAfter = false)
{
var node = nodeList.Last();
if (WordStylesGenerator.IsParagraphStyle(node.Style, _propertyTable))
Expand Down
9 changes: 9 additions & 0 deletions Src/xWorks/LcmXhtmlGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1101,7 +1101,16 @@ public IFragment AddProperty(List<ConfigurableDictionaryNode> nodeList,
xw.WriteStartElement(isBlockProperty ? "div" : "span");
xw.WriteAttributeString("class", className);
WriteNodeId(xw, nodeList.Last(), settings);
if (writingSystem != null)
{
xw.WriteStartElement("span");
xw.WriteAttributeString("lang", writingSystem);
}
xw.WriteString(content);
if (writingSystem != null)
{
xw.WriteEndElement();
}
xw.WriteEndElement();
xw.Flush();
return fragment;
Expand Down
16 changes: 11 additions & 5 deletions Src/xWorks/xWorksTests/ConfiguredXHTMLGeneratorTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -483,9 +483,15 @@ public void GenerateContentForEntry_HomographNumbersGeneratesCorrectResult()
XHTMLStringBuilder.Append(result);
XHTMLStringBuilder.AppendLine("</TESTWRAPPER>");

var entryWithHomograph = "/TESTWRAPPER/div[@class='lexentry']/span[@class='homographnumber' and text()='1']";
// Normally the propertyvalue for a headword with homograph number is IMultiStringAccessor.
// However, in the test setup the propertyvalue for homograph number is an int
// and therefore hits the int case of GenerateContentForValue in ConfiguredLcmGenerator,
// and is directed to "GenerateContentForSimpleString", which applies the first analysis WS.
// This creates an extra "/span[@lang='en' and text()=...]" at the end of the lexentry.
// We don't care if a WS is assigned, so we ignore this possible extra span and check only for the correct homograph number.
var entryWithHomograph = "/TESTWRAPPER/div[@class='lexentry']/span[@class='homographnumber' and text()=1] | /TESTWRAPPER/div[@class='lexentry']/span[@class='homographnumber']/*[text()=1]";
AssertThatXmlIn.String(XHTMLStringBuilder.ToString()).HasSpecifiedNumberOfMatchesForXpath(entryWithHomograph, 1);
entryWithHomograph = "/TESTWRAPPER/div[@class='lexentry']/span[@class='homographnumber' and text()='2']";
entryWithHomograph = "/TESTWRAPPER/div[@class='lexentry']/span[@class='homographnumber' and text()=2] | /TESTWRAPPER/div[@class='lexentry']/span[@class='homographnumber']/*[text()=2]";
AssertThatXmlIn.String(XHTMLStringBuilder.ToString()).HasSpecifiedNumberOfMatchesForXpath(entryWithHomograph, 1);
}

Expand Down Expand Up @@ -5504,7 +5510,7 @@ public void GenerateContentForEntry_PictureWithCreator()
//SUT
var result = ConfiguredLcmGenerator.GenerateContentForEntry(testEntry, mainEntryNode, null, settings).ToString();
const string oneSenseWithPicture = "/div[@class='lexentry']/span[@class='pictures']/div[@class='picture']/img[@class='photo' and @id]";
const string oneSenseWithPictureCaption = "/div[@class='lexentry']/span[@class='pictures']/div[@class='picture']/div[@class='captionContent']/span[@class='creator' and text()='Jason Naylor']";
const string oneSenseWithPictureCaption = "/div[@class='lexentry']/span[@class='pictures']/div[@class='picture']/div[@class='captionContent']/span[@class='creator']/span[@lang='en' and text()='Jason Naylor']";
//This assert is dependent on the specific entry data created in CreateInterestingLexEntry
AssertThatXmlIn.String(result).HasSpecifiedNumberOfMatchesForXpath(oneSenseWithPicture, 1);
AssertThatXmlIn.String(result).HasSpecifiedNumberOfMatchesForXpath(oneSenseWithPictureCaption, 1);
Expand Down Expand Up @@ -6488,7 +6494,7 @@ public void GenerateContentForEntry_DateCustomFieldGeneratesContent()
var settings = new ConfiguredLcmGenerator.GeneratorSettings(Cache, m_propertyTable, false, false, null);
//SUT
var result = ConfiguredLcmGenerator.GenerateContentForEntry(testEntry, mainEntryNode, null, settings).ToString();
var customDataPath = string.Format("/div[@class='lexentry']/span[@class='customdate' and text()='{0}']", customData.ToLongDateString());
var customDataPath = string.Format("/div[@class='lexentry']/span[@class='customdate']/span[@lang='en' and text()='{0}']", customData.ToLongDateString());
AssertThatXmlIn.String(result).HasSpecifiedNumberOfMatchesForXpath(customDataPath, 1);
}
}
Expand Down Expand Up @@ -6518,7 +6524,7 @@ public void GenerateContentForEntry_IntegerCustomFieldGeneratesContent()
var settings = new ConfiguredLcmGenerator.GeneratorSettings(Cache, m_propertyTable, false, false, null);
//SUT
var result = ConfiguredLcmGenerator.GenerateContentForEntry(testEntry, mainEntryNode, null, settings).ToString();
var customDataPath = string.Format("/div[@class='lexentry']/span[@class='custominteger' and text()='{0}']", customData);
var customDataPath = string.Format("/div[@class='lexentry']/span[@class='custominteger']/span[@lang='en' and text()='{0}']", customData);
AssertThatXmlIn.String(result).HasSpecifiedNumberOfMatchesForXpath(customDataPath, 1);
}
}
Expand Down
24 changes: 21 additions & 3 deletions Src/xWorks/xWorksTests/LcmJsonGeneratorTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1103,13 +1103,31 @@ public void SavePublishedJsonWithStyles_DisplayXhtmlPopulated()
DefaultDecorator, 1,
new DictionaryConfigurationModel { Parts = new List<ConfigurableDictionaryNode> { mainEntryNode } },
m_propertyTable, "test.json", null, out int[] _);
var expectedResults = @"{""xhtmlTemplate"":""lexentry"",""guid"":""g" + testEntry.Guid + @""",""letterHead"": ""c"",""sortIndex"": 0,

// An explicitly stated writing system is not necessary for the homograph number to be correct.
// Normally the propertyvalue for a headword with homograph number is IMultiStringAccessor.
// However, in the test setup the propertyvalue for homograph number is an int
// and therefore hits the int case of GenerateContentForValue in ConfiguredLcmGenerator,
// and is directed to "GenerateContentForSimpleString", which applies the first analysis WS.
// The homograph portion of this test is only concerned with checking value of the homograph number; we don't care if a WS is assigned.
var expectedResultsWithoutWs = @"{""xhtmlTemplate"":""lexentry"",""guid"":""g" + testEntry.Guid + @""",""letterHead"": ""c"",""sortIndex"": 0,
""homographnumber"":""0"",""citationform"":[{""lang"":""fr"",""value"":""Citation""}],
""displayXhtml"":""<div class=\""lexentry\"" nodeId=\""" + mainEntryNode.GetNodeId() +
@"\"" id=\""g" + testEntry.Guid + @"\""><span class=\""homographnumber\"" nodeId=\""" + homographNum.GetNodeId() +
@"\"">0</span><span class=\""citationform\""><span nodeId=\""" + citationForm.GetNodeId() + @"\"" lang=\""fr\"">Citation</span></span></div>""}";
var expected = (JObject)JsonConvert.DeserializeObject(expectedResults, new JsonSerializerSettings { Formatting = Formatting.None });
VerifyJson(results[0][0].ToString(Formatting.None), expected);
var expectedResultsWithWs = @"{""xhtmlTemplate"":""lexentry"",""guid"":""g" + testEntry.Guid + @""",""letterHead"": ""c"",""sortIndex"": 0,
""homographnumber"":""0"",""citationform"":[{""lang"":""fr"",""value"":""Citation""}],
""displayXhtml"":""<div class=\""lexentry\"" nodeId=\""" + mainEntryNode.GetNodeId() +
@"\"" id=\""g" + testEntry.Guid + @"\""><span class=\""homographnumber\"" nodeId=\""" + homographNum.GetNodeId() +
@"\""><span lang=\""en\"">0</span></span><span class=\""citationform\""><span nodeId=\""" + citationForm.GetNodeId() + @"\"" lang=\""fr\"">Citation</span></span></div>""}";

var expectedWithoutWs = (JObject)JsonConvert.DeserializeObject(expectedResultsWithoutWs, new JsonSerializerSettings { Formatting = Formatting.None });
var expectedWithWs = (JObject)JsonConvert.DeserializeObject(expectedResultsWithWs, new JsonSerializerSettings { Formatting = Formatting.None });

dynamic jsonResult = JsonConvert.DeserializeObject(results[0][0].ToString(Formatting.None), new JsonSerializerSettings { Formatting = Formatting.None });
string actualReformatted = JsonConvert.SerializeObject(jsonResult, Formatting.Indented);
Assert.That(actualReformatted, Is.AnyOf(JsonConvert.SerializeObject(expectedWithoutWs, Formatting.Indented),
JsonConvert.SerializeObject(expectedWithWs, Formatting.Indented)));
}

[Test]
Expand Down
Loading