/// <summary> /// Returns an array of string values (keys) for the objects under this layout node. /// </summary> /// <param name="fdoCache">The fdo cache.</param> /// <param name="sda">The sda.</param> /// <param name="layout">The layout.</param> /// <param name="hvo">The hvo.</param> /// <param name="layoutCache">The layout cache.</param> /// <param name="caller">where layout is a component of a 'part' element, caller /// is the 'part ref' that invoked it.</param> /// <param name="stringTbl">The string TBL.</param> /// <param name="wsForce">if non-zero, "string" elements are forced to use that writing system for multistrings.</param> /// <returns></returns> static public string[] StringsFor(FdoCache fdoCache, ISilDataAccess sda, XmlNode layout, int hvo, LayoutCache layoutCache, XmlNode caller, StringTable stringTbl, int wsForce) { // Some nodes are known to be uninteresting. if (XmlVc.CanSkipNode(layout)) return new string[0]; // don't know how to sort, treat as empty key. switch (layout.Name) { case "string": { int hvoTarget = hvo; XmlVc.GetActualTarget(layout, ref hvoTarget, sda); // modify the hvo if needed if (hvo != hvoTarget) { return AddStringFromOtherObj(layout, hvoTarget, fdoCache, sda); } int flid = GetFlid(sda, layout, hvo); if (wsForce != 0) { // If we are forcing a writing system, and it's a multistring, get the forced alternative. int itype = sda.MetaDataCache.GetFieldType(flid); itype = itype & (int)CellarPropertyTypeFilter.VirtualMask; switch (itype) { case (int) CellarPropertyType.MultiUnicode: case (int) CellarPropertyType.MultiString: if (wsForce < 0) { int wsActual; var tss = WritingSystemServices.GetMagicStringAlt(fdoCache, sda, wsForce, hvo, flid, true, out wsActual); return new[] {tss == null ? "" : tss.Text }; } return new[] {sda.get_MultiStringAlt(hvo, flid, wsForce).Text}; } } bool fFoundType; var strValue = fdoCache.GetText(hvo, flid, layout, out fFoundType); if (fFoundType) return new[] {strValue}; throw new Exception("Bad property type (" + strValue + " for hvo " + hvo + " found for string property " + flid + " in " + layout.OuterXml); } case "configureMlString": { int flid = GetFlid(sda, layout, hvo); // The Ws info specified in the part ref node HashSet<int> wsIds = WritingSystemServices.GetAllWritingSystems(fdoCache, caller, null, hvo, flid); if (wsIds.Count == 1) { var strValue = sda.get_MultiStringAlt(hvo, flid, wsIds.First()).Text; return new[] {strValue}; } return new[] {AddMultipleAlternatives(fdoCache, sda, wsIds, hvo, flid, caller)}; } case "multiling": return ProcessMultiLingualChildren(fdoCache, sda, layout, hvo, layoutCache, caller, stringTbl, wsForce); case "layout": // "layout" can occur when GetNodeToUseForColumn returns a phony 'layout' // formed by unifying a layout with child nodes. Assemble its children. // (arguably, we should treat that like div if current flow is a pile. // but we can't tell that and it rarely makes a difference.) case "para": case "span": { return AssembleChildKeys(fdoCache, sda, layout, hvo, layoutCache, caller, stringTbl, wsForce); } case "column": // top-level node for whole column; concatenate children as for "para" // if multipara is false, otherwise as for "div" if (XmlUtils.GetOptionalBooleanAttributeValue(layout, "multipara", false)) return ChildKeys(fdoCache, sda, layout, hvo, layoutCache, caller, stringTbl, wsForce); else return AssembleChildKeys(fdoCache, sda, layout, hvo, layoutCache, caller, stringTbl, wsForce); case "part": { string partref = XmlUtils.GetOptionalAttributeValue(layout, "ref"); if (partref == null) return ChildKeys(fdoCache, sda, layout, hvo, layoutCache, caller, stringTbl, wsForce); // an actual part, made up of its pieces XmlNode part = XmlVc.GetNodeForPart(hvo, partref, false, sda, layoutCache); // This is the critical place where we introduce a caller. The 'layout' is really a 'part ref' which is the // 'caller' for all embedded nodes in the called part. return StringsFor(fdoCache, sda, part, hvo, layoutCache, layout, stringTbl, wsForce); } case "div": case "innerpile": { // Concatenate keys for child nodes (as distinct strings) return ChildKeys(fdoCache, sda, layout, hvo, layoutCache, caller, stringTbl, wsForce); } case "obj": { // Follow the property, get the object, look up the layout to use, // invoke recursively. int flid = GetFlid(sda, layout, hvo); int hvoTarget = sda.get_ObjectProp(hvo, flid); if (hvoTarget == 0) break; // return empty key string targetLayoutName = XmlUtils.GetOptionalAttributeValue(layout, "layout"); // uses 'default' if missing. XmlNode layoutTarget = GetLayoutNodeForChild(sda, hvoTarget, flid, targetLayoutName, layout, layoutCache); if (layoutTarget == null) break; return ChildKeys(fdoCache, sda, layoutTarget, hvoTarget, layoutCache, caller, stringTbl, wsForce); } case "seq": { // Follow the property. For each object, look up the layout to use, // invoke recursively, concatenate int flid = GetFlid(sda, layout, hvo); int[] contents; int ctarget = sda.get_VecSize(hvo, flid); using (ArrayPtr arrayPtr = MarshalEx.ArrayToNative<int>(ctarget)) { int chvo; sda.VecProp(hvo, flid, ctarget, out chvo, arrayPtr); contents = MarshalEx.NativeToArray<int>(arrayPtr, chvo); } string[] result = null; string targetLayoutName = XmlVc.GetLayoutName(layout, caller); // also allows for finding "param" attr in caller, if not null int i = 0; foreach (int hvoTarget in contents) { int prevResultLength = GetArrayLength(result); XmlNode layoutTarget = GetLayoutNodeForChild(sda, hvoTarget, flid, targetLayoutName, layout, layoutCache); if (layoutTarget == null) continue; // should not happen, but best recovery we can make result = Concatenate(result, ChildKeys(fdoCache, sda, layoutTarget, hvoTarget, layoutCache, caller, stringTbl, wsForce)); // add a separator between the new childkey group and the previous childkey group if (i > 0 && prevResultLength != GetArrayLength(result) && prevResultLength > 0) { int ichIns = 0; if (result[prevResultLength - 1] != null) ichIns = result[prevResultLength - 1].Length; AddSeparator(ref result[prevResultLength - 1], ichIns, layout); } ++i; } return result; } case "choice": { foreach(XmlNode whereNode in layout.ChildNodes) { if (whereNode.Name != "where") { if (whereNode.Name == "otherwise") return StringsFor(fdoCache, sda, XmlUtils.GetFirstNonCommentChild(whereNode), hvo, layoutCache, caller, stringTbl, wsForce); continue; // ignore any other nodes,typically comments } // OK, it's a where node. if (XmlVc.ConditionPasses(whereNode, hvo, fdoCache, sda, caller)) return StringsFor(fdoCache, sda, XmlUtils.GetFirstNonCommentChild(whereNode), hvo, layoutCache, caller, stringTbl, wsForce); } break; // if no condition passes and no otherwise, return null. } case "if": { if (XmlVc.ConditionPasses(layout, hvo, fdoCache, sda, caller)) return StringsFor(fdoCache, sda, XmlUtils.GetFirstNonCommentChild(layout), hvo, layoutCache, caller, stringTbl, wsForce); break; } case "ifnot": { if (!XmlVc.ConditionPasses(layout, hvo, fdoCache, sda, caller)) return StringsFor(fdoCache, sda, XmlUtils.GetFirstNonCommentChild(layout), hvo, layoutCache, caller, stringTbl, wsForce); break; } case "lit": { string literal = layout.InnerText; if (stringTbl != null) { string sTranslate = XmlUtils.GetOptionalAttributeValue(layout, "translate", ""); if (sTranslate.Trim().ToLower() != "do not translate") literal = stringTbl.LocalizeLiteralValue(literal); } return new[] { literal }; } case "int": { int flid = GetFlid(sda, layout, hvo); int val = sda.get_IntProp(hvo, flid); return new[] {AlphaCompNumberString(val)}; } case "datetime": { int flid = GetFlid(sda, layout, hvo); CellarPropertyType itype = (CellarPropertyType)sda.MetaDataCache.GetFieldType(flid); if (itype == CellarPropertyType.Time) { DateTime dt = SilTime.GetTimeProperty(sda, hvo, flid); return new[] {DateTimeCompString(dt)}; } else { string stFieldName = XmlUtils.GetManditoryAttributeValue(layout, "field"); throw new Exception("Bad field type (" + stFieldName + " for hvo " + hvo + " found for " + layout.Name + " property " + flid + " in " + layout.OuterXml); } } case "picture": // Treat a picture as a non-empty string for purposes of deciding whether something is empty. // This string seems as good as anything for other purposes. return new[] {"a picture"}; default: // unknown or comment node, adds nothing Debug.Assert(false, "unrecognized XML node."); break; } return new string[0]; // don't know how to sort, treat as empty key. }