File: PrintConfig\PrtCap_Reader.cs
Web Access
Project: src\src\Microsoft.DotNet.Wpf\src\ReachFramework\ReachFramework.csproj (ReachFramework)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
/*++
 
 
Abstract:
 
    Definition and implementation of the private XmlPrintCapReader class.
 
 
 
--*/
 
using System.Xml;
using System.IO;
using System.Globalization;
 
#pragma warning disable 1634, 1691 // Allows suppression of certain PreSharp messages
 
namespace MS.Internal.Printing.Configuration
{
    /// <summary>
    /// Reader class of XML PrintCapabilities
    /// </summary>
    internal class XmlPrintCapReader
    {
        #region Constructors
 
        /// <summary>
        /// Instantiates a reader object for the given XML PrintCapabilities
        /// </summary>
        /// <remarks>Constructor verifies the root element is valid</remarks>
        /// <exception cref="FormatException">thrown if XML PrintCapabilities is not well-formed</exception>
        public XmlPrintCapReader(Stream xmlStream)
        {
            // Internally the XML PrintCapabilities reader uses XmlTextReader
            _xmlReader = new XmlTextReader(xmlStream);
 
            // We need namespace support from the reader.
            _xmlReader.Namespaces = true;
 
            // Don't resolve external resources.
            _xmlReader.XmlResolver = null;
 
            // Verify root element is <PrintCapabilities> in our standard namespace
            if ((_xmlReader.MoveToContent() != XmlNodeType.Element) ||
                (_xmlReader.LocalName != PrintSchemaTags.Framework.PrintCapRoot) ||
                (_xmlReader.NamespaceURI != PrintSchemaNamespaces.Framework))
            {
                throw NewPrintCapFormatException(String.Format(CultureInfo.CurrentCulture,
                                                 PTUtility.GetTextFromResource("FormatException.InvalidRootElement"),
                                                 _xmlReader.NamespaceURI,
                                                 _xmlReader.LocalName));
            }
 
            // Verify the XML PrintCapabilities version is supported
 
            // For XML attribute without a prefix (e.g. <... name="prn:PageMediaSize">),
            // even though the XML document has default namespace defined as our standard
            // Print Schema framework namespace, the XML atribute still has NULL namespaceURI.
            // It will only have the correct namespaceURI when a prefix is used. This doesn't
            // apply to XML element, whose namespaceURI works fine with default namespace.
 
            // GetAttribute doesn't move the reader cursor away from the current element
            string version = _xmlReader.GetAttribute(PrintSchemaTags.Framework.RootVersionAttr,
                                                     PrintSchemaNamespaces.FrameworkAttrForXmlReader);
 
            if (version == null)
            {
                throw NewPrintCapFormatException(String.Format(CultureInfo.CurrentCulture,
                                                 PTUtility.GetTextFromResource("FormatException.RootMissingAttribute"),
                                                 PrintSchemaTags.Framework.RootVersionAttr));
            }
 
            // Convert string to number to verify
            decimal versionNum;
 
            try
            {
                versionNum = XmlConvertHelper.ConvertStringToDecimal(version);
            }
            catch (FormatException e)
            {
                throw NewPrintCapFormatException(String.Format(CultureInfo.CurrentCulture,
                                                 PTUtility.GetTextFromResource("FormatException.RootInvalidAttribute"),
                                                 PrintSchemaTags.Framework.RootVersionAttr,
                                                 version),
                                                 e);
            }
 
            if (versionNum != PrintSchemaTags.Framework.SchemaVersion)
            {
                throw NewPrintCapFormatException(String.Format(CultureInfo.CurrentCulture,
                                                 PTUtility.GetTextFromResource("FormatException.VersionNotSupported"),
                                                 versionNum));
            }
 
            // Reset internal states to be ready for client's reading of the PrintCapabilities XML
            ResetCurrentElementState();
        }
 
        #endregion Constructors
 
        #region Public Methods
 
