<PackageReference Include="Castle.Core" Version="3.0.0.2001" />

XPathAdapter

using System; using System.Collections; using System.Collections.Generic; using System.Linq; using System.Xml; using System.Xml.Serialization; using System.Xml.XPath; namespace Castle.Components.DictionaryAdapter { public class XPathAdapter : DictionaryBehaviorAttribute, IDictionaryInitializer, IDictionaryPropertyGetter, IDictionaryPropertySetter, IDictionaryBehavior, IDictionaryCreateStrategy, IDictionaryCopyStrategy { private readonly Func<XPathNavigator> createRoot; private Dictionary<Type, XPathContext> overlays; private XmlMetadata rootXmlMeta; private XPathNavigator root; private static readonly XPathExpression XPathElement = XPathExpression.Compile("*[castle-da:match($key,$ns)]"); private static readonly XPathExpression XPathAttribute = XPathExpression.Compile("@*[castle-da:match($key,$ns)]"); private static readonly XPathExpression XPathElementOrAttribute = XPathExpression.Compile("(*|@*)[castle-da:match($key,$ns)]"); public XPathNavigator Root => EnsureOffRoot(); public XPathAdapter Parent { get; set; } public IXPathNavigable Source { get; set; } public XPathContext Context { get; set; } public XPathAdapter() : this(new XmlDocument()) { } public XPathAdapter(IXPathNavigable source) { Source = source; Context = new XPathContext(); root = source.CreateNavigator(); } protected XPathAdapter(XPathNavigator source, XPathAdapter parent) { Parent = parent; Context = parent.Context.CreateChild(null); root = source.Clone(); } protected XPathAdapter(Func<XPathNavigator> createSource, XPathAdapter parent) { Parent = parent; Context = parent.Context.CreateChild(null); createRoot = createSource; } void IDictionaryInitializer.Initialize(IDictionaryAdapter dictionaryAdapter, object[] behaviors) { DictionaryAdapterMeta meta = dictionaryAdapter.Meta; if (meta.MetaInitializers.OfType<XPathBehavior>().FirstOrDefault() == null) throw new InvalidOperationException($"""{meta.Type.FullName}"""); XmlMetadata xmlMeta = dictionaryAdapter.GetXmlMeta(); if (dictionaryAdapter.This.CreateStrategy == null) { dictionaryAdapter.This.CreateStrategy = this; dictionaryAdapter.This.AddCopyStrategy(this); } if (rootXmlMeta == null) { rootXmlMeta = xmlMeta; Context.ApplyBehaviors(rootXmlMeta, behaviors); if (Parent == null) { foreach (object obj in behaviors) { if (obj is XPathAttribute) { XPathAttribute xPathAttribute = (XPathAttribute)obj; XPathExpression compiledExpression = xPathAttribute.CompiledExpression; if (!MoveOffRoot(root, XPathNodeType.Element)) break; if (Context.Matches(compiledExpression, root)) break; XPathNavigator xPathNavigator = Context.SelectSingleNode(compiledExpression, root); if (xPathNavigator != null) { root = xPathNavigator; break; } } } MoveOffRoot(root, XPathNodeType.Element); } } else { if (overlays == null) overlays = new Dictionary<Type, XPathContext>(); if (!overlays.TryGetValue(meta.Type, out XPathContext value)) { value = new XPathContext().ApplyBehaviors(xmlMeta, behaviors); overlays.Add(meta.Type, value); } } } object IDictionaryPropertyGetter.GetPropertyValue(IDictionaryAdapter dictionaryAdapter, string key, object storedValue, PropertyDescriptor property, bool ifExists) { if (!ShouldIgnoreProperty(property) && (storedValue == null || IsVolatileProperty(dictionaryAdapter, property))) { XPathResult result = EvaluateProperty(key, property, dictionaryAdapter); storedValue = ReadProperty(result, ifExists, dictionaryAdapter); if (storedValue != null) dictionaryAdapter.StoreProperty(property, key, storedValue); } return storedValue; } bool IDictionaryPropertySetter.SetPropertyValue(IDictionaryAdapter dictionaryAdapter, string key, ref object value, PropertyDescriptor property) { if (!ShouldIgnoreProperty(property)) { EnsureOffRoot(); if (root.CanEdit) { XPathResult xPathResult = EvaluateProperty(key, property, dictionaryAdapter); if (xPathResult.CanWrite) WriteProperty(xPathResult, ref value, dictionaryAdapter); } } return true; } object IDictionaryCreateStrategy.Create(IDictionaryAdapter adapter, Type type, IDictionary dictionary) { return Create(adapter, type, dictionary, new XPathAdapter(new XmlDocument())); } private static object Create(IDictionaryAdapter adapter, Type type, IDictionary dictionary, XPathAdapter xpathAdapter) { dictionary = (dictionary ?? new Hashtable()); DictionaryDescriptor dictionaryDescriptor = new DictionaryDescriptor(adapter.Meta.Behaviors); adapter.This.Descriptor.CopyBehaviors(dictionaryDescriptor); dictionaryDescriptor.AddBehavior(xpathAdapter); return adapter.This.Factory.GetAdapter(type, dictionary, dictionaryDescriptor); } bool IDictionaryCopyStrategy.Copy(IDictionaryAdapter source, IDictionaryAdapter target, ref Func<PropertyDescriptor, bool> selector) { selector = (selector ?? ((Func<PropertyDescriptor, bool>)((PropertyDescriptor property) => IsPropertyDefined(property.PropertyName, source, this)))); return false; } public override IDictionaryBehavior Copy() { return null; } private object ReadProperty(XPathResult result, bool ifExists, IDictionaryAdapter dictionaryAdapter) { Type type = result.Type; if (ReadCustom(result, out object value)) return value; if ((object)type != typeof(string)) { if (typeof(IXPathNavigable).IsAssignableFrom(type)) return ReadFragment(result); if (type.IsArray || typeof(IEnumerable).IsAssignableFrom(type)) return ReadCollection(result, ifExists, dictionaryAdapter); if (type.IsInterface) return ReadComponent(result, ifExists, dictionaryAdapter); } return ReadSimple(result); } private object ReadFragment(XPathResult result) { result.GetNavigator(false, true, out XPathNavigator result2); if (result2 == null) return null; if ((object)result.Type == typeof(XmlElement)) { XmlDocument xmlDocument = new XmlDocument(); xmlDocument.Load(result2.ReadSubtree()); return xmlDocument.DocumentElement; } return result2.Clone(); } private object ReadSimple(XPathResult result) { if (result.GetNavigator(false, true, out XPathNavigator result2)) { if (result2 != null) { if (!IsNullableType(result.Type, out Type underlyingType)) underlyingType = result.Type; if (underlyingType.IsEnum) return Enum.Parse(underlyingType, result2.Value); if ((object)underlyingType == typeof(Guid)) return new Guid(result2.Value); try { return result2.ValueAs(underlyingType); } catch (InvalidCastException) { if (DefaultXmlSerializer.Instance.ReadObject(result, result2, out object value)) return value; } } if (result.Result != null) return Convert.ChangeType(result.Result, result.Type); } return null; } private object ReadComponent(XPathResult result, bool ifExists, IDictionaryAdapter dictionaryAdapter) { if (result.Property != null) ifExists = (ifExists || dictionaryAdapter.Meta.Type.IsAssignableFrom(result.Property.PropertyType)); if (!result.GetNavigator(false, true, out XPathNavigator result2) || (result2 == null && ifExists)) return null; Type type = result.Type; XPathAdapter xpathAdapter; if (result2 != null) { if (result.XmlMeta != null) type = result.XmlMeta.Type; else { XmlQualifiedName xmlType = GetEffectiveContext(dictionaryAdapter).GetXmlType(result2); type = (dictionaryAdapter.GetXmlSubclass(xmlType, type) ?? type); } xpathAdapter = new XPathAdapter(result2, this); } else { Func<XPathNavigator> createSource = () => result.GetNavigator(true); xpathAdapter = new XPathAdapter(createSource, this); } return Create(dictionaryAdapter, type, null, xpathAdapter); } private object ReadCollection(XPathResult result, bool ifExists, IDictionaryAdapter dictionaryAdapter) { if (ifExists && result.Result == null) return null; if (result.Type.IsArray) return ReadArray(result, dictionaryAdapter); if (result.Type.IsGenericType) return ReadList(result, dictionaryAdapter); return null; } private object ReadArray(XPathResult result, IDictionaryAdapter dictionaryAdapter) { if ((object)result.Type == typeof(byte[])) { if (result.GetNavigator(false, true, out XPathNavigator result2) && result2 != null) return Convert.FromBase64String(result2.InnerXml); return null; } Type elementType = result.Type.GetElementType(); IEnumerable<XPathResult> nodes = result.GetNodes(elementType, (Type type) => dictionaryAdapter.GetXmlMeta(type)); if (nodes == null) return null; object[] array = (from node in nodes select ReadProperty(node, false, dictionaryAdapter)).ToArray(); Array array2 = Array.CreateInstance(elementType, array.Length); array.CopyTo(array2, 0); return array2; } private object ReadList(XPathResult result, IDictionaryAdapter dictionaryAdapter) { Type type2 = null; Type type3 = null; Type[] genericArguments = result.Type.GetGenericArguments(); Type genericTypeDefinition = result.Type.GetGenericTypeDefinition(); Type itemType = genericArguments[0]; Func<Type, XmlMetadata> getXmlMeta = (Type type) => dictionaryAdapter.GetXmlMeta(type); IEnumerable<XPathResult> nodes = result.GetNodes(itemType, getXmlMeta); if (nodes == null) return null; if ((object)genericTypeDefinition == typeof(IEnumerable<>) || (object)genericTypeDefinition == typeof(ICollection<>) || (object)genericTypeDefinition == typeof(List<>)) type2 = typeof(EditableList<>).MakeGenericType(genericArguments); else if ((object)genericTypeDefinition == typeof(HashSet<>)) { type2 = typeof(List<>).MakeGenericType(genericArguments); } else { type2 = typeof(EditableBindingList<>).MakeGenericType(genericArguments); type3 = typeof(BindingListInitializer<>).MakeGenericType(genericArguments); } IList list = (IList)Activator.CreateInstance(type2); foreach (XPathResult item in nodes) { list.Add(ReadProperty(item, false, dictionaryAdapter)); } if ((object)genericTypeDefinition == typeof(HashSet<>)) return Activator.CreateInstance(typeof(HashSet<>).MakeGenericType(genericArguments), list); if ((object)type3 != null) { Func<object> func = delegate { XPathResult result3 = result.CreateNode(itemType, null, getXmlMeta); return ReadProperty(result3, false, dictionaryAdapter); }; Func<int, object, object> func2 = delegate(int index, object item) { XPathResult result2 = result.CreateNode(itemType, item, getXmlMeta); WriteProperty(result2, ref item, dictionaryAdapter); return item; }; Func<int, object, object> func3 = delegate(int index, object item) { XPathResult nodeAt = result.GetNodeAt(itemType, index); WriteProperty(nodeAt, ref item, dictionaryAdapter); return item; }; Action<int> action = delegate(int index) { object value2 = list; if (dictionaryAdapter.ShouldClearProperty(result.Property, value2)) result.Remove(true); else result.RemoveAt(index); }; Action action2 = delegate { object value = list; if (dictionaryAdapter.ShouldClearProperty(result.Property, value)) result.Remove(true); else WriteCollection(result, ref value, dictionaryAdapter); }; IValueInitializer valueInitializer = (IValueInitializer)Activator.CreateInstance(type3, func2, func, func3, action, action2); valueInitializer.Initialize(dictionaryAdapter, list); } return list; } private bool ReadCustom(XPathResult result, out object value) { return result.ReadObject(out value); } private void WriteProperty(XPathResult result, ref object value, IDictionaryAdapter dictionaryAdapter) { Type type = result.Type; bool flag = value == null; if (result.Property != null) flag = (flag || dictionaryAdapter.ShouldClearProperty(result.Property, value)); if (flag) { result.Remove(true); value = null; } else if (!WriteCustom(result, value, dictionaryAdapter)) { if ((object)type == typeof(string)) WriteSimple(result, value, dictionaryAdapter); else if (typeof(IXPathNavigable).IsAssignableFrom(type)) { WriteFragment(result, (IXPathNavigable)value); } else if (type.IsArray || typeof(IEnumerable).IsAssignableFrom(type)) { WriteCollection(result, ref value, dictionaryAdapter); } else if (type.IsInterface) { WriteComponent(result, ref value, dictionaryAdapter); } else { WriteSimple(result, value, dictionaryAdapter); } } } private void WriteFragment(XPathResult result, IXPathNavigable value) { XPathNavigator navigator = result.GetNavigator(true); if (navigator == null) root.AppendChild(value.CreateNavigator()); else if (value != null) { navigator.ReplaceSelf(value.CreateNavigator()); } else { navigator.DeleteSelf(); } } private void WriteSimple(XPathResult result, object value, IDictionaryAdapter dictionaryAdapter) { XPathNavigator navigator = result.GetNavigator(true); if (!result.Type.IsEnum && (object)result.Type != typeof(Guid)) try { navigator.SetTypedValue(value); } catch (InvalidCastException) { DefaultXmlSerializer.Instance.WriteObject(result, navigator, value); } else navigator.SetTypedValue(value.ToString()); } private void WriteComponent(XPathResult result, ref object value, IDictionaryAdapter dictionaryAdapter) { IDictionaryAdapter dictionaryAdapter2 = value as IDictionaryAdapter; if (dictionaryAdapter2 != null) { XPathAdapter xPathAdapter = For(dictionaryAdapter2); if (xPathAdapter != null) { XPathNavigator xPathNavigator = xPathAdapter.Root; XPathNavigator navigator = result.GetNavigator(false); if (xPathNavigator != null && navigator != null && xPathNavigator.IsSamePosition(navigator)) return; } XPathNavigator source = result.RemoveChildren(); if ((object)result.Type != dictionaryAdapter2.Meta.Type && !result.OmitPolymorphism) { XmlTypeAttribute xmlType = dictionaryAdapter2.GetXmlMeta().XmlType; XPathContext effectiveContext = GetEffectiveContext(dictionaryAdapter); effectiveContext.SetXmlType(xmlType.TypeName, xmlType.Namespace, source); } IDictionaryAdapter dictionaryAdapter3 = (IDictionaryAdapter)ReadComponent(result, false, dictionaryAdapter); dictionaryAdapter2.CopyTo(dictionaryAdapter3); value = dictionaryAdapter3; } } private void WriteCollection(XPathResult result, ref object value, IDictionaryAdapter dictionaryAdapter) { if (value is byte[]) { XPathNavigator navigator = result.GetNavigator(true); navigator.SetValue(Convert.ToBase64String((byte[])value)); } else { result.Remove(value == null); if (value != null) { if (result.Type.IsArray) WriteArray(result, value, dictionaryAdapter); else if (result.Type.IsGenericType) { WriteList(result, value, dictionaryAdapter); } if (result.Property != null) value = ((IDictionaryPropertyGetter)this).GetPropertyValue(dictionaryAdapter, result.Key, (object)null, result.Property, false); } } } private void WriteArray(XPathResult result, object value, IDictionaryAdapter dictionaryAdapter) { Array array = (Array)value; Type elementType = array.GetType().GetElementType(); foreach (object item in array) { object value2 = item; XPathResult result2 = result.CreateNode(elementType, value2, (Type type) => dictionaryAdapter.GetXmlMeta(type)); WriteProperty(result2, ref value2, dictionaryAdapter); } } private void WriteList(XPathResult result, object value, IDictionaryAdapter dictionaryAdapter) { Type[] genericArguments = result.Type.GetGenericArguments(); Type type2 = genericArguments[0]; foreach (object item in (IEnumerable)value) { object value2 = item; XPathResult result2 = result.CreateNode(type2, value2, (Type type) => dictionaryAdapter.GetXmlMeta(type)); WriteProperty(result2, ref value2, dictionaryAdapter); } } private bool WriteCustom(XPathResult result, object value, IDictionaryAdapter dictionaryAdapter) { return result.WriteObject(value); } public static XPathAdapter For(object adapter) { if (adapter == null) throw new ArgumentNullException("adapter"); IDictionaryAdapter dictionaryAdapter = adapter as IDictionaryAdapter; if (dictionaryAdapter != null && dictionaryAdapter.This.Descriptor != null) { XPathAdapter xPathAdapter = null; ICollection<IDictionaryPropertyGetter> getters = dictionaryAdapter.This.Descriptor.Getters; if (getters != null) xPathAdapter = getters.OfType<XPathAdapter>().SingleOrDefault(); if (xPathAdapter != null) return xPathAdapter; } return null; } public static bool IsPropertyDefined(string propertyName, IDictionaryAdapter dictionaryAdapter) { XPathAdapter xPathAdapter = For(dictionaryAdapter); if (xPathAdapter == null) return false; return IsPropertyDefined(propertyName, dictionaryAdapter, xPathAdapter); } public static bool IsPropertyDefined(string propertyName, IDictionaryAdapter dictionaryAdapter, XPathAdapter xpath) { string key = dictionaryAdapter.GetKey(propertyName); if (key == null) return false; PropertyDescriptor property = dictionaryAdapter.This.Properties[propertyName]; XPathResult xPathResult = xpath.EvaluateProperty(key, property, dictionaryAdapter); return xPathResult.GetNavigator(false) != null; } private XPathContext GetEffectiveContext(IDictionaryAdapter dictionaryAdapter) { if (overlays != null && overlays.TryGetValue(dictionaryAdapter.Meta.Type, out XPathContext value)) return value; return Context; } private XPathResult EvaluateProperty(string key, PropertyDescriptor property, IDictionaryAdapter dictionaryAdapter) { XPathExpression xPathExpression = null; object obj = null; Func<XPathNavigator> func = null; XPathContext effectiveContext = GetEffectiveContext(dictionaryAdapter); XmlMetadata xmlMeta = dictionaryAdapter.GetXmlMeta(property.Property.DeclaringType); XPathContext keyContext = effectiveContext.CreateChild(xmlMeta, property.Behaviors); object[] behaviors = property.Behaviors; object result; foreach (object obj2 in behaviors) { string ns = null; Func<XPathNavigator> func2 = null; if (obj2 is XmlElementAttribute) { xPathExpression = XPathElement; XmlElementAttribute xmlElementAttribute = (XmlElementAttribute)obj2; if (!string.IsNullOrEmpty(xmlElementAttribute.ElementName)) key = xmlElementAttribute.ElementName; if (!string.IsNullOrEmpty(xmlElementAttribute.Namespace)) ns = xmlElementAttribute.Namespace; func2 = (() => keyContext.AppendElement(key, ns, root.Clone())); } else if (obj2 is XmlAttributeAttribute) { xPathExpression = XPathAttribute; XmlAttributeAttribute xmlAttributeAttribute = (XmlAttributeAttribute)obj2; if (!string.IsNullOrEmpty(xmlAttributeAttribute.AttributeName)) key = xmlAttributeAttribute.AttributeName; if (!string.IsNullOrEmpty(xmlAttributeAttribute.Namespace)) ns = xmlAttributeAttribute.Namespace; func2 = (() => keyContext.CreateAttribute(key, ns, root.Clone())); } else if (obj2 is XmlArrayAttribute) { xPathExpression = XPathElement; XmlArrayAttribute xmlArrayAttribute = (XmlArrayAttribute)obj2; if (!string.IsNullOrEmpty(xmlArrayAttribute.ElementName)) key = xmlArrayAttribute.ElementName; if (!string.IsNullOrEmpty(xmlArrayAttribute.Namespace)) ns = xmlArrayAttribute.Namespace; func2 = (() => keyContext.AppendElement(key, ns, root.Clone())); } else { if (!(obj2 is XPathAttribute)) continue; XPathAttribute xPathAttribute = (XPathAttribute)obj2; xPathExpression = xPathAttribute.CompiledExpression; } if (xPathExpression != null) { keyContext.Arguments.Clear(); keyContext.Arguments.AddParam("key", "", key); keyContext.Arguments.AddParam("ns", "", ns ?? "_"); if (keyContext.Evaluate(xPathExpression, Root, out result)) { func = (func2 ?? func); return new XPathResult(property, key, result, keyContext, obj2, func); } } obj = (obj ?? obj2); func = (func ?? func2); } if (xPathExpression != null) return new XPathResult(property, key, null, keyContext, obj, func); keyContext.Arguments.Clear(); keyContext.Arguments.AddParam("key", "", key); keyContext.Arguments.AddParam("ns", "", "_"); func = (func ?? ((Func<XPathNavigator>)(() => keyContext.AppendElement(key, null, root)))); keyContext.Evaluate(XPathElementOrAttribute, Root, out result); return new XPathResult(property, key, result, keyContext, null, func); } private static bool ShouldIgnoreProperty(PropertyDescriptor property) { return property.Behaviors.Any((object behavior) => behavior is XmlIgnoreAttribute); } private static bool IsVolatileProperty(IDictionaryAdapter dictionaryAdapter, PropertyDescriptor property) { return dictionaryAdapter.Meta.Behaviors.Union(property.Behaviors).Any((object behavior) => behavior is VolatileAttribute); } private static bool IsNullableType(Type type, out Type underlyingType) { if (type.IsGenericType && (object)type.GetGenericTypeDefinition() == typeof(Nullable<>)) { underlyingType = type.GetGenericArguments()[0]; return true; } underlyingType = null; return false; } private XPathNavigator EnsureOffRoot() { if (root == null && createRoot != null) root = createRoot().Clone(); if (root != null && !MoveOffRoot(root, XPathNodeType.Element)) { string namespaceUri = string.Empty; XmlRootAttribute xmlRoot = rootXmlMeta.XmlRoot; string name; if (xmlRoot != null) { name = xmlRoot.ElementName; namespaceUri = xmlRoot.Namespace; } else name = rootXmlMeta.XmlType.TypeName; root = Context.AppendElement(name, namespaceUri, root); Context.AddStandardNamespaces(root); } return root; } private static bool MoveOffRoot(XPathNavigator source, XPathNodeType to) { if (source.NodeType == XPathNodeType.Root) return source.MoveToChild(to); return true; } } }