File: System\ServiceModel\Syndication\SyndicationElementExtension.cs
Web Access
Project: src\src\libraries\System.ServiceModel.Syndication\src\System.ServiceModel.Syndication.csproj (System.ServiceModel.Syndication)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Diagnostics;
using System.IO;
using System.Runtime.Serialization;
using System.Xml;
using System.Xml.Serialization;
 
namespace System.ServiceModel.Syndication
{
    public class SyndicationElementExtension
    {
        private XmlBuffer _buffer;
        private int _bufferElementIndex;
        // extensionData and extensionDataWriter are only present on the send side
        private readonly object _extensionData;
        private readonly ExtensionDataWriter _extensionDataWriter;
        private string _outerName;
        private string _outerNamespace;
 
        public SyndicationElementExtension(XmlReader xmlReader)
        {
            if (xmlReader is null)
            {
                throw new ArgumentNullException(nameof(xmlReader));
            }
 
            SyndicationFeedFormatter.MoveToStartElement(xmlReader);
            _outerName = xmlReader.LocalName;
            _outerNamespace = xmlReader.NamespaceURI;
            _buffer = new XmlBuffer(int.MaxValue);
            using (XmlDictionaryWriter writer = _buffer.OpenSection(XmlDictionaryReaderQuotas.Max))
            {
                writer.WriteStartElement(Rss20Constants.ExtensionWrapperTag);
                writer.WriteNode(xmlReader, false);
                writer.WriteEndElement();
            }
            _buffer.CloseSection();
            _buffer.Close();
            _bufferElementIndex = 0;
        }
 
        public SyndicationElementExtension(object dataContractExtension) : this(dataContractExtension, (XmlObjectSerializer)null)
        {
        }
 
        public SyndicationElementExtension(object dataContractExtension, XmlObjectSerializer dataContractSerializer)
            : this(null, null, dataContractExtension, dataContractSerializer)
        {
        }
 
        public SyndicationElementExtension(string outerName, string outerNamespace, object dataContractExtension)
            : this(outerName, outerNamespace, dataContractExtension, null)
        {
        }
 
        public SyndicationElementExtension(string outerName, string outerNamespace, object dataContractExtension, XmlObjectSerializer dataContractSerializer)
        {
            if (dataContractExtension is null)
            {
                throw new ArgumentNullException(nameof(dataContractExtension));
            }
 
            if (outerName == string.Empty)
            {
                throw new ArgumentException(SR.OuterNameOfElementExtensionEmpty, nameof(outerName));
            }
 
            dataContractSerializer ??= new DataContractSerializer(dataContractExtension.GetType());
            _outerName = outerName;
            _outerNamespace = outerNamespace;
            _extensionData = dataContractExtension;
            _extensionDataWriter = new ExtensionDataWriter(_extensionData, dataContractSerializer, _outerName, _outerNamespace);
        }
 
        public SyndicationElementExtension(object xmlSerializerExtension, XmlSerializer serializer)
        {
            if (xmlSerializerExtension is null)
            {
                throw new ArgumentNullException(nameof(xmlSerializerExtension));
            }
 
            serializer ??= new XmlSerializer(xmlSerializerExtension.GetType());
            _extensionData = xmlSerializerExtension;
            _extensionDataWriter = new ExtensionDataWriter(_extensionData, serializer);
        }
 
        internal SyndicationElementExtension(XmlBuffer buffer, int bufferElementIndex, string outerName, string outerNamespace)
        {
            _buffer = buffer;
            _bufferElementIndex = bufferElementIndex;
            _outerName = outerName;
            _outerNamespace = outerNamespace;
        }
 
        public string OuterName
        {
            get
            {
                if (_outerName == null)
                {
                    EnsureOuterNameAndNs();
                }
 
                return _outerName;
            }
        }
 
        public string OuterNamespace
        {
            get
            {
                if (_outerName == null)
                {
                    EnsureOuterNameAndNs();
                }
 
                return _outerNamespace;
            }
        }
 
        public TExtension GetObject<TExtension>() => GetObject<TExtension>(new DataContractSerializer(typeof(TExtension)));
 
        public TExtension GetObject<TExtension>(XmlObjectSerializer serializer)
        {
            if (serializer is null)
            {
                throw new ArgumentNullException(nameof(serializer));
            }
 
            if (_extensionData != null && typeof(TExtension).IsAssignableFrom(_extensionData.GetType()))
            {
                return (TExtension)_extensionData;
            }
            using (XmlReader reader = GetReader())
            {
                return (TExtension)serializer.ReadObject(reader, false);
            }
        }
 
        public TExtension GetObject<TExtension>(XmlSerializer serializer)
        {
            if (serializer is null)
            {
                throw new ArgumentNullException(nameof(serializer));
            }
 
            if (_extensionData != null && typeof(TExtension).IsAssignableFrom(_extensionData.GetType()))
            {
                return (TExtension)_extensionData;
            }
            using (XmlReader reader = GetReader())
            {
                return (TExtension)serializer.Deserialize(reader);
            }
        }
 
