Skip to content
Closed
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
23 changes: 23 additions & 0 deletions Src/Common/Controls/XMLViews/XMLViewsTests/TestManyOneBrowse.cs
Original file line number Diff line number Diff line change
Expand Up @@ -449,5 +449,28 @@ public void DisplayDoubleSeqPathObject()
m_mdc, m_sda, m_layouts, out useHvo, collectStructNodes);
Assert.That(useHvo, Is.EqualTo(9)); // the third sense, in which context we display the 4th SD
}

/// <summary>A part-ref node must carry itself as the resulting Caller, so child fragments (e.g. multilingual strings) can resolve attributes like "ws" from it.</summary>
[Test]
public void GetDisplayCommandForColumn_PartRefNode_CarriesItselfAsCaller()
{
ArrayList list = new ArrayList();
XmlViewsUtils.CollectBrowseItems(1, m_columnList[0], list, null, m_mdc, m_sda, m_layouts);
IManyOnePathSortItem bvi = list[0] as IManyOnePathSortItem;

// Hand-built <part ref="Msas"/> node, the shape XmlVc embeds when a part ref names another part.
var doc = new XmlDocument();
XmlElement partRefNode = doc.CreateElement("part");
partRefNode.SetAttribute("ref", "Msas");

int hvo;
NodeDisplayCommand cmd = XmlViewsUtils.GetDisplayCommandForColumn(bvi, partRefNode, null,
m_mdc, m_sda, m_layouts, out hvo, null);

var childrenCmd = cmd as NodeChildrenDisplayCommand;
Assert.That(childrenCmd, Is.Not.Null, "a resolvable part ref must yield a NodeChildrenDisplayCommand");
Assert.That(childrenCmd.Caller, Is.SameAs(partRefNode),
"the part ref node itself must be carried down as the caller so child fragments can resolve attributes from it");
}
}
}
40 changes: 36 additions & 4 deletions Src/Common/Controls/XMLViews/XmlVc.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5074,6 +5074,12 @@ public override int GetHashCode()
/// </summary>
public class NodeChildrenDisplayCommand : NodeDisplayCommand
{
// Optional "part ref" node that called the part whose children we display. When non-null it is
// passed down as the caller so that child fragments (e.g. multilingual strings) can resolve
// attributes such as "ws" from it. This mirrors XmlVc's behavior where the "part" case is the
// one thing that calls ProcessChildren with a non-null caller.
readonly XmlNode m_caller;

/// ------------------------------------------------------------------------------------
/// <summary>
/// Initializes a new instance of the <see cref="NodeChildrenDisplayCommand"/> class.
Expand All @@ -5085,14 +5091,39 @@ public NodeChildrenDisplayCommand(XmlNode node)
{
}

/// ------------------------------------------------------------------------------------
/// <summary>
/// Initializes a new instance of the <see cref="NodeChildrenDisplayCommand"/> class that
/// carries a caller (part ref) node down to the children being displayed.
/// </summary>
/// <param name="node">The node whose children are displayed.</param>
/// <param name="caller">The calling part ref node (may be null).</param>
/// ------------------------------------------------------------------------------------
public NodeChildrenDisplayCommand(XmlNode node, XmlNode caller)
: base(node)
{
m_caller = caller;
}

/// <summary>
/// The calling part ref node, or null if none was supplied.
/// </summary>
public XmlNode Caller
{
get { return m_caller; }
}

internal override void PerformDisplay(XmlVc vc, int fragId, int hvo, IVwEnv vwenv)
{
vc.ProcessChildren(Node, vwenv, hvo);
if (m_caller == null)
vc.ProcessChildren(Node, vwenv, hvo);
else
vc.ProcessChildren(Node, vwenv, hvo, m_caller);
}

internal override void DetermineNeededFields(XmlVc vc, int fragId, NeededPropertyInfo info)
{
DetermineNeededFieldsForChildren(vc, Node, null, info);
DetermineNeededFieldsForChildren(vc, Node, m_caller, info);
}

// Make it work sensibly as a hash key
Expand All @@ -5108,7 +5139,8 @@ internal override void DetermineNeededFields(XmlVc vc, int fragId, NeededPropert
/// ------------------------------------------------------------------------------------
public override bool Equals(object obj)
{
return base.Equals(obj) && obj is NodeChildrenDisplayCommand;
var other = obj as NodeChildrenDisplayCommand;
return other != null && base.Equals(obj) && other.m_caller == m_caller;
}

/// ------------------------------------------------------------------------------------
Expand All @@ -5122,7 +5154,7 @@ public override bool Equals(object obj)
/// ------------------------------------------------------------------------------------
public override int GetHashCode()
{
return base.GetHashCode();
return base.GetHashCode() ^ (m_caller == null ? 0 : m_caller.GetHashCode());
}


Expand Down
14 changes: 9 additions & 5 deletions Src/Common/Controls/XMLViews/XmlViewsUtils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1134,19 +1134,23 @@ static NodeDisplayCommand GetDisplayCommandForColumn1(IManyOnePathSortItem bvi,
collectOuterStructParts.Add(node);
return GetDisplayCommandForColumn1(bvi, mainChild, cache, mdc, sda, layouts, depth, out hvo, collectOuterStructParts);
}
// Review JohnT: In XmlVc, "part" is the one thing that calls ProcessChildren with non-null caller.
// this should make some difference here, but I can't figure what yet, or come up with a test that fails.
// We may need a "caller" argument to pass this down so it can be used in GetNodeForRelatedObject.
// In XmlVc, "part" is the one thing that calls ProcessChildren with a non-null caller (the
// part ref node). The off-screen cell-render path must do the same, otherwise children that
// read attributes such as "ws" from the caller (e.g. multilingual strings configured via the
// part ref) resolve nothing and render blank. So we capture the part ref node and carry it as
// the caller into the NodeChildrenDisplayCommand. (See LT- for the "Grammatical Info." blank
// column bug in the Avalonia browse table.)
case "part":
{
string layoutName = XmlUtils.GetOptionalAttributeValue(node, "ref");
if (layoutName != null)
{
// It's actually a part ref, in a layout, not a part looked up by one!
// Get the node it refers to, and make a command to process its children.
// Get the node it refers to, and make a command to process its children, passing this
// part ref node down as the caller so child fragments can resolve attributes from it.
XmlNode part = XmlVc.GetNodeForPart(hvo, layoutName, false, sda, layouts);
if (part != null)
return new NodeChildrenDisplayCommand(part); // display this object using the children of the part referenced.
return new NodeChildrenDisplayCommand(part, node); // display this object using the children of the part referenced.
else
return new NodeDisplayCommand(node); // no matching part, do default.
}
Expand Down
Loading