diff --git a/Src/Common/Controls/XMLViews/XMLViewsTests/TestManyOneBrowse.cs b/Src/Common/Controls/XMLViews/XMLViewsTests/TestManyOneBrowse.cs index 935d349cdb..536bd1b32b 100644 --- a/Src/Common/Controls/XMLViews/XMLViewsTests/TestManyOneBrowse.cs +++ b/Src/Common/Controls/XMLViews/XMLViewsTests/TestManyOneBrowse.cs @@ -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 } + + /// 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. + [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 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"); + } } } diff --git a/Src/Common/Controls/XMLViews/XmlVc.cs b/Src/Common/Controls/XMLViews/XmlVc.cs index 885f15157e..7e354b04b1 100644 --- a/Src/Common/Controls/XMLViews/XmlVc.cs +++ b/Src/Common/Controls/XMLViews/XmlVc.cs @@ -5074,6 +5074,12 @@ public override int GetHashCode() /// 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; + /// ------------------------------------------------------------------------------------ /// /// Initializes a new instance of the class. @@ -5085,14 +5091,39 @@ public NodeChildrenDisplayCommand(XmlNode node) { } + /// ------------------------------------------------------------------------------------ + /// + /// Initializes a new instance of the class that + /// carries a caller (part ref) node down to the children being displayed. + /// + /// The node whose children are displayed. + /// The calling part ref node (may be null). + /// ------------------------------------------------------------------------------------ + public NodeChildrenDisplayCommand(XmlNode node, XmlNode caller) + : base(node) + { + m_caller = caller; + } + + /// + /// The calling part ref node, or null if none was supplied. + /// + 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 @@ -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; } /// ------------------------------------------------------------------------------------ @@ -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()); } diff --git a/Src/Common/Controls/XMLViews/XmlViewsUtils.cs b/Src/Common/Controls/XMLViews/XmlViewsUtils.cs index 1ffcd8a631..4d90f3a02e 100644 --- a/Src/Common/Controls/XMLViews/XmlViewsUtils.cs +++ b/Src/Common/Controls/XMLViews/XmlViewsUtils.cs @@ -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. }