        public XmlReader GetReader()
        {
            EnsureBuffer();
            XmlDictionaryReader reader = _buffer.GetReader(0);
            int index = 0;
            reader.ReadStartElement(Rss20Constants.ExtensionWrapperTag);
            while (reader.IsStartElement())
            {
                if (index == _bufferElementIndex)
                {
                    break;
                }
                ++index;
                reader.Skip();
            }
            return reader;
        }
 
        public void WriteTo(XmlWriter writer)
        {
            if (writer is null)
            {
                throw new ArgumentNullException(nameof(writer));
            }
 
            if (_extensionDataWriter != null)
            {
                _extensionDataWriter.WriteTo(writer);
            }
            else
            {
                using (XmlReader reader = GetReader())
                {
                    writer.WriteNode(reader, false);
                }
            }
        }
 
        private void EnsureBuffer()
        {
            if (_buffer == null)
            {
                _buffer = new XmlBuffer(int.MaxValue);
                using (XmlDictionaryWriter writer = _buffer.OpenSection(XmlDictionaryReaderQuotas.Max))
                {
                    writer.WriteStartElement(Rss20Constants.ExtensionWrapperTag);
                    WriteTo(writer);
                    writer.WriteEndElement();
                }
                _buffer.CloseSection();
                _buffer.Close();
                _bufferElementIndex = 0;
            }
        }
 
        private void EnsureOuterNameAndNs()
        {
            Debug.Assert(_extensionDataWriter != null, "outer name is null only for datacontract and xmlserializer cases");
            _extensionDataWriter.ComputeOuterNameAndNs(out _outerName, out _outerNamespace);
        }
 
        // this class holds the extension data and the associated serializer (either DataContractSerializer or XmlSerializer but not both)
        private sealed class ExtensionDataWriter
        {
            private readonly XmlObjectSerializer _dataContractSerializer;
            private readonly object _extensionData;
            private readonly string _outerName;
            private readonly string _outerNamespace;
            private readonly XmlSerializer _xmlSerializer;
 
            public ExtensionDataWriter(object extensionData, XmlObjectSerializer dataContractSerializer, string outerName, string outerNamespace)
            {
                Debug.Assert(extensionData != null && dataContractSerializer != null, "null check");
                _dataContractSerializer = dataContractSerializer;
                _extensionData = extensionData;
                _outerName = outerName;
                _outerNamespace = outerNamespace;
            }
 
            public ExtensionDataWriter(object extensionData, XmlSerializer serializer)
            {
                Debug.Assert(extensionData != null && serializer != null, "null check");
                _xmlSerializer = serializer;
                _extensionData = extensionData;
            }
 
            public void WriteTo(XmlWriter writer)
            {
                if (_xmlSerializer != null)
                {
                    Debug.Assert((_dataContractSerializer == null && _outerName == null && _outerNamespace == null), "Xml serializer cannot have outer name, ns");
                    _xmlSerializer.Serialize(writer, _extensionData);
                }
                else
                {
                    Debug.Assert(_xmlSerializer == null, "Xml serializer cannot be configured");
                    if (_outerName != null)
                    {
                        writer.WriteStartElement(_outerName, _outerNamespace);
                        _dataContractSerializer.WriteObjectContent(writer, _extensionData);
                        writer.WriteEndElement();
                    }
                    else
                    {
                        _dataContractSerializer.WriteObject(writer, _extensionData);
                    }
                }
            }
 
            internal void ComputeOuterNameAndNs(out string name, out string ns)
            {
                Debug.Assert(_outerName == null, "All callers of this function should already check for a null outer name.");
                if (_dataContractSerializer != null)
                {
                    Debug.Assert(_xmlSerializer == null, "only one of xmlserializer or datacontract serializer can be present");
                    XsdDataContractExporter dcExporter = new XsdDataContractExporter();
                    XmlQualifiedName qName = dcExporter.GetRootElementName(_extensionData.GetType());
                    if (qName != null)
                    {
                        name = qName.Name;
                        ns = qName.Namespace;
                    }
                    else
                    {
                        // this can happen if an IXmlSerializable type is specified with IsAny=true
                        ReadOuterNameAndNs(out name, out ns);
                    }
                }
                else
                {
                    Debug.Assert(_dataContractSerializer == null, "only one of xmlserializer or datacontract serializer can be present");
                    XmlReflectionImporter importer = new XmlReflectionImporter();
                    XmlTypeMapping typeMapping = importer.ImportTypeMapping(_extensionData.GetType());
                    if (typeMapping != null && !string.IsNullOrEmpty(typeMapping.ElementName))
                    {
                        name = typeMapping.ElementName;
                        ns = typeMapping.Namespace;
                    }
                    else
                    {
                        // this can happen if an IXmlSerializable type is specified with IsAny=true
                        ReadOuterNameAndNs(out name, out ns);
                    }
                }
            }
 
            internal void ReadOuterNameAndNs(out string name, out string ns)
            {
                using (MemoryStream stream = new MemoryStream())
                {
                    using (XmlWriter writer = XmlWriter.Create(stream))
                    {
                        WriteTo(writer);
                    }
                    stream.Seek(0, SeekOrigin.Begin);
                    using (XmlReader reader = XmlReader.Create(stream))
                    {
                        SyndicationFeedFormatter.MoveToStartElement(reader);
                        name = reader.LocalName;
                        ns = reader.NamespaceURI;
                    }
                }
            }
        }
    }
}