afe290612fab45c6447a3211d2d40193147b0ae4
[platform/upstream/dotnet/runtime.git] /
1 // Licensed to the .NET Foundation under one or more agreements.
2 // The .NET Foundation licenses this file to you under the MIT license.
3
4 using System;
5 using System.Collections.Generic;
6 using System.Diagnostics.CodeAnalysis;
7 using System.Globalization;
8 using System.Reflection;
9 using System.Runtime.Serialization.DataContracts;
10 using System.Text;
11 using System.Xml;
12
13 namespace System.Runtime.Serialization
14 {
15
16
17     public static class XPathQueryGenerator
18     {
19         private const string XPathSeparator = "/";
20         private const string NsSeparator = ":";
21
22         [RequiresDynamicCode(DataContract.SerializerAOTWarning)]
23         [RequiresUnreferencedCode(DataContract.SerializerTrimmerWarning)]
24         public static string CreateFromDataContractSerializer(Type type, MemberInfo[] pathToMember, out XmlNamespaceManager namespaces)
25         {
26             return CreateFromDataContractSerializer(type, pathToMember, null, out namespaces);
27         }
28
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)
33         {
34             ArgumentNullException.ThrowIfNull(type);
35             ArgumentNullException.ThrowIfNull(pathToMember);
36
37             DataContract currentContract = DataContract.GetDataContract(type);
38             ExportContext context;
39
40             if (rootElementXpath == null)
41             {
42                 context = new ExportContext(currentContract);
43             }
44             else
45             {
46                 // use the provided xpath for top level element
47                 context = new ExportContext(rootElementXpath);
48             }
49
50             for (int pathToMemberIndex = 0; pathToMemberIndex < pathToMember.Length; pathToMemberIndex++)
51             {
52                 currentContract = ProcessDataContract(currentContract, context, pathToMember[pathToMemberIndex]);
53             }
54
55             namespaces = context.Namespaces;
56             return context.XPath;
57         }
58
59         [RequiresDynamicCode(DataContract.SerializerAOTWarning)]
60         [RequiresUnreferencedCode(DataContract.SerializerTrimmerWarning)]
61         private static DataContract ProcessDataContract(DataContract contract, ExportContext context, MemberInfo memberNode)
62         {
63             if (contract is ClassDataContract)
64             {
65                 return ProcessClassDataContract((ClassDataContract)contract, context, memberNode);
66             }
67             throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(XmlObjectSerializer.CreateSerializationException(SR.QueryGeneratorPathToMemberNotFound));
68         }
69
70         [RequiresDynamicCode(DataContract.SerializerAOTWarning)]
71         [RequiresUnreferencedCode(DataContract.SerializerTrimmerWarning)]
72         private static DataContract ProcessClassDataContract(ClassDataContract contract, ExportContext context, MemberInfo memberNode)
73         {
74             string prefix = context.SetNamespace(contract.Namespace!.Value);
75             foreach (DataMember member in GetDataMembers(contract))
76             {
77                 if (member.MemberInfo.Name == memberNode.Name && member.MemberInfo.DeclaringType!.IsAssignableFrom(memberNode.DeclaringType))
78                 {
79                     context.WriteChildToContext(member, prefix);
80                     return member.MemberTypeContract;
81                 }
82             }
83             throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(XmlObjectSerializer.CreateSerializationException(SR.QueryGeneratorPathToMemberNotFound));
84         }
85
86         private static IEnumerable<DataMember> GetDataMembers(ClassDataContract contract)
87         {
88             if (contract.BaseClassContract != null)
89             {
90                 foreach (DataMember baseClassMember in GetDataMembers(contract.BaseClassContract))
91                 {
92                     yield return baseClassMember;
93                 }
94             }
95             if (contract.Members != null)
96             {
97                 foreach (DataMember member in contract.Members)
98                 {
99                     yield return member;
100                 }
101             }
102         }
103
104         private sealed class ExportContext
105         {
106             private readonly XmlNamespaceManager _namespaces;
107             private int _nextPrefix;
108             private readonly StringBuilder _xPathBuilder;
109
110             public ExportContext(DataContract rootContract)
111             {
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);
115             }
116
117             public ExportContext(StringBuilder rootContractXPath)
118             {
119                 _namespaces = new XmlNamespaceManager(new NameTable());
120                 _xPathBuilder = rootContractXPath;
121             }
122
123             public void WriteChildToContext(DataMember contextMember, string prefix)
124             {
125                 _xPathBuilder.Append(XPathQueryGenerator.XPathSeparator + prefix + XPathQueryGenerator.NsSeparator + contextMember.Name);
126             }
127
128             public XmlNamespaceManager Namespaces
129             {
130                 get
131                 {
132                     return _namespaces;
133                 }
134             }
135
136             public string XPath
137             {
138                 get
139                 {
140                     return _xPathBuilder.ToString();
141                 }
142             }
143
144             public string SetNamespace(string ns)
145             {
146                 string? prefix = _namespaces.LookupPrefix(ns);
147                 if (prefix == null || prefix.Length == 0)
148                 {
149                     prefix = "xg" + (_nextPrefix++).ToString(NumberFormatInfo.InvariantInfo);
150                     Namespaces.AddNamespace(prefix, ns);
151                 }
152                 return prefix;
153             }
154         }
155     }
156 }