public void MultiStringAlt() { CheckDisposed(); ITsString tsStringNew = m_ISilDataAccess.get_MultiStringAlt(1117, 2227, 7); Assert.AreEqual(0, tsStringNew.Length); Assert.IsFalse(m_ISilDataAccess.IsDirty()); ITsPropsBldr propsBldr = TsPropsBldrClass.Create(); ITsStrBldr strBldr = TsStrBldrClass.Create(); propsBldr.SetStrPropValue((int)FwTextPropType.ktptNamedStyle, "Verse"); strBldr.Replace(0, 0, "Test", propsBldr.GetTextProps()); ITsString tsString = strBldr.GetString(); m_ISilDataAccess.SetMultiStringAlt(1117, 2227, 7, tsString); tsStringNew = m_ISilDataAccess.get_MultiStringAlt(1117, 2227, 7); Assert.AreEqual(tsString, tsStringNew); Assert.IsTrue(m_ISilDataAccess.IsDirty()); strBldr.Replace(0, 0, "SecondTest", propsBldr.GetTextProps()); tsString = strBldr.GetString(); m_ISilDataAccess.SetMultiStringAlt(1117, 2227, 7, tsString); tsStringNew = m_ISilDataAccess.get_MultiStringAlt(1117, 2227, 7); Assert.AreEqual(tsString, tsStringNew); Assert.IsTrue(m_ISilDataAccess.IsDirty()); tsStringNew = m_ISilDataAccess.get_MultiStringAlt(1117, 2227, 8); Assert.AreEqual(0, tsStringNew.Length); Assert.IsTrue(m_ISilDataAccess.IsDirty()); CheckProp(1117, 2227, tsString, CellarModuleDefns.kcptMultiString); }
public void MultiStringAlt() { ITsString tsStringNew = m_ISilDataAccess.get_MultiStringAlt(1117, 2227, 7); Assert.IsNotNull(tsStringNew); Assert.AreEqual(0, tsStringNew.Length); ITsPropsBldr propsBldr = TsStringUtils.MakePropsBldr(); ITsStrBldr strBldr = TsStringUtils.MakeStrBldr(); propsBldr.SetStrPropValue((int)FwTextPropType.ktptNamedStyle, "Verse"); strBldr.Replace(0, 0, "Test", propsBldr.GetTextProps()); ITsString tsString = strBldr.GetString(); m_IVwCacheDa.CacheStringAlt(1117, 2227, 7, tsString); tsStringNew = m_ISilDataAccess.get_MultiStringAlt(1117, 2227, 7); Assert.AreEqual(tsString, tsStringNew); strBldr.Replace(0, 0, "SecondTest", propsBldr.GetTextProps()); tsString = strBldr.GetString(); m_IVwCacheDa.CacheStringAlt(1117, 2227, 7, tsString); tsStringNew = m_ISilDataAccess.get_MultiStringAlt(1117, 2227, 7); Assert.AreEqual(tsString, tsStringNew); tsStringNew = m_ISilDataAccess.get_MultiStringAlt(1117, 2227, 8); Assert.IsNotNull(tsStringNew); Assert.AreEqual(0, tsStringNew.Length); }
public void MultiStringVirtualHandler() { CheckDisposed(); MultiStringVirtualHandler msvh = new MultiStringVirtualHandler("LexDb", "NewString"); m_fdoCache.InstallVirtualProperty(msvh); Assert.IsTrue(msvh.Writeable); Assert.AreEqual((int)CellarModuleDefns.kcptMultiString, msvh.Type); Assert.IsFalse(msvh.ComputeEveryTime); int hvoLexDb = 0; if (m_fdoCache.LangProject != null && m_fdoCache.LangProject.LexDbOA != null) { hvoLexDb = m_fdoCache.LangProject.LexDbOA.Hvo; } if (hvoLexDb == 0) { // Rather than try to create a real LexDb, simulate one. hvoLexDb = khvoTest; } ISilDataAccess sda = m_fdoCache.MainCacheAccessor; IVwCacheDa cda = sda as IVwCacheDa; int wsVern = m_fdoCache.LangProject.DefaultVernacularWritingSystem; msvh.Load(hvoLexDb, msvh.Tag, wsVern, cda); ITsStrFactory tsf = TsStrFactoryClass.Create(); ITsString tss = tsf.MakeString("", wsVern); ITsString tss1 = sda.get_MultiStringAlt(hvoLexDb, msvh.Tag, wsVern); Assert.IsTrue(tss.Equals(tss1)); Assert.IsTrue(msvh.Writeable); tss = tsf.MakeString("This is a test", wsVern); msvh.WriteObj(hvoLexDb, msvh.Tag, wsVern, tss, sda); cda.CacheStringAlt(hvoLexDb, msvh.Tag, wsVern, tss); tss1 = sda.get_MultiStringAlt(hvoLexDb, msvh.Tag, wsVern); Assert.IsTrue(tss.Equals(tss1)); }
/// <summary> /// Parse the text in hvoPara.Contents[vc.DestWs] and make words /// </summary> /// <param name="hvoPara"></param> public void Parse(int hvoPara) { ITsString tssSrc = m_sda.get_MultiStringAlt(hvoPara, ViewSampleVc.ktagParaContents, m_vc.DestWs); WordMaker wm = new WordMaker(tssSrc, m_wsf); int ichMin, ichLim; int cbundle = m_sda.get_VecSize(hvoPara, ViewSampleVc.ktagParaBundles); // Clean it out. This wouldn't normally be appropriate for an owning property, but we can get away // with it for a non-database cache. if (cbundle != 0) { m_sda.Replace(hvoPara, ViewSampleVc.ktagParaBundles, 0, cbundle, new int[0], 0); } int ibundle = 0; ITsPropsFactory tpf = (ITsPropsFactory) new FwKernelLib.TsPropsFactoryClass(); ITsTextProps ttp = tpf.MakeProps(null, m_vc.SourceWs, 0); for (ITsString tssWord = wm.NextWord(out ichMin, out ichLim); tssWord != null; tssWord = wm.NextWord(out ichMin, out ichLim)) { // 4 is an arbitrary classid; this kind of cache does nothing with it. int hvoBundle = m_sda.MakeNewObject(4, hvoPara, ViewSampleVc.ktagParaBundles, ibundle); ibundle++; m_sda.SetString(hvoBundle, ViewSampleVc.ktagBundleBase, tssWord); ITsStrBldr tsb = tssWord.GetBldr(); tsb.Replace(0, 0, "idiom(", ttp); tsb.Replace(tsb.get_Length(), tsb.get_Length(), ")", ttp); m_sda.SetString(hvoBundle, ViewSampleVc.ktagBundleIdiom, tsb.GetString()); tsb = tssWord.GetBldr(); tsb.Replace(0, 0, "ling(", ttp); tsb.Replace(tsb.get_Length(), tsb.get_Length(), ")", ttp); m_sda.SetString(hvoBundle, ViewSampleVc.ktagBundleLing, tsb.GetString()); } m_sda.PropChanged(null, (int)FwViews.PropChangeType.kpctNotifyAll, hvoPara, ViewSampleVc.ktagParaBundles, 0, ibundle, cbundle); }
internal ITsString Value(int ws) { return(m_sda.get_MultiStringAlt(khvoRoot, kflid, ws)); }
private static void CacheStringAltForAllCurrentWs(IEnumerable<int> currentWsList, IVwCacheDa cda, int hvoSec, int flidSec, ISilDataAccess sdaMain, int hvoMain, int flidMain) { CacheStringAltForAllCurrentWs(currentWsList, cda, hvoSec, flidSec, delegate(int ws1) { ITsString tssMain; if (hvoMain != 0) tssMain = sdaMain.get_MultiStringAlt(hvoMain, flidMain, ws1); else tssMain = TsStringUtils.MakeTss("", ws1); return tssMain; }); }
private void CopyStringsToSecondary(IList<int> writingSystems, ISilDataAccess sdaMain, int hvoMain, int flidMain, IVwCacheDa cda, int hvoSec, int flidSec, ITsStrFactory tsf) { CheckDisposed(); foreach (int ws in writingSystems) { int wsActual = 0; ITsString tss; if (ws > 0) { wsActual = ws; } else if (ws == WritingSystemServices.kwsFirstAnal) { IList<int> currentAnalysisWsList = m_caches.MainCache.ServiceLocator.WritingSystems.CurrentAnalysisWritingSystems.Select(wsObj => wsObj.Handle).ToArray(); CacheStringAltForAllCurrentWs(currentAnalysisWsList, cda, hvoSec, flidSec, sdaMain, hvoMain, flidMain); continue; } else if (ws == WritingSystemServices.kwsFirstVern) { IList<int> currentVernWsList = m_caches.MainCache.ServiceLocator.WritingSystems.CurrentVernacularWritingSystems.Select(wsObj => wsObj.Handle).ToArray(); CacheStringAltForAllCurrentWs(currentVernWsList, cda, hvoSec, flidSec, sdaMain, hvoMain, flidMain); continue; } else if (ws == WritingSystemServices.kwsVernInParagraph) { wsActual = RawWordformWs; } if (wsActual <= 0) throw new ArgumentException(String.Format("magic ws {0} not yet supported.", ws)); if (hvoMain == 0) { tss = MakeTss("", wsActual, tsf); } else { tss = sdaMain.get_MultiStringAlt(hvoMain, flidMain, wsActual); } cda.CacheStringAlt(hvoSec, flidSec, wsActual, tss); } }
/// <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. }
internal static string[] AddStringFromOtherObj(XmlNode frag, int hvoTarget, FdoCache cache, ISilDataAccess sda) { int flid = XmlVc.GetFlid(frag, hvoTarget, sda); ITsStrFactory tsf = cache.TsStrFactory; CellarPropertyType itype = (CellarPropertyType)sda.MetaDataCache.GetFieldType(flid); if (itype == CellarPropertyType.Unicode) { return new[] { sda.get_UnicodeProp(hvoTarget, flid) }; } else if (itype == CellarPropertyType.String) { return new[] { sda.get_StringProp(hvoTarget, flid).Text }; } else // multistring of some type { int wsid = 0; string sep = ""; if (s_cwsMulti > 1) { string sLabelWs = XmlUtils.GetOptionalAttributeValue(frag, "ws"); if (sLabelWs != null && sLabelWs == "current") { sep = DisplayMultiSep(frag) + DisplayWsLabel(s_qwsCurrent, cache); wsid = s_qwsCurrent.Handle; } } if (wsid == 0) wsid = WritingSystemServices.GetWritingSystem(cache, frag, null, WritingSystemServices.kwsAnal).Handle; if (itype == CellarPropertyType.MultiUnicode) { return new[] { sep, sda.get_MultiStringAlt(hvoTarget, flid, wsid).Text }; } else { return new[] { sep, sda.get_MultiStringAlt(hvoTarget, flid, wsid).Text }; } } }
private void CopyStringsToSecondary(List<int> writingSystems, ISilDataAccess sdaMain, int hvoMain, int flidMain, IVwCacheDa cda, int hvoSec, int flidSec, ITsStrFactory tsf) { CheckDisposed(); foreach (int ws in writingSystems) { int wsActual = 0; ITsString tss; if (ws > 0) { wsActual = ws; } else if (ws == LangProject.kwsFirstAnal) { int[] currentAnalysisWsList = m_caches.MainCache.LangProject.CurAnalysisWssRS.HvoArray; CacheStringAltForAllCurrentWs(currentAnalysisWsList, cda, hvoSec, flidSec, sdaMain, hvoMain, flidMain); continue; } else if (ws == LangProject.kwsFirstVern) { int[] currentVernWsList = m_caches.MainCache.LangProject.CurVernWssRS.HvoArray; CacheStringAltForAllCurrentWs(currentVernWsList, cda, hvoSec, flidSec, sdaMain, hvoMain, flidMain); continue; } else if (ws == LangProject.kwsVernInParagraph) { wsActual = this.RawWordformWs; } if (wsActual <= 0) throw new ArgumentException(String.Format("magic ws {0} not yet supported.", ws)); if (hvoMain == 0) { tss = MakeTss("", wsActual, tsf); } else { tss = sdaMain.get_MultiStringAlt(hvoMain, flidMain, wsActual); } cda.CacheStringAlt(hvoSec, flidSec, wsActual, tss); } }
private ITsString FirstAlternativeName(ISilDataAccess sda, int hvoText, int[] rgws) { ITsString tss; for (int i = 0; i < rgws.Length; ++i) { tss = sda.get_MultiStringAlt(hvoText, (int)CmMajorObject.CmMajorObjectTags.kflidName, rgws[i]); if (tss.Length > 0) return tss; } return null; }
public void Setup() { // Create the following: // - part and layout inventories // - metadata cache // - DataAccess cache // - collection of columns to display. // We want a MetaDataCache that knows about // - LexEntry.Senses, Msas, CitationForm, Bibliography, Etymology // - LexSense.SemanticDomains, SenseType, Status, gloss // - CmPossibility Name, abbr // - MoMorphSynAnalysis // - MoStemMsa // - MoDerivationalMsa string m_sTestPath = Path.Combine(DirectoryFinder.FwSourceDirectory, Path.Combine("Common", Path.Combine("Controls", Path.Combine("XMLViews", Path.Combine("XMLViewsTests", "SampleCm.xml"))))); m_mdc = MetaDataCache.CreateMetaDataCache(m_sTestPath); // We want ISilDataAccess with: // - LexEntry (1) with no senses and one MSA (2) // - LexEntry (4) with one sense (5) and no MSA // - LexEntry (6) with three senses (7, 8, 9) and two MSAs (10, 11) // - sense(5) with no semantic domains // - senses with one SD (7->30, 8->31) // - sense with three SDs, one the same as the first (9->30, 31, 32) // - MoStemMsa (2, 11) // - MoDerivationalMsa (10) m_cda = VwCacheDaClass.Create(); m_sda = m_cda as ISilDataAccess; m_wsManager = new PalasoWritingSystemManager(); m_sda.WritingSystemFactory = m_wsManager; var parser = new SimpleDataParser(m_mdc, m_cda); parser.Parse(Path.Combine(DirectoryFinder.FwSourceDirectory, Path.Combine("Common", Path.Combine("Controls", Path.Combine("XMLViews", Path.Combine("XMLViewsTests", "SampleData.xml")))))); int wsEn = m_wsManager.GetWsFromStr("en"); // These are mainly to check out the parser. Assert.AreEqual(3, m_sda.get_ObjectProp(2, 23011), "part of speech of an MoStemMsa"); Assert.AreEqual(2, m_sda.get_VecItem(1, 2009, 0), "owned msa"); Assert.AreEqual("noun", m_sda.get_MultiStringAlt(3, 7003, wsEn).Text, "got ms property"); Assert.AreEqual(9, m_sda.get_VecItem(6, 2010, 2), "3rd sense"); Assert.AreEqual(31, m_sda.get_VecItem(9, 21016, 1), "2nd semantic domain"); // Columns includes // - CitationForm (string inside span) // - Bibliography (string not in span) // - Sense glosses (string in para in seq, nested in column element) // - Semantic domains (pair of strings in para in seq in seq, using layout refs) // - MSAs (simplified, but polymorphic with one having <choice> and one <obj> to CmPossibility XmlDocument docColumns = new XmlDocument(); docColumns.Load(Path.Combine(DirectoryFinder.FwSourceDirectory, Path.Combine("Common", Path.Combine("Controls", Path.Combine("XMLViews", Path.Combine("XMLViewsTests", "TestColumns.xml")))))); m_columnList = docColumns.DocumentElement.ChildNodes; // Parts just has what those columns need. string partDirectory = Path.Combine(DirectoryFinder.FwSourceDirectory, Path.Combine("Common", Path.Combine("Controls", Path.Combine("XMLViews", "XMLViewsTests")))); Dictionary <string, string[]> keyAttrs = new Dictionary <string, string[]>(); keyAttrs["layout"] = new string[] { "class", "type", "name" }; keyAttrs["group"] = new string[] { "label" }; keyAttrs["part"] = new string[] { "ref" }; // Currently there are no specialized layout files that match. m_layoutInventory = new Inventory(new string[] { partDirectory }, "*.fwlayout", "/LayoutInventory/*", keyAttrs, "TestManyOneBrowse", "ProjectPath"); keyAttrs = new Dictionary <string, string[]>(); keyAttrs["part"] = new string[] { "id" }; m_partInventory = new Inventory(new string[] { partDirectory }, "TestParts.xml", "/PartInventory/bin/*", keyAttrs, "TestManyOneBrowse", "ProjectPath"); m_layouts = new LayoutCache(m_mdc, m_layoutInventory, m_partInventory); }
/// <summary> /// the tss of the wordform form of the given hvoMatchingWordform in the ws we are querying for. /// </summary> /// <param name="sda"></param> /// <param name="tssTxtWord">the tss of the baseline at current word boundaries</param> /// <param name="tssWordAnn">the tss of the wordform form (in the baseline ws) of the annotation.</param> /// <param name="hvoMatchingWordform"></param> /// <param name="wsMatchQuery">the ws of the wordform we're looking for</param> /// <returns></returns> private ITsString GetTssWffCandidate(ISilDataAccess sda, ITsString tssTxtWord, ITsString tssWordAnn, int hvoMatchingWordform, int wsMatchQuery) { ITsString tssWff = tssWordAnn; int wsTxtWord = StringUtils.GetWsAtOffset(tssTxtWord, 0); if (hvoMatchingWordform == 0) { // return a candidate if it matches the ws we're trying to query. return wsMatchQuery == wsTxtWord ? tssWordAnn : null; } // if the ws of the matcher doesn't match the ws of the baseline // find the wordform in an alternative ws. if (wsMatchQuery != wsTxtWord) { try { // bulk load wordform forms in the alternative ws if we get any misses. m_cache.EnableBulkLoadingIfPossible(true); tssWff = sda.get_MultiStringAlt(hvoMatchingWordform, (int)WfiWordform.WfiWordformTags.kflidForm, wsMatchQuery); } finally { m_cache.EnableBulkLoadingIfPossible(false); } } return tssWff; }
public void Setup() { // Create the following: // - part and layout inventories // - metadata cache // - DataAccess cache // - collection of columns to display. // We want a MetaDataCache that knows about // - LexEntry.Senses, Msas, CitationForm, Bibliography, Etymology // - LexSense.SemanticDomains, SenseType, Status, gloss // - CmPossibility Name, abbr // - MoMorphSynAnalysis // - MoStemMsa // - MoDerivationalMsa m_mdc = FwMetaDataCacheClass.Create(); string m_sTestPath = Path.Combine(DirectoryFinder.FwSourceDirectory, @"Common\Controls\XmlViews\XmlViewsTests\SampleCm.xml"); m_mdc.InitXml(m_sTestPath, true); // We want ISilDataAccess with: // - LexEntry (1) with no senses and one MSA (2) // - LexEntry (4) with one sense (5) and no MSA // - LexEntry (6) with three senses (7, 8, 9) and two MSAs (10, 11) // - sense(5) with no semantic domains // - senses with one SD (7->30, 8->31) // - sense with three SDs, one the same as the first (9->30, 31, 32) // - MoStemMsa (2, 11) // - MoDerivationalMsa (10) m_cda = VwCacheDaClass.Create(); m_sda = m_cda as ISilDataAccess; m_wsf = LgWritingSystemFactoryClass.Create(); m_sda.WritingSystemFactory = m_wsf; SimpleDataParser parser = new SimpleDataParser(m_mdc, m_cda); parser.Parse(Path.Combine(DirectoryFinder.FwSourceDirectory, @"Common\Controls\XmlViews\XmlViewsTests\SampleData.xml")); int wsEn = m_wsf.GetWsFromStr("en"); // These are mainly to check out the parser. Assert.AreEqual(3, m_sda.get_ObjectProp(2, 23011), "part of speech of an MoStemMsa"); Assert.AreEqual(2, m_sda.get_VecItem(1, 2009, 0), "owned msa"); Assert.AreEqual("noun", m_sda.get_MultiStringAlt(3, 7003, wsEn).Text, "got ms property"); Assert.AreEqual(9, m_sda.get_VecItem(6, 2010, 2), "3rd sense"); Assert.AreEqual(31, m_sda.get_VecItem(9, 21016, 1), "2nd semantic domain"); // Columns includes // - CitationForm (string inside span) // - Bibliography (string not in span) // - Sense glosses (string in para in seq, nested in column element) // - Semantic domains (pair of strings in para in seq in seq, using layout refs) // - MSAs (simplified, but polymorphic with one having <choice> and one <obj> to CmPossibility XmlDocument docColumns = new XmlDocument(); docColumns.Load(Path.Combine(DirectoryFinder.FwSourceDirectory, @"Common\Controls\XmlViews\XmlViewsTests\TestColumns.xml")); m_columnList = docColumns.DocumentElement.ChildNodes; // Parts just has what those columns need. string partDirectory = Path.Combine(DirectoryFinder.FwSourceDirectory, @"Common\Controls\XmlViews\XmlViewsTests"); Dictionary<string, string[]> keyAttrs = new Dictionary<string, string[]>(); keyAttrs["layout"] = new string[] {"class", "type", "name" }; keyAttrs["group"] = new string[] {"label"}; keyAttrs["part"] = new string[] {"ref"}; // Currently there are no specialized layout files that match. m_layoutInventory = new Inventory(new string[] {partDirectory}, "*Layouts.xml", "/LayoutInventory/*", keyAttrs); keyAttrs = new Dictionary<string, string[]>(); keyAttrs["part"] = new string[] {"id"}; m_partInventory = new Inventory(new string[] {partDirectory}, "TestParts.xml", "/PartInventory/bin/*", keyAttrs); if (m_layouts != null) m_layouts.Dispose(); m_layouts = new LayoutCache(m_mdc, m_layoutInventory, m_partInventory); }
/// <summary> /// Get the value of one alternative of a Multilingual alternation. /// an empty string in the correct writing system if no value recorded. ///</summary> /// <param name='hvo'> </param> /// <param name='tag'> </param> /// <param name='ws'> </param> /// <returns></returns> public virtual ITsString get_MultiStringAlt(int hvo, int tag, int ws) { return(m_baseSda.get_MultiStringAlt(hvo, tag, ws)); }
// Make a string representing a WfiAnalysis, suitable for use in a combo box item. static internal ITsString MakeAnalysisStringRep(int hvoWa, FdoCache fdoCache, bool fUseStyleSheet, int wsVern) { // ITsTextProps boldItalicAnalysis = BoldItalicAnalysis(fdoCache); // ITsTextProps italicAnalysis = ItalicAnalysis(fdoCache, Sandbox.SandboxVc.krgbRed); ITsTextProps posTextProperties = PartOfSpeechTextProperties(fdoCache, true, fUseStyleSheet); ITsTextProps formTextProperties = FormTextProperties(fdoCache, fUseStyleSheet, wsVern); ITsTextProps glossTextProperties = GlossTextProperties(fdoCache, true, fUseStyleSheet); ITsStrBldr tsb = TsStrBldrClass.Create(); ISilDataAccess sda = fdoCache.MainCacheAccessor; int cmorph = fdoCache.GetVectorSize(hvoWa, (int)WfiAnalysis.WfiAnalysisTags.kflidMorphBundles); if (cmorph == 0) { return(fdoCache.MakeUserTss(ITextStrings.ksNoMorphemes)); } bool fRtl = fdoCache.LanguageWritingSystemFactoryAccessor.get_EngineOrNull(wsVern).RightToLeft; int start = 0; int lim = cmorph; int increment = 1; if (fRtl) { start = cmorph - 1; lim = -1; increment = -1; } for (int i = start; i != lim; i += increment) { int hvoMb = fdoCache.GetVectorItem(hvoWa, (int)WfiAnalysis.WfiAnalysisTags.kflidMorphBundles, i); int hvoMf = fdoCache.GetObjProperty(hvoMb, (int)WfiMorphBundle.WfiMorphBundleTags.kflidMorph); ITsString tssForm = null; if (hvoMf != 0) { int hvoEntry = fdoCache.GetOwnerOfObject(hvoMf); int hvoLexemeForm = sda.get_ObjectProp(hvoEntry, (int)LexEntry.LexEntryTags.kflidLexemeForm); if (hvoLexemeForm != 0) { tssForm = sda.get_MultiStringAlt(hvoLexemeForm, (int)MoForm.MoFormTags.kflidForm, wsVern); } if (tssForm == null || tssForm.Length == 0) { tssForm = fdoCache.MainCacheAccessor.get_MultiStringAlt(hvoEntry, (int)LexEntry.LexEntryTags.kflidCitationForm, wsVern); } if (tssForm.Length == 0) { // If there isn't a lexeme form OR citation form use the form of the morph. tssForm = fdoCache.MainCacheAccessor.get_MultiStringAlt(hvoMf, (int)MoForm.MoFormTags.kflidForm, wsVern); } } else // no MoForm linked to this bundle, use its own form. { tssForm = fdoCache.MainCacheAccessor.get_MultiStringAlt(hvoMb, (int)WfiMorphBundle.WfiMorphBundleTags.kflidForm, wsVern); } int ichForm = tsb.Length; tsb.ReplaceTsString(ichForm, ichForm, tssForm); tsb.SetProperties(ichForm, tsb.Length, formTextProperties); // add category (part of speech) int hvoMsa = fdoCache.GetObjProperty(hvoMb, (int)WfiMorphBundle.WfiMorphBundleTags.kflidMsa); tsb.Replace(tsb.Length, tsb.Length, " ", null); int ichMinMsa = tsb.Length; string interlinName = ksMissingString; if (hvoMsa != 0) { IMoMorphSynAnalysis msa = MoMorphSynAnalysis.CreateFromDBObject(fdoCache, hvoMsa); interlinName = msa.InterlinearAbbr; } tsb.Replace(ichMinMsa, ichMinMsa, interlinName, posTextProperties); //add sense int hvoSense = fdoCache.GetObjProperty(hvoMb, (int)WfiMorphBundle.WfiMorphBundleTags.kflidSense); tsb.Replace(tsb.Length, tsb.Length, " ", null); int ichMinSense = tsb.Length; if (hvoSense != 0) { ITsString tssGloss = fdoCache.MainCacheAccessor.get_MultiStringAlt(hvoSense, (int)LexSense.LexSenseTags.kflidGloss, fdoCache.DefaultAnalWs); tsb.Replace(ichMinSense, ichMinSense, tssGloss.Text, glossTextProperties); } else { tsb.Replace(ichMinSense, ichMinSense, ksMissingString, glossTextProperties); } // Enhance JohnT: use proper seps. tsb.Replace(tsb.Length, tsb.Length, ksPartSeparator, null); } // Delete the final separator. (Enhance JohnT: this needs to get smarter when we do // real seps.) int ichFrom = tsb.Length - ksPartSeparator.Length; if (ichFrom < 0) { ichFrom = 0; } tsb.Replace(ichFrom, tsb.Length, "", null); return(tsb.GetString()); }
/// <summary> /// the tss of the wordform form of the given hvoMatchingWordform in the ws we are querying for. /// </summary> /// <param name="sda"></param> /// <param name="tssTxtWord">the tss of the baseline at current word boundaries</param> /// <param name="tssWordAnn">the tss of the wordform form (in the baseline ws) of the annotation.</param> /// <param name="hvoMatchingWordform"></param> /// <param name="wsMatchQuery">the ws of the wordform we're looking for</param> /// <returns></returns> private ITsString GetTssWffCandidate(ISilDataAccess sda, ITsString tssTxtWord, ITsString tssWordAnn, int hvoMatchingWordform, int wsMatchQuery) { ITsString tssWff = tssWordAnn; int wsTxtWord = TsStringUtils.GetWsAtOffset(tssTxtWord, 0); if (hvoMatchingWordform == 0) { // return a candidate if it matches the ws we're trying to query. return wsMatchQuery == wsTxtWord ? tssWordAnn : null; } // if the ws of the matcher doesn't match the ws of the baseline // find the wordform in an alternative ws. if (wsMatchQuery != wsTxtWord) { tssWff = sda.get_MultiStringAlt(hvoMatchingWordform, WfiWordformTags.kflidForm, wsMatchQuery); } return tssWff; }
/// <summary> /// /// </summary> /// <param name="hvo"></param> /// <param name="tag"></param> /// <param name="ws"></param> /// <returns></returns> public ITsString get_MultiStringAlt(int hvo, int tag, int ws) { return(m_sda.get_MultiStringAlt(hvo, tag, ws)); }
private void CacheStringAltForAllCurrentWs(int[] currentWsList, IVwCacheDa cda, int hvoSec, int flidSec, ISilDataAccess sdaMain, int hvoMain, int flidMain) { foreach (int ws1 in currentWsList) cda.CacheStringAlt(hvoSec, flidSec, ws1, sdaMain.get_MultiStringAlt(hvoMain, flidMain, ws1)); }
/// <summary> /// Get the most appropriate WfiGloss from among those belonging to the given WfiAnalysis. /// The goal is to match as closely as possible the strings found in sda.get_MultiStringAlt(hvoWord, ktagSbWordGloss, ws) /// for each ws in wsIds. Ideally we find an alternative where all writing systems match exactly. /// Failing that, one where as many as possible match, and the others are blank. /// </summary> /// <param name="wfiAnalysis"></param> /// <param name="wsIds"></param> /// <param name="sda"></param> /// <param name="hvoWord"></param> /// <returns></returns> internal static IWfiGloss GetBestGloss(IWfiAnalysis wfiAnalysis, List<int> wsIds, ISilDataAccess sda, int hvoWord) { IWfiGloss best = null; int cBestMatch = 0; int cBestBlanks = 0; // number of non-matching alternatives that are blank, in the best match so far foreach (IWfiGloss possibleGloss in wfiAnalysis.MeaningsOC) { int cMatch = 0; int cBlanks = 0; // number of non-matching alternatives that are blank, in the current item. foreach (int wsId in wsIds) { ITsString tssGloss = sda.get_MultiStringAlt(hvoWord, ktagSbWordGloss, wsId); ITsString tssMainGloss = possibleGloss.Form.get_String(wsId); string sdaGloss = tssGloss.Text; string smainGloss = tssMainGloss.Text; if (tssGloss.Equals(tssMainGloss)) cMatch++; else if (tssMainGloss.Length == 0) cBlanks++; else { cMatch = int.MinValue; // non-matching alternative on the WfiGloss, this one is no good for sure cBlanks = int.MinValue; } } // This one is better if: // - it has more matches // - it has as many matches and more relevant empty alternatives // - it is as good otherwise and has fewer non-relevant alternatives. if (cMatch > cBestMatch || (cMatch == cBestMatch && cBlanks > cBestBlanks) || (cMatch == cBestMatch && cBlanks == cBestBlanks && best != null && AdditionalAlternatives(possibleGloss, wsIds) < AdditionalAlternatives(best, wsIds))) { cBestMatch = cMatch; cBestBlanks = cBlanks; best = possibleGloss; } } // we won't return something where nothing matched, just because it had a blank alternative return cBestMatch > 0 ? best : null; }
public static void UpdateMainTransFromSegmented(StTxtPara para, int[] wss) { if (!para.IsValidObject()) { return; // in merge, paragraph may be modified then deleted. } FdoCache cache = para.Cache; BtConverter.EnsureMainParaSegments(para, wss[0]); ISilDataAccess sda = cache.MainCacheAccessor; List <int> segments = para.Segments; int kflidFT = StTxtPara.SegmentFreeTranslationFlid(cache); ITsString tssContents = para.Contents.UnderlyingTsString; IScripture scr = para.Cache.LangProject.TranslatedScriptureOA; ICmTranslation originalBT = para.GetBT(); // Can be null string sUnfinished = BackTranslationStatus.Unfinished.ToString(); foreach (int ws in wss) { ITsStrBldr bldr = TsStrBldrClass.Create(); bool wantNextSpace = false; // suppresses space before the first thing we add. bool haveBtText = false; // Text that isn't segment label text foreach (int hvoSeg in segments) { // If it's a label, insert it directly. Suppress following space. int beginOffset = sda.get_IntProp(hvoSeg, (int)CmBaseAnnotation.CmBaseAnnotationTags.kflidBeginOffset); int endOffset = sda.get_IntProp(hvoSeg, (int)CmBaseAnnotation.CmBaseAnnotationTags.kflidEndOffset); ITsString tssFt; // Whether we want to insert a space before the current segment is determined by the previous one. // Save that value so we can set wantSpace appropriately for the following one. bool wantSpace = wantNextSpace; if (SegmentBreaker.HasLabelText(tssContents, beginOffset, endOffset)) { tssFt = (new CmBaseAnnotation(cache, hvoSeg)).TextAnnotated; tssFt = scr.ConvertCVNumbersInStringForBT(CorrectFootnotes(tssFt), ws); wantNextSpace = false; } else { int hvoFt = sda.get_ObjectProp(hvoSeg, kflidFT); tssFt = sda.get_MultiStringAlt(hvoFt, (int)CmAnnotation.CmAnnotationTags.kflidComment, ws); haveBtText |= (tssFt.Length > 0); wantNextSpace = EndsWithNonSpace(tssFt); } if (tssFt.Length > 0) { if (wantSpace) { // The preceding segment should typically be followed by a space. if (!StartsWithSpaceOrOrc(tssFt)) { bldr.Replace(bldr.Length, bldr.Length, " ", null); } } bldr.ReplaceTsString(bldr.Length, bldr.Length, tssFt); } } // If the back translation doesn't have text, we don't want to create verse // segment labels. This prevents the problem where the book thinks it has a // back translation because of automatically generated verse labels (TE-8283). if (!haveBtText) { // This check might not be needed, but it shouldn't hurt anything. if (originalBT != null) { if (originalBT.Translation.GetAlternative(ws).Length > 0) { string origStatus = originalBT.Status.GetAlternative(ws); if (!String.IsNullOrEmpty(origStatus) && origStatus != sUnfinished) { originalBT.Status.SetAlternative(sUnfinished, ws); } } } continue; } ITsString newFt = bldr.GetString(); ICmTranslation trans; if (newFt.Length == 0) { trans = para.GetBT(); if (trans == null) { return; // don't bother creating one to store an empty translation! } } else { trans = para.GetOrCreateBT(); } // Don't write unless it changed...PropChanged can be expensive. if (!trans.Translation.GetAlternative(ws).UnderlyingTsString.Equals(newFt)) { trans.Translation.SetAlternative(newFt, ws); trans.Status.SetAlternative(sUnfinished, ws); } } }
public override void Display(IVwEnv vwenv, int hvo, int frag) { CheckDisposed(); ITsStrFactory tsf = null; switch (frag) { case kfragStText: // The whole text, root object for the InterlinDocChild. if (hvo == 0) { return; // What if the user deleted all the texts? See LT-6727. } vwenv.set_IntProperty((int)FwTextPropType.ktptEditable, (int)FwTextPropVar.ktpvDefault, (int)TptEditable.ktptNotEditable); vwenv.OpenDiv(); StText stText = new StText(m_cache, hvo); vwenv.set_IntProperty((int)FwTextPropType.ktptMarginBottom, (int)FwTextPropVar.ktpvMilliPoint, 6000); vwenv.set_IntProperty((int)FwTextPropType.ktptFontSize, (int)FwTextPropVar.ktpvMilliPoint, 24000); // Add both vernacular and analysis if we have them (LT-5561). bool fAddedVernacular = false; int wsVernTitle = 0; // if (stText.Title.TryWs(LangProject.kwsFirstVern, out wsVernTitle)) { vwenv.OpenParagraph(); vwenv.AddStringAltMember(vtagStTextTitle, wsVernTitle, this); vwenv.CloseParagraph(); fAddedVernacular = true; } int wsAnalysisTitle = 0; vwenv.set_IntProperty((int)FwTextPropType.ktptMarginBottom, (int)FwTextPropVar.ktpvMilliPoint, 10000); vwenv.OpenParagraph(); ITsString tssAnal; if (stText.Title.TryWs(LangProject.kwsFirstAnal, out wsAnalysisTitle, out tssAnal) && !tssAnal.Equals(stText.Title.BestVernacularAlternative)) { if (fAddedVernacular) { // display analysis title at smaller font size. vwenv.set_IntProperty((int)FwTextPropType.ktptFontSize, (int)FwTextPropVar.ktpvMilliPoint, 12000); } vwenv.AddStringAltMember(vtagStTextTitle, wsAnalysisTitle, this); } else { // just add a blank title. tsf = TsStrFactoryClass.Create(); ITsString blankTitle = tsf.MakeString("", m_wsAnalysis); vwenv.AddString(blankTitle); } vwenv.CloseParagraph(); int wsSource = 0; ITsString tssSource = stText.SourceOfTextForWs(m_wsVernForDisplay); if (tssSource == null || tssSource.Length == 0) { tssSource = stText.SourceOfTextForWs(m_wsAnalysis); if (tssSource != null && tssSource.Length > 0) { wsSource = m_wsAnalysis; } } else { wsSource = m_wsVernForDisplay; } vwenv.set_IntProperty((int)FwTextPropType.ktptMarginBottom, (int)FwTextPropVar.ktpvMilliPoint, 10000); if (tssSource != null && tssSource.Length > 0) { vwenv.OpenParagraph(); vwenv.set_IntProperty((int)FwTextPropType.ktptFontSize, (int)FwTextPropVar.ktpvMilliPoint, 12000); vwenv.AddStringAltMember(vtagStTextSource, wsSource, this); vwenv.CloseParagraph(); } else { // just add a blank source. tsf = TsStrFactoryClass.Create(); ITsString tssBlank = tsf.MakeString("", m_wsAnalysis); vwenv.AddString(tssBlank); } vwenv.set_IntProperty((int)FwTextPropType.ktptMarginBottom, (int)FwTextPropVar.ktpvMilliPoint, 10000); vwenv.OpenParagraph(); if (stText.OwningFlid == (int)Text.TextTags.kflidContents) { vwenv.AddObjProp((int)CmObjectFields.kflidCmObject_Owner, this, kfragTextDescription); } vwenv.CloseParagraph(); base.Display(vwenv, hvo, frag); vwenv.CloseDiv(); break; case kfragTextDescription: vwenv.AddStringAltMember((int)CmMajorObject.CmMajorObjectTags.kflidDescription, m_wsAnalysis, this); break; case kfragSegFf: // One freeform annotation. int[] wssAnalysis = m_WsList.AnalysisWsIds; if (wssAnalysis.Length == 0) { break; // This is bizarre, but for the sake of paranoia... } tsf = TsStrFactoryClass.Create(); int hvoType = m_cache.MainCacheAccessor.get_ObjectProp(hvo, (int)CmAnnotation.CmAnnotationTags.kflidAnnotationType); string label = ""; if (hvoType == NoteSegmentDefn) { label = ITextStrings.ksNt; } else if (hvoType == FtSegmentDefn) { label = ITextStrings.ksFT; } else if (hvoType == LtSegmentDefn) { label = ITextStrings.ksLT; } else { throw new Exception("Unexpected FF annotation type"); } ITsString tssLabel = tsf.MakeString(label, m_cache.DefaultUserWs); ISilDataAccess sda = vwenv.DataAccess; if (wssAnalysis.Length == 1) { ITsString tss = sda.get_MultiStringAlt(hvo, (int)CmAnnotation.CmAnnotationTags.kflidComment, m_cache.DefaultAnalWs); if (tss.Length == 0) { break; } vwenv.OpenParagraph(); vwenv.AddString(tssLabel); vwenv.AddStringAltMember((int)CmAnnotation.CmAnnotationTags.kflidComment, m_cache.DefaultAnalWs, this); vwenv.CloseParagraph(); } else { int labelWidth, labelHeight; vwenv.get_StringWidth(tssLabel, null, out labelWidth, out labelHeight); // This roughly corresponds to the width of the space at the end of FT. // The nice way to do it (here and in the base class) would be a table or 'interlinear' paragraph. labelWidth += 3000; int cNonBlank = 0; for (int i = 0; i < wssAnalysis.Length; i++) { ITsString tss = sda.get_MultiStringAlt(hvo, (int)CmAnnotation.CmAnnotationTags.kflidComment, wssAnalysis[i]); if (tss.Length == 0) { continue; } if (cNonBlank != 0) { // Indent subsequent paragraphs by the width of the main label. vwenv.set_IntProperty((int)FwTextPropType.ktptLeadingIndent, (int)FwTextPropVar.ktpvMilliPoint, labelWidth); } vwenv.OpenParagraph(); if (cNonBlank == 0) { vwenv.AddString(tssLabel); } cNonBlank++; // after tests above! m_WsList.AddWsLabel(vwenv, i); vwenv.AddStringAltMember((int)CmAnnotation.CmAnnotationTags.kflidComment, wssAnalysis[i], this); vwenv.CloseParagraph(); } } break; default: base.Display(vwenv, hvo, frag); break; } }
static string AddMultipleAlternatives(FdoCache cache, ISilDataAccess sda, IEnumerable<int> wsIds, int hvo, int flid, XmlNode frag) { string sep = XmlUtils.GetOptionalAttributeValue(frag, "sep", null); bool fLabel = XmlUtils.GetOptionalBooleanAttributeValue(frag, "showLabels", false); // true to 'separate' using multistring labels. string result = ""; bool fFirst = true; foreach (int ws in wsIds) { string val = sda.get_MultiStringAlt(hvo, flid, ws).Text; if (string.IsNullOrEmpty(val)) continue; // doesn't even count as 'first' if (fLabel) { IWritingSystem wsObj = cache.ServiceLocator.WritingSystemManager.Get(ws); result += DisplayWsLabel(wsObj, cache); } if (fFirst) { fFirst = false; } else if (sep != null) { result = result + sep; } result += val; } return result; }