1 // Licensed to the .NET Foundation under one or more agreements.
2 // The .NET Foundation licenses this file to you under the MIT license.
5 using System.Collections.Generic;
6 using System.Diagnostics.CodeAnalysis;
7 using System.Globalization;
8 using System.Reflection;
9 using System.Runtime.Serialization.DataContracts;
13 namespace System.Runtime.Serialization
17 public static class XPathQueryGenerator
19 private const string XPathSeparator = "/";
20 private const string NsSeparator = ":";
22 [RequiresDynamicCode(DataContract.SerializerAOTWarning)]
23 [RequiresUnreferencedCode(DataContract.SerializerTrimmerWarning)]
24 public static string CreateFromDataContractSerializer(Type type, MemberInfo[] pathToMember, out XmlNamespaceManager namespaces)
26 return CreateFromDataContractSerializer(type, pathToMember, null, out namespaces);
29 // Here you can provide your own root element Xpath which will replace the Xpath of the top level element
30 [RequiresDynamicCode(DataContract.SerializerAOTWarning)]
31 [RequiresUnreferencedCode(DataContract.SerializerTrimmerWarning)]
32 public static string CreateFromDataContractSerializer(Type type, MemberInfo[] pathToMember, StringBuilder? rootElementXpath, out XmlNamespaceManager namespaces)
34 ArgumentNullException.ThrowIfNull(type);
35 ArgumentNullException.ThrowIfNull(pathToMember);
37 DataContract currentContract = DataContract.GetDataContract(type);
38 ExportContext context;
40 if (rootElementXpath == null)
42 context = new ExportContext(currentContract);
46 // use the provided xpath for top level element
47 context = new ExportContext(rootElementXpath);
50 for (int pathToMemberIndex = 0; pathToMemberIndex < pathToMember.Length; pathToMemberIndex++)
52 currentContract = ProcessDataContract(currentContract, context, pathToMember[pathToMemberIndex]);
55 namespaces = context.Namespaces;
59 [RequiresDynamicCode(DataContract.SerializerAOTWarning)]
60 [RequiresUnreferencedCode(DataContract.SerializerTrimmerWarning)]
61 private static DataContract ProcessDataContract(DataContract contract, ExportContext context, MemberInfo memberNode)
63 if (contract is ClassDataContract)
65 return ProcessClassDataContract((ClassDataContract)contract, context, memberNode);
67 throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(XmlObjectSerializer.CreateSerializationException(SR.QueryGeneratorPathToMemberNotFound));
70 [RequiresDynamicCode(DataContract.SerializerAOTWarning)]
71 [RequiresUnreferencedCode(DataContract.SerializerTrimmerWarning)]
72 private static DataContract ProcessClassDataContract(ClassDataContract contract, ExportContext context, MemberInfo memberNode)
74 string prefix = context.SetNamespace(contract.Namespace!.Value);
75 foreach (DataMember member in GetDataMembers(contract))
77 if (member.MemberInfo.Name == memberNode.Name && member.MemberInfo.DeclaringType!.IsAssignableFrom(memberNode.DeclaringType))
79 context.WriteChildToContext(member, prefix);
80 return member.MemberTypeContract;
83 throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(XmlObjectSerializer.CreateSerializationException(SR.QueryGeneratorPathToMemberNotFound));
86 private static IEnumerable<DataMember> GetDataMembers(ClassDataContract contract)
88 if (contract.BaseClassContract != null)
90 foreach (DataMember baseClassMember in GetDataMembers(contract.BaseClassContract))
92 yield return baseClassMember;
95 if (contract.Members != null)
97 foreach (DataMember member in contract.Members)
104 private sealed class ExportContext
106 private readonly XmlNamespaceManager _namespaces;
107 private int _nextPrefix;
108 private readonly StringBuilder _xPathBuilder;
110 public ExportContext(DataContract rootContract)
112 _namespaces = new XmlNamespaceManager(new NameTable());
113 string prefix = SetNamespace(rootContract.TopLevelElementNamespace!.Value);
114 _xPathBuilder = new StringBuilder(XPathQueryGenerator.XPathSeparator + prefix + XPathQueryGenerator.NsSeparator + rootContract.TopLevelElementName!.Value);
117 public ExportContext(StringBuilder rootContractXPath)
119 _namespaces = new XmlNamespaceManager(new NameTable());
120 _xPathBuilder = rootContractXPath;
123 public void WriteChildToContext(DataMember contextMember, string prefix)
125 _xPathBuilder.Append(XPathQueryGenerator.XPathSeparator + prefix + XPathQueryGenerator.NsSeparator + contextMember.Name);
128 public XmlNamespaceManager Namespaces
140 return _xPathBuilder.ToString();
144 public string SetNamespace(string ns)
146 string? prefix = _namespaces.LookupPrefix(ns);
147 if (prefix == null || prefix.Length == 0)
149 prefix = "xg" + (_nextPrefix++).ToString(NumberFormatInfo.InvariantInfo);
150 Namespaces.AddNamespace(prefix, ns);