        /// <summary>
        /// Moves the reader cursor to the next Print Schema Framework element at the given depth.
        /// (The element could be Feature, ParameterDefinition, Option, ScoredProperty or Property)
        /// </summary>
        /// <param name="depth">client-requested traversing depth</param>
        /// <param name="typeFilterFlags">flags to indicate client interested node types</param>
        /// <returns>True if next Framework element is ready to read.
        /// False if no more Framework element at the given depth.</returns>
        /// <exception cref="XmlException">XML is not well-formed.</exception>
        public bool MoveToNextSchemaElement(int depth, PrintSchemaNodeTypes typeFilterFlags)
        {
            bool foundElement = false;
 
            while (!foundElement && _xmlReader.Read())
            {
                // Read() throws XmlException if error occurred while parsing the XML.
 
                // If we hit an end-element tag at higher depth, we know there are no more
                // Framework elements at the client-requested depth.
                if ((_xmlReader.NodeType == XmlNodeType.EndElement) &&
                    (_xmlReader.Depth < depth))
                {
                    break;
                }
 
                // Stop at the next XML start element at the client-requested depth
                // and in the standard Framework element namespace.
                if ((_xmlReader.NodeType != XmlNodeType.Element) ||
                    (_xmlReader.Depth != depth) ||
                    (_xmlReader.NamespaceURI != PrintSchemaNamespaces.Framework))
                {
                    continue;
                }
 
                // Find a candidate, so reset internal states to be ready for its parsing.
                ResetCurrentElementState();
 
                foundElement = true;
 
                _currentElementDepth = depth;
                _currentElementIsEmpty = _xmlReader.IsEmptyElement;
 
                // Map element name to Schema node type
                int enumValue = PrintSchemaMapper.SchemaNameToEnumValueWithMap(
                                                  PrintSchemaTags.Framework.NodeTypeMapTable,
                                                  _xmlReader.LocalName);
 
                if (enumValue > 0)
                {
                    _currentElementNodeType = (PrintSchemaNodeTypes)enumValue;
                }
                else
                {
                    #if _DEBUG
                    Trace.WriteLine("-Warning- skip unknown element '" + _xmlReader.LocalName +
                                    "' at line " + _xmlReader.LineNumber + ", position " +
                                    _xmlReader.LinePosition);
                    #endif
 
                    foundElement = false;
                }
 
                if (foundElement)
                {
                    // Check whether or not the found element type is what client is interested in.
                    // If not, we will skip this element.
                    if ((CurrentElementNodeType & typeFilterFlags) == 0)
                    {
                        #if _DEBUG
                        Trace.WriteLine("-Warning- skip not-wanted element '" + _xmlReader.LocalName +
                                        "' at line " + _xmlReader.LineNumber + ", position " +
                                        _xmlReader.LinePosition);
                        #endif
 
                        foundElement = false;
                    }
                }
 
                if (foundElement)
                {
                    // The element is what the client wants.
                    if (CurrentElementNodeType != PrintSchemaNodeTypes.Value)
                    {
                        // Element other than <Value> should have the "name" XML attribute.
                        // Reader will verify the "name" XML attribute has a QName value that
                        // is in our standard Keyword namespace.
                        string QName = _xmlReader.GetAttribute(PrintSchemaTags.Framework.NameAttr,
                                                               PrintSchemaNamespaces.FrameworkAttrForXmlReader);
 
                        // Only <Option> element is allowed not to have the "name" XML attribute
                        if (QName == null)
                        {
                            if (CurrentElementNodeType != PrintSchemaNodeTypes.Option)
                            {
                                #if _DEBUG
                                Trace.WriteLine("-Warning- skip element " + CurrentElementNodeType +
                                                " at line " + _xmlReader.LineNumber + ", position " +
                                                _xmlReader.LinePosition + " due to missing 'name' XML attribute");
                                #endif
 
                                foundElement = false;
                            }
                        }
                        else
                        {
                            string URI = XmlReaderQName.GetURI(_xmlReader, QName);
                            string localName = XmlReaderQName.GetLocalName(QName);
 
                            if (URI == PrintSchemaNamespaces.Framework)
                            {
                                _currentElementPSFNameAttrValue = localName;
                            }
                            else if (URI == PrintSchemaNamespaces.StandardKeywordSet)
                            {
                                _currentElementNameAttrValue = localName;
                            }
                            else
                            {
                                // If QName value is not in standard PSF or PSK namespace,
                                // then skip this Schema element.
                                #if _DEBUG
                                Trace.WriteLine("-Warning- skip element " + CurrentElementNodeType +
                                                " at line " + _xmlReader.LineNumber + ", position " +
                                                _xmlReader.LinePosition +
                                                " due to non-PSF/PSK 'name' XML attribute value: " + QName);
                                #endif
 
                                foundElement = false;
                            }
                        }
                    }
                    else
                    {
                        // For <Value> element, we need to get its element text value.
                        // If this function tells client the <Value> element is found, it guarantees
                        // that the <Value> element text value is non-empty.
 
                        // Needs to handle xsi:type verification
                        // ReadElementString() returns empty string if the element is empty
                        // (<item></item> or <item/>), and it could throws XmlException.
                        _currentElementTextValue = _xmlReader.ReadElementString();
 
                        // Our schema requires that <Value> element should always have non-empty value.
                        if ((_currentElementTextValue == null) || (_currentElementTextValue.Length == 0))
                        {
                            #if _DEBUG
                            Trace.WriteLine("-Warning- skip element " + CurrentElementNodeType +
                                            " at line " + _xmlReader.LineNumber + ", position " +
                                            _xmlReader.LinePosition + " since it has empty element text");
                            #endif
 
                            _currentElementTextValue = null;
                            foundElement = false;
                        }
                    }
                }
            }
 
            return foundElement;
        }
 
