diff --git a/Src/xWorks/ConfiguredLcmGenerator.cs b/Src/xWorks/ConfiguredLcmGenerator.cs index ddcaacd052..f87bdddee3 100644 --- a/Src/xWorks/ConfiguredLcmGenerator.cs +++ b/Src/xWorks/ConfiguredLcmGenerator.cs @@ -29,6 +29,7 @@ using System.IO; using System.Linq; using System.Reflection; +using System.Runtime.Remoting.Messaging; using System.Text; using System.Text.RegularExpressions; using System.Threading; @@ -382,6 +383,34 @@ public static string GetClassNameAttributeForConfig(ConfigurableDictionaryNode c return classAtt; } + private static string PlainFieldName(string fieldname) + { + if (fieldname.EndsWith("OA") || fieldname.EndsWith("OS") || fieldname.EndsWith("OC") + || fieldname.EndsWith("RA") || fieldname.EndsWith("RS") || fieldname.EndsWith("RC")) + { + return fieldname.Substring(0, fieldname.Length - 2); + } + return fieldname; + } + + private static object GetValueFromMember(MemberInfo property, object instance) + { + switch (property.MemberType) + { + case MemberTypes.Property: + { + return ((PropertyInfo)property).GetValue(instance, new object[] { }); + } + case MemberTypes.Method: + { + // Execute the presumed extension method (passing the instance as the 'this' parameter) + return ((MethodInfo)property).Invoke(instance, new object[] { instance }); + } + default: + return null; + } + } + /// /// This method will use reflection to pull data out of the given object based on the given configuration and /// write out appropriate content using the settings parameter. @@ -429,7 +458,7 @@ internal static IFragment GenerateContentForFieldByReflection(object field, Conf { // REVIEW: We have overloaded terms here, this is a C# class not a css class, consider a different name var customFieldOwnerClassName = GetClassNameForCustomFieldParent(config, settings.Cache); - if (!GetPropValueForCustomField(field, config, cache, customFieldOwnerClassName, config.FieldDescription, ref propertyValue)) + if (!GetPropValueForCustomField(field, config, cache, publicationDecorator, customFieldOwnerClassName, config.FieldDescription, ref propertyValue)) return settings.ContentGenerator.CreateFragment(); } else @@ -454,7 +483,18 @@ internal static IFragment GenerateContentForFieldByReflection(object field, Conf #endif return settings.ContentGenerator.CreateFragment(); } - propertyValue = GetValueFromMember(property, field); + // This code demonstrates using the cache metadata, + // an alternative form of reflection to get values that respect the decorator + bool success = false; + if (field is ICmObject) + { + success = GetPropValueForCustomField(field, config, cache, publicationDecorator, + ((ICmObject)field).ClassName, PlainFieldName(property.Name), ref propertyValue); + } + + if (!success) + propertyValue = GetValueFromMember(property, field); + GetSortedReferencePropertyValue(config, ref propertyValue, field); } // If the property value is null there is nothing to generate @@ -467,7 +507,7 @@ internal static IFragment GenerateContentForFieldByReflection(object field, Conf if (config.IsCustomField) { // Get the custom field value (in SubField) using the property which came from the field object - if (!GetPropValueForCustomField(propertyValue, config, cache, ((ICmObject)propertyValue).ClassName, + if (!GetPropValueForCustomField(propertyValue, config, cache, publicationDecorator, ((ICmObject)propertyValue).ClassName, config.SubField, ref propertyValue)) { return settings.ContentGenerator.CreateFragment(); @@ -572,86 +612,92 @@ private static IFragment GenerateContentForGroupingNode(object field, Configurab /// true if the custom field was valid and false otherwise /// propertyValue can be null if the custom field is valid but no value is stored for the owning object private static bool GetPropValueForCustomField(object fieldOwner, ConfigurableDictionaryNode config, - LcmCache cache, string customFieldOwnerClassName, string customFieldName, ref object propertyValue) + LcmCache cache, ISilDataAccess decorator, string customFieldOwnerClassName, string customFieldName, ref object propertyValue) { + if (decorator == null) + decorator = cache.DomainDataByFlid; int customFieldFlid = GetCustomFieldFlid(config, cache, customFieldOwnerClassName, customFieldName); - if (customFieldFlid != 0) + if (customFieldFlid == 0) + return false; + + var customFieldType = cache.MetaDataCacheAccessor.GetFieldType(customFieldFlid); + ICmObject specificObject; + if (fieldOwner is ISenseOrEntry) { - var customFieldType = cache.MetaDataCacheAccessor.GetFieldType(customFieldFlid); - ICmObject specificObject; - if (fieldOwner is ISenseOrEntry) + specificObject = ((ISenseOrEntry)fieldOwner).Item; + if (!((IFwMetaDataCacheManaged)cache.MetaDataCacheAccessor).GetFields(specificObject.ClassID, + true, (int)CellarPropertyTypeFilter.All).Contains(customFieldFlid)) { - specificObject = ((ISenseOrEntry)fieldOwner).Item; - if (!((IFwMetaDataCacheManaged)cache.MetaDataCacheAccessor).GetFields(specificObject.ClassID, - true, (int)CellarPropertyTypeFilter.All).Contains(customFieldFlid)) - { - return false; - } - } - else - { - specificObject = (ICmObject)fieldOwner; + return false; } + } + else + { + specificObject = (ICmObject)fieldOwner; + } - switch (customFieldType) - { - case (int)CellarPropertyType.ReferenceCollection: - case (int)CellarPropertyType.OwningCollection: - // Collections are stored essentially the same as sequences. - case (int)CellarPropertyType.ReferenceSequence: - case (int)CellarPropertyType.OwningSequence: - { - var sda = cache.MainCacheAccessor; - // This method returns the hvo of the object pointed to - var chvo = sda.get_VecSize(specificObject.Hvo, customFieldFlid); - int[] contents; - using (var arrayPtr = MarshalEx.ArrayToNative(chvo)) - { - sda.VecProp(specificObject.Hvo, customFieldFlid, chvo, out chvo, arrayPtr); - contents = MarshalEx.NativeToArray(arrayPtr, chvo); - } - // if the hvo is invalid set propertyValue to null otherwise get the object - propertyValue = contents.Select(id => cache.LangProject.Services.GetObject(id)); - break; - } - case (int)CellarPropertyType.ReferenceAtomic: - case (int)CellarPropertyType.OwningAtomic: + switch (customFieldType) + { + case (int)CellarPropertyType.ReferenceCollection: + case (int)CellarPropertyType.OwningCollection: + // Collections are stored essentially the same as sequences. + case (int)CellarPropertyType.ReferenceSequence: + case (int)CellarPropertyType.OwningSequence: + { + var sda = cache.MainCacheAccessor; + // This method returns the hvo of the object pointed to + var chvo = sda.get_VecSize(specificObject.Hvo, customFieldFlid); + int[] contents; + using (var arrayPtr = MarshalEx.ArrayToNative(chvo)) { - // This method returns the hvo of the object pointed to - propertyValue = cache.MainCacheAccessor.get_ObjectProp(specificObject.Hvo, customFieldFlid); - // if the hvo is invalid set propertyValue to null otherwise get the object - propertyValue = (int)propertyValue > 0 ? cache.LangProject.Services.GetObject((int)propertyValue) : null; - break; - } - case (int)CellarPropertyType.GenDate: - { - propertyValue = new GenDate(cache.MainCacheAccessor.get_IntProp(specificObject.Hvo, customFieldFlid)); - break; + sda.VecProp(specificObject.Hvo, customFieldFlid, chvo, out chvo, arrayPtr); + contents = MarshalEx.NativeToArray(arrayPtr, chvo); } + // Convert the contents to IEnumerable + var objects = contents.Select(id => cache.LangProject.Services.GetObject(id)); + var type = objects.FirstOrDefault()?.GetType() ?? typeof(object); + var castMethod = typeof(Enumerable).GetMethod("Cast").MakeGenericMethod(type); + propertyValue = castMethod.Invoke(null, new object[] { objects }); + break; + } + case (int)CellarPropertyType.ReferenceAtomic: + case (int)CellarPropertyType.OwningAtomic: + { + // This method returns the hvo of the object pointed to + propertyValue = decorator.get_ObjectProp(specificObject.Hvo, customFieldFlid); + // if the hvo is invalid set propertyValue to null otherwise get the object + propertyValue = (int)propertyValue > 0 ? cache.LangProject.Services.GetObject((int)propertyValue) : null; + break; + } + case (int)CellarPropertyType.GenDate: + { + propertyValue = new GenDate(decorator.get_IntProp(specificObject.Hvo, customFieldFlid)); + break; + } - case (int)CellarPropertyType.Time: - { - propertyValue = SilTime.ConvertFromSilTime(cache.MainCacheAccessor.get_TimeProp(specificObject.Hvo, customFieldFlid)); - break; - } - case (int)CellarPropertyType.MultiUnicode: - case (int)CellarPropertyType.MultiString: - { - propertyValue = cache.MainCacheAccessor.get_MultiStringProp(specificObject.Hvo, customFieldFlid); - break; - } - case (int)CellarPropertyType.String: - { - propertyValue = cache.MainCacheAccessor.get_StringProp(specificObject.Hvo, customFieldFlid); - break; - } - case (int)CellarPropertyType.Integer: - { - propertyValue = cache.MainCacheAccessor.get_IntProp(specificObject.Hvo, customFieldFlid); - break; - } - } + case (int)CellarPropertyType.Time: + { + propertyValue = SilTime.ConvertFromSilTime(decorator.get_TimeProp(specificObject.Hvo, customFieldFlid)); + break; + } + case (int)CellarPropertyType.MultiUnicode: + case (int)CellarPropertyType.MultiString: + { + propertyValue = decorator.get_MultiStringProp(specificObject.Hvo, customFieldFlid); + break; + } + case (int)CellarPropertyType.String: + { + propertyValue = decorator.get_StringProp(specificObject.Hvo, customFieldFlid); + break; + } + case (int)CellarPropertyType.Integer: + { + propertyValue = decorator.get_IntProp(specificObject.Hvo, customFieldFlid); + break; + } } + return true; } @@ -706,6 +752,7 @@ private static void GetSortedReferencePropertyValue(ConfigurableDictionaryNode c { var options = config.DictionaryNodeOptions as DictionaryNodeListOptions; var unsortedReferences = propertyValue as IEnumerable; + var list = propertyValue as List; if (options == null || unsortedReferences == null || !unsortedReferences.Any()) return; // Calculate and store the ids for each of the references once for efficiency. @@ -1219,24 +1266,6 @@ private static Type GetTypeFromMember(MemberInfo property) } } - private static object GetValueFromMember(MemberInfo property, object instance) - { - switch (property.MemberType) - { - case MemberTypes.Property: - { - return ((PropertyInfo)property).GetValue(instance, new object[] {}); - } - case MemberTypes.Method: - { - // Execute the presumed extension method (passing the instance as the 'this' parameter) - return ((MethodInfo)property).Invoke(instance, new object[] {instance}); - } - default: - return null; - } - } - private static Type GetCustomFieldType(Type lookupType, ConfigurableDictionaryNode config, LcmCache cache) { // FDO doesn't work with interfaces, just concrete classes so chop the I off any interface types diff --git a/Src/xWorks/DictionaryPublicationDecorator.cs b/Src/xWorks/DictionaryPublicationDecorator.cs index 69988b8821..06b843b311 100644 --- a/Src/xWorks/DictionaryPublicationDecorator.cs +++ b/Src/xWorks/DictionaryPublicationDecorator.cs @@ -244,6 +244,15 @@ private string GetSenseNumber(ILexSense sense) return Cache.GetOutlineNumber(sense, LexSenseTags.kflidSenses, false, true, this); } + public override ITsMultiString get_MultiStringProp(int hvo, int tag) + { + if (tag == m_mlHeadwordFlid) + { + return new PublicationAwareMultiStringAccessor(hvo, tag, this); + } + return base.get_MultiStringProp(hvo, tag); + } + public override ITsString get_MultiStringAlt(int hvo, int tag, int ws) { if (tag == m_mlHeadwordFlid) @@ -615,5 +624,83 @@ private bool IsPublishableReference(ILexEntryRef entryRef) // A reference is also not publishable if all of its PrimarySensesOrEntries are excluded return entryRef.PrimarySensesOrEntries.Any(senseOrEntry => !m_excludedItems.Contains(senseOrEntry.Item.Hvo)); } + + private class PublicationAwareMultiStringAccessor : IMultiAccessorBase + { + private readonly int m_hvo; + private readonly int m_tag; + private readonly DictionaryPublicationDecorator m_decorator; + + public PublicationAwareMultiStringAccessor(int hvo, int tag, DictionaryPublicationDecorator decorator) + { + m_hvo = hvo; + m_tag = tag; + m_decorator = decorator; + } + + public ITsString GetStringFromIndex(int iws, out int _ws) + { + throw new NotImplementedException(); + } + + public ITsString get_String(int ws) + { + return m_decorator.get_MultiStringAlt(m_hvo, m_tag, ws); + } + + public void set_String(int ws, ITsString _tss) + { + throw new NotImplementedException(); + } + + public int StringCount { get; } + public void SetAnalysisDefaultWritingSystem(string val) + { + throw new NotImplementedException(); + } + + public void SetVernacularDefaultWritingSystem(string val) + { + throw new NotImplementedException(); + } + + public void SetUserWritingSystem(string val) + { + throw new NotImplementedException(); + } + + public void set_String(int ws, string val) + { + throw new NotImplementedException(); + } + + public bool TryWs(int ws, out int actualWs) + { + throw new NotImplementedException(); + } + + public bool TryWs(int ws, out int actualWs, out ITsString tssActual) + { + throw new NotImplementedException(); + } + + public ITsString StringOrNull(int ws) + { + throw new NotImplementedException(); + } + + public int Flid { get; } + public ITsString NotFoundTss { get; } + public ITsString AnalysisDefaultWritingSystem { get; set; } + public ITsString VernacularDefaultWritingSystem { get; set; } + public string UiString { get; } + public ITsString UserDefaultWritingSystem { get; set; } + public ITsString RawUserDefaultWritingSystem { get; } + public ITsString BestAnalysisVernacularAlternative { get; } + public ITsString BestAnalysisAlternative { get; } + public ITsString BestVernacularAlternative { get; } + public ITsString BestVernacularAnalysisAlternative { get; } + public int[] AvailableWritingSystemIds { get; } + } } } diff --git a/x64/Debug/System.ValueTuple.dll b/x64/Debug/System.ValueTuple.dll new file mode 100644 index 0000000000..1cadbf3ee3 Binary files /dev/null and b/x64/Debug/System.ValueTuple.dll differ