XPathAdapter
public class XPathAdapter : DictionaryBehaviorAttribute, IDictionaryInitializer, IDictionaryPropertyGetter, IDictionaryPropertySetter, IDictionaryBehavior, IDictionaryCreateStrategy, IDictionaryCopyStrategy
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 (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 (XPathNavigator source, XPathNodeType to)
{
if (source.NodeType == XPathNodeType.Root)
return source.MoveToChild(to);
return true;
}
}
}