        /// <summary>
        /// Generic processing of option-element XML attributes
        /// </summary>
        /// <exception>none</exception>
        public void OptionAttributeGenericHandler(PrintCapabilityOption option)
        {
            // Currently we support these option-element attributes:
            // "name"
 
            // Handle the "name" XML attribute
            option._optionName = this.CurrentElementNameAttrValue;
        }
 
        /// <summary>
        /// Gets current Property/ScoredProperty's full text value from its "Value" child-element.
        /// </summary>
        /// <exception cref="FormatException">can't find the value</exception>
        /// <exception cref="XmlException">XML is not well-formed.</exception>
        public string GetCurrentPropertyFullValueWithException()
        {
            // No need to loop here. We just need to look for the first <Value> child-element
            if (!MoveToNextSchemaElement(CurrentElementDepth + 1, PrintSchemaNodeTypes.Value))
            {
                throw NewPrintCapFormatException(String.Format(CultureInfo.CurrentCulture,
                                                 PTUtility.GetTextFromResource("FormatException.MissingRequiredChildElement"),
                                                 PrintSchemaTags.Framework.Value,
                                                 _xmlReader.LineNumber,
                                                 _xmlReader.LinePosition));
            }
 
            return CurrentElementTextValue;
        }
 
        /// <summary>
        /// Gets current Property/ScoredProperty's integer value from its "Value" child-element
        /// </summary>
        /// <exception cref="FormatException">either can't find the value or find invalid value number</exception>
        /// <exception cref="XmlException">XML is not well-formed.</exception>
        public int GetCurrentPropertyIntValueWithException()
        {
            // Do the assignment outside of try-catch so the FormatException of value-not-found could be thrown properly.
            string textValue = GetCurrentPropertyFullValueWithException();
 
            int intValue = 0;
 
            try
            {
                intValue = XmlConvertHelper.ConvertStringToInt32(textValue);
            }
            catch (FormatException e)
            {
                throw NewPrintCapFormatException(String.Format(CultureInfo.CurrentCulture,
                                                 PTUtility.GetTextFromResource("FormatException.InvalidXMLIntValue"),
                                                 CurrentElementTextValue,
                                                 _xmlReader.LineNumber,
                                                 _xmlReader.LinePosition),
                                                 e);
            }
 
            return intValue;
        }
 
        /// <summary>
        /// Gets current Property/ScoredProperty's QName LocalName value from its "Value" child-element.
        /// The returned property QName value is guaranteed to be in the standard keyword set namespace.
        /// </summary>
        /// <exception cref="FormatException">either can't find the value or value is private</exception>
        /// <exception cref="XmlException">XML is not well-formed.</exception>
        public string GetCurrentPropertyQNameValueWithException()
        {
            string QName = GetCurrentPropertyFullValueWithException();
 
            if (XmlReaderQName.GetURI(_xmlReader, QName) == PrintSchemaNamespaces.StandardKeywordSet)
            {
                return XmlReaderQName.GetLocalName(QName);
            }
            else
            {
                // Needs to handle private XML text value
                throw NewPrintCapFormatException(String.Format(CultureInfo.CurrentCulture,
                                                 PTUtility.GetTextFromResource("FormatException.PrivateXMLTextValue"),
                                                 QName,
                                                 _xmlReader.LineNumber,
                                                 _xmlReader.LinePosition));
            }
        }
 
        /// <summary>
        /// Gets current Property/ScoredProperty's ParameterRef name from its "ParameterRef" child-element.
        /// The returned ParameterRef name is guaranteed to be in the standard keyword set namespace.
        /// </summary>
        /// <exception cref="FormatException">either can't find the child-element or its name is private</exception>
        /// <exception cref="XmlException">XML is not well-formed.</exception>
        public string GetCurrentPropertyParamRefNameWithException()
        {
            if (!MoveToNextSchemaElement(CurrentElementDepth + 1, PrintSchemaNodeTypes.ParameterRef))
            {
                throw NewPrintCapFormatException(String.Format(CultureInfo.CurrentCulture,
                                                 PTUtility.GetTextFromResource("FormatException.MissingRequiredChildElement"),
                                                 PrintSchemaTags.Framework.ParameterRef,
                                                 _xmlReader.LineNumber,
                                                 _xmlReader.LinePosition));
            }
 
            return CurrentElementNameAttrValue;
        }
 
        #endregion Public Methods
 
        #region Public Properties
 
        /// <summary>
        /// Current element's Print Schema node type
        /// </summary>
        public PrintSchemaNodeTypes CurrentElementNodeType
        {
            get
            {
                return _currentElementNodeType;
            }
        }
 
