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 // See the LICENSE file in the project root for more information.
4 
5 using System.IO;
6 using System.Runtime;
7 using System.Runtime.Serialization;
8 using System.Xml;
9 using System.Xml.Serialization;
10 using System.Runtime.CompilerServices;
11 using System.Diagnostics;
12 
13 namespace System.ServiceModel.Syndication
14 {
15     public class SyndicationElementExtension
16     {
17         private XmlBuffer _buffer;
18         private int _bufferElementIndex;
19         // extensionData and extensionDataWriter are only present on the send side
20         private object _extensionData;
21         private ExtensionDataWriter _extensionDataWriter;
22         private string _outerName;
23         private string _outerNamespace;
24 
SyndicationElementExtension(XmlReader xmlReader)25         public SyndicationElementExtension(XmlReader xmlReader)
26         {
27             if (xmlReader == null)
28             {
29                 throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("xmlReader");
30             }
31             SyndicationFeedFormatter.MoveToStartElement(xmlReader);
32             _outerName = xmlReader.LocalName;
33             _outerNamespace = xmlReader.NamespaceURI;
34             _buffer = new XmlBuffer(int.MaxValue);
35             using (XmlDictionaryWriter writer = _buffer.OpenSection(XmlDictionaryReaderQuotas.Max))
36             {
37                 writer.WriteStartElement(Rss20Constants.ExtensionWrapperTag);
38                 writer.WriteNode(xmlReader, false);
39                 writer.WriteEndElement();
40             }
41             _buffer.CloseSection();
42             _buffer.Close();
43             _bufferElementIndex = 0;
44         }
45 
SyndicationElementExtension(object dataContractExtension)46         public SyndicationElementExtension(object dataContractExtension)
47             : this(dataContractExtension, (XmlObjectSerializer)null)
48         {
49         }
50 
SyndicationElementExtension(object dataContractExtension, XmlObjectSerializer dataContractSerializer)51         public SyndicationElementExtension(object dataContractExtension, XmlObjectSerializer dataContractSerializer)
52             : this(null, null, dataContractExtension, dataContractSerializer)
53         {
54         }
55 
SyndicationElementExtension(string outerName, string outerNamespace, object dataContractExtension)56         public SyndicationElementExtension(string outerName, string outerNamespace, object dataContractExtension)
57             : this(outerName, outerNamespace, dataContractExtension, null)
58         {
59         }
60 
SyndicationElementExtension(string outerName, string outerNamespace, object dataContractExtension, XmlObjectSerializer dataContractSerializer)61         public SyndicationElementExtension(string outerName, string outerNamespace, object dataContractExtension, XmlObjectSerializer dataContractSerializer)
62         {
63             if (dataContractExtension == null)
64             {
65                 throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("dataContractExtension");
66             }
67             if (outerName == string.Empty)
68             {
69                 throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgument(SR.Format(SR.OuterNameOfElementExtensionEmpty));
70             }
71             if (dataContractSerializer == null)
72             {
73                 dataContractSerializer = new DataContractSerializer(dataContractExtension.GetType());
74             }
75             _outerName = outerName;
76             _outerNamespace = outerNamespace;
77             _extensionData = dataContractExtension;
78             _extensionDataWriter = new ExtensionDataWriter(_extensionData, dataContractSerializer, _outerName, _outerNamespace);
79         }
80 
SyndicationElementExtension(object xmlSerializerExtension, XmlSerializer serializer)81         public SyndicationElementExtension(object xmlSerializerExtension, XmlSerializer serializer)
82         {
83             if (xmlSerializerExtension == null)
84             {
85                 throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("xmlSerializerExtension");
86             }
87             if (serializer == null)
88             {
89                 serializer = new XmlSerializer(xmlSerializerExtension.GetType());
90             }
91             _extensionData = xmlSerializerExtension;
92             _extensionDataWriter = new ExtensionDataWriter(_extensionData, serializer);
93         }
94 
SyndicationElementExtension(XmlBuffer buffer, int bufferElementIndex, string outerName, string outerNamespace)95         internal SyndicationElementExtension(XmlBuffer buffer, int bufferElementIndex, string outerName, string outerNamespace)
96         {
97             _buffer = buffer;
98             _bufferElementIndex = bufferElementIndex;
99             _outerName = outerName;
100             _outerNamespace = outerNamespace;
101         }
102 
103         public string OuterName
104         {
105             get
106             {
107                 if (_outerName == null)
108                 {
109                     EnsureOuterNameAndNs();
110                 }
111                 return _outerName;
112             }
113         }
114 
115         public string OuterNamespace
116         {
117             get
118             {
119                 if (_outerName == null)
120                 {
121                     EnsureOuterNameAndNs();
122                 }
123                 return _outerNamespace;
124             }
125         }
126 
GetObject()127         public TExtension GetObject<TExtension>()
128         {
129             return GetObject<TExtension>(new DataContractSerializer(typeof(TExtension)));
130         }
131 
GetObject(XmlObjectSerializer serializer)132         public TExtension GetObject<TExtension>(XmlObjectSerializer serializer)
133         {
134             if (serializer == null)
135             {
136                 throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("serializer");
137             }
138             if (_extensionData != null && typeof(TExtension).IsAssignableFrom(_extensionData.GetType()))
139             {
140                 return (TExtension)_extensionData;
141             }
142             using (XmlReader reader = GetReader())
143             {
144                 return (TExtension)serializer.ReadObject(reader, false);
145             }
146         }
147 
GetObject(XmlSerializer serializer)148         public TExtension GetObject<TExtension>(XmlSerializer serializer)
149         {
150             if (serializer == null)
151             {
152                 throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("serializer");
153             }
154             if (_extensionData != null && typeof(TExtension).IsAssignableFrom(_extensionData.GetType()))
155             {
156                 return (TExtension)_extensionData;
157             }
158             using (XmlReader reader = GetReader())
159             {
160                 return (TExtension)serializer.Deserialize(reader);
161             }
162         }
163 
GetReader()164         public XmlReader GetReader()
165         {
166             this.EnsureBuffer();
167             XmlReader reader = _buffer.GetReader(0);
168             int index = 0;
169             reader.ReadStartElement(Rss20Constants.ExtensionWrapperTag);
170             while (reader.IsStartElement())
171             {
172                 if (index == _bufferElementIndex)
173                 {
174                     break;
175                 }
176                 ++index;
177                 reader.Skip();
178             }
179             return reader;
180         }
181 
WriteTo(XmlWriter writer)182         public void WriteTo(XmlWriter writer)
183         {
184             if (writer == null)
185             {
186                 throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("writer");
187             }
188             if (_extensionDataWriter != null)
189             {
190                 _extensionDataWriter.WriteTo(writer);
191             }
192             else
193             {
194                 using (XmlReader reader = GetReader())
195                 {
196                     writer.WriteNode(reader, false);
197                 }
198             }
199         }
200 
EnsureBuffer()201         private void EnsureBuffer()
202         {
203             if (_buffer == null)
204             {
205                 _buffer = new XmlBuffer(int.MaxValue);
206                 using (XmlDictionaryWriter writer = _buffer.OpenSection(XmlDictionaryReaderQuotas.Max))
207                 {
208                     writer.WriteStartElement(Rss20Constants.ExtensionWrapperTag);
209                     this.WriteTo(writer);
210                     writer.WriteEndElement();
211                 }
212                 _buffer.CloseSection();
213                 _buffer.Close();
214                 _bufferElementIndex = 0;
215             }
216         }
217 
EnsureOuterNameAndNs()218         private void EnsureOuterNameAndNs()
219         {
220             Debug.Assert(_extensionDataWriter != null, "outer name is null only for datacontract and xmlserializer cases");
221             _extensionDataWriter.ComputeOuterNameAndNs(out _outerName, out _outerNamespace);
222         }
223 
224         // this class holds the extension data and the associated serializer (either DataContractSerializer or XmlSerializer but not both)
225         private class ExtensionDataWriter
226         {
227             private readonly XmlObjectSerializer _dataContractSerializer;
228             private readonly object _extensionData;
229             private readonly string _outerName;
230             private readonly string _outerNamespace;
231             private readonly XmlSerializer _xmlSerializer;
232 
ExtensionDataWriter(object extensionData, XmlObjectSerializer dataContractSerializer, string outerName, string outerNamespace)233             public ExtensionDataWriter(object extensionData, XmlObjectSerializer dataContractSerializer, string outerName, string outerNamespace)
234             {
235                 Debug.Assert(extensionData != null && dataContractSerializer != null, "null check");
236                 _dataContractSerializer = dataContractSerializer;
237                 _extensionData = extensionData;
238                 _outerName = outerName;
239                 _outerNamespace = outerNamespace;
240             }
241 
ExtensionDataWriter(object extensionData, XmlSerializer serializer)242             public ExtensionDataWriter(object extensionData, XmlSerializer serializer)
243             {
244                 Debug.Assert(extensionData != null && serializer != null, "null check");
245                 _xmlSerializer = serializer;
246                 _extensionData = extensionData;
247             }
248 
WriteTo(XmlWriter writer)249             public void WriteTo(XmlWriter writer)
250             {
251                 if (_xmlSerializer != null)
252                 {
253                     Debug.Assert((_dataContractSerializer == null && _outerName == null && _outerNamespace == null), "Xml serializer cannot have outer name, ns");
254                     _xmlSerializer.Serialize(writer, _extensionData);
255                 }
256                 else
257                 {
258                     Debug.Assert(_xmlSerializer == null, "Xml serializer cannot be configured");
259                     if (_outerName != null)
260                     {
261                         writer.WriteStartElement(_outerName, _outerNamespace);
262                         _dataContractSerializer.WriteObjectContent(writer, _extensionData);
263                         writer.WriteEndElement();
264                     }
265                     else
266                     {
267                         _dataContractSerializer.WriteObject(writer, _extensionData);
268                     }
269                 }
270             }
271 
ComputeOuterNameAndNs(out string name, out string ns)272             internal void ComputeOuterNameAndNs(out string name, out string ns)
273             {
274                 if (_outerName != null)
275                 {
276                     Debug.Assert(_xmlSerializer == null, "outer name is not null for data contract extension only");
277                     name = _outerName;
278                     ns = _outerNamespace;
279                 }
280                 else if (_dataContractSerializer != null)
281                 {
282                     Debug.Assert(_xmlSerializer == null, "only one of xmlserializer or datacontract serializer can be present");
283                     XsdDataContractExporter dcExporter = new XsdDataContractExporter();
284                     XmlQualifiedName qName = dcExporter.GetRootElementName(_extensionData.GetType());
285                     if (qName != null)
286                     {
287                         name = qName.Name;
288                         ns = qName.Namespace;
289                     }
290                     else
291                     {
292                         // this can happen if an IXmlSerializable type is specified with IsAny=true
293                         ReadOuterNameAndNs(out name, out ns);
294                     }
295                 }
296                 else
297                 {
298                     Debug.Assert(_dataContractSerializer == null, "only one of xmlserializer or datacontract serializer can be present");
299                     XmlReflectionImporter importer = new XmlReflectionImporter();
300                     XmlTypeMapping typeMapping = importer.ImportTypeMapping(_extensionData.GetType());
301                     if (typeMapping != null && !string.IsNullOrEmpty(typeMapping.ElementName))
302                     {
303                         name = typeMapping.ElementName;
304                         ns = typeMapping.Namespace;
305                     }
306                     else
307                     {
308                         // this can happen if an IXmlSerializable type is specified with IsAny=true
309                         ReadOuterNameAndNs(out name, out ns);
310                     }
311                 }
312             }
313 
ReadOuterNameAndNs(out string name, out string ns)314             internal void ReadOuterNameAndNs(out string name, out string ns)
315             {
316                 using (MemoryStream stream = new MemoryStream())
317                 {
318                     using (XmlWriter writer = XmlWriter.Create(stream))
319                     {
320                         this.WriteTo(writer);
321                     }
322                     stream.Seek(0, SeekOrigin.Begin);
323                     using (XmlReader reader = XmlReader.Create(stream))
324                     {
325                         SyndicationFeedFormatter.MoveToStartElement(reader);
326                         name = reader.LocalName;
327                         ns = reader.NamespaceURI;
328                     }
329                 }
330             }
331         }
332     }
333 }
334