        /// <summary>
        /// Current element's depth
        /// </summary>
        public int CurrentElementDepth
        {
            get
            {
                return _currentElementDepth;
            }
        }
 
        /// <summary>
        /// Checks if current element is an empty XML element
        /// </summary>
        public bool CurrentElementIsEmpty
        {
            get
            {
                return _currentElementIsEmpty;
            }
        }
 
        /// <summary>
        /// Current element's "name" XML attribute value in Print Schema Keyword namespace.
        /// </summary>
        /// <remarks>
        /// The value is the localname part of a QName whose namespace is the standard
        /// Print Schema Keyword namespace.
        /// </remarks>
        public string CurrentElementNameAttrValue
        {
            get
            {
                return _currentElementNameAttrValue;
            }
        }
 
        /// <summary>
        /// Current 'Value' element's text value
        /// </summary>
        public string CurrentElementTextValue
        {
            get
            {
                return _currentElementTextValue;
            }
        }
 
        /// <summary>
        /// Current element's "name" XML attribute value in Print Schema Framework namespace.
        /// </summary>
        /// <remarks>
        /// The value is the localname part of a QName whose namespace is the standard
        /// Print Schema Framework namespace.
        /// </remarks>
        public string CurrentElementPSFNameAttrValue
        {
            get
            {
                return _currentElementPSFNameAttrValue;
            }
        }
 
        #endregion Public Properties
 
        #region Internal Fields
 
        // The XML text reader instance
        internal XmlTextReader _xmlReader;
 
        #endregion Internal Fields
 
        #region Private Methods
 
        /// <summary>
        /// Resets reader's internal schema-element state so we are ready for next element
        /// </summary>
        private void ResetCurrentElementState()
        {
            _currentElementNodeType = PrintSchemaNodeTypes.None;
            _currentElementDepth = 0;
            _currentElementIsEmpty = false;
            _currentElementNameAttrValue = null;
            _currentElementTextValue = null;
            _currentElementPSFNameAttrValue = null;
        }
 
        /// <summary>
        /// Returns a new FormatException instance for not-well-formed PrintCapabilities XML.
        /// </summary>
        /// <param name="detailMsg">detailed message about the violation of well-formness</param>
        /// <returns>the new FormatException instance</returns>
        private static FormatException NewPrintCapFormatException(string detailMsg)
        {
            return InternalPrintCapabilities.NewPrintCapFormatException(detailMsg);
        }
 
        /// <summary>
        /// Returns a new FormatException instance for not-well-formed PrintCapabilities XML.
        /// </summary>
        /// <param name="detailMsg">detailed message about the violation of well-formness</param>
        /// <param name="innerException">the exception that causes the violation of well-formness</param>
        /// <returns>the new FormatException instance</returns>
        private static FormatException NewPrintCapFormatException(string detailMsg, Exception innerException)
        {
            return InternalPrintCapabilities.NewPrintCapFormatException(detailMsg, innerException);
        }
 
        #endregion Private Methods
 
        #region Private Fields
 
        private PrintSchemaNodeTypes    _currentElementNodeType;
        private int                     _currentElementDepth;
        private bool                    _currentElementIsEmpty;
        private string                  _currentElementNameAttrValue;
        private string                  _currentElementTextValue;
        private string                  _currentElementPSFNameAttrValue;
 
        #endregion Private Fields
    }
 
    /// <summary>
    /// QName helper class for XMLTextReader
    /// </summary>
    internal class XmlReaderQName
    {
        #region Constructors
 
        // Never need to use an instance. All static methods.
        private XmlReaderQName() {}
 
        #endregion Constructors
 
        #region Public Methods
 
        /// <summary>
        /// Gets URI of the QName
        /// </summary>
        /// <param name="xmlReader">the XmlTextReader object</param>
        /// <param name="QName">the qualified name</param>
        /// <returns>URI of the QName (null if no matching namespace is found)</returns>
        /// <exception>none</exception>
        public static string GetURI(XmlTextReader xmlReader, string QName)
        {
            int colonIndex = QName.IndexOf(":", StringComparison.Ordinal);
 
            string prefix = (colonIndex == (-1)) ? "" : QName.Substring(0, colonIndex);
 
            // Prefix is non-null (could be empty), so LookupNamespace won't throw exception.
            // It will return null if no matching prefix is found.
            return xmlReader.LookupNamespace(prefix);
        }
 
        /// <summary>
        /// Gets local name of the QName
        /// </summary>
        /// <param name="QName">the qualified name</param>
        /// <returns>local name of the QName</returns>
        /// <exception>none</exception>
        public static string GetLocalName(string QName)
        {
            int colonIndex = QName.IndexOf(":", StringComparison.Ordinal);
 
            return QName.Substring(colonIndex + 1);
        }
 
        #endregion Public Methods
    }
}