2013-12-17 22:50:36 +01:00
/*
*
* An XmlReader implementation for loading SGML (including HTML) converting it
* to well formed XML, by adding missing quotes, empty attribute values, ignoring
* duplicate attributes, case folding on tag names, adding missing closing tags
* based on SGML DTD information, and so on.
*
* Copyright (c) 2002 Microsoft Corporation. All rights reserved. (Chris Lovett)
*
*/
/*
*
* Copyright (c) 2007-2013 MindTouch. All rights reserved.
* www.mindtouch.com oss@mindtouch.com
*
* For community documentation and downloads visit wiki.developer.mindtouch.com;
* please review the licensing section.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
using System ;
using System.Collections.Generic ;
using System.Diagnostics.CodeAnalysis ;
using System.Globalization ;
using System.IO ;
using System.Reflection ;
using System.Text ;
using System.Xml ;
namespace ReadSharp.Ports.Sgml
{
2013-12-24 10:50:46 +01:00
/// <summary>
/// SGML is case insensitive, so here you can choose between converting
/// to lower case or upper case tags. "None" means that the case is left
/// alone, except that end tags will be folded to match the start tags.
/// </summary>
public enum CaseFolding
{
2013-12-17 22:50:36 +01:00
/// <summary>
2013-12-24 10:50:46 +01:00
/// Do not convert case, except for converting end tags to match start tags.
2013-12-17 22:50:36 +01:00
/// </summary>
2013-12-24 10:50:46 +01:00
None ,
2013-12-17 22:50:36 +01:00
2013-12-24 10:50:46 +01:00
/// <summary>
/// Convert tags to upper case.
/// </summary>
ToUpper ,
2013-12-17 22:50:36 +01:00
2013-12-24 10:50:46 +01:00
/// <summary>
/// Convert tags to lower case.
/// </summary>
ToLower
}
2013-12-17 22:50:36 +01:00
#if PORTABLE
2013-12-24 10:50:46 +01:00
/// <summary>
/// System.Xml.WhitespaceHandling is not available in .NETPortable.
/// </summary>
public enum WhitespaceHandling
{
All ,
None ,
Significant
}
2013-12-17 22:50:36 +01:00
#endif
2013-12-24 10:50:46 +01:00
/// <summary>
/// This stack maintains a high water mark for allocated objects so the client
/// can reuse the objects in the stack to reduce memory allocations, this is
/// used to maintain current state of the parser for element stack, and attributes
/// in each element.
/// </summary>
internal class HWStack
{
private object [] m_items ;
private int m_size ;
private int m_count ;
private int m_growth ;
2013-12-17 22:50:36 +01:00
/// <summary>
2013-12-24 10:50:46 +01:00
/// Initialises a new instance of the HWStack class.
2013-12-17 22:50:36 +01:00
/// </summary>
2013-12-24 10:50:46 +01:00
/// <param name="growth">The amount to grow the stack space by, if more space is needed on the stack.</param>
public HWStack ( int growth )
2013-12-17 22:50:36 +01:00
{
2013-12-24 10:50:46 +01:00
this . m_growth = growth ;
2013-12-17 22:50:36 +01:00
}
/// <summary>
2013-12-24 10:50:46 +01:00
/// The number of items currently in the stack.
2013-12-17 22:50:36 +01:00
/// </summary>
2013-12-24 10:50:46 +01:00
public int Count
2013-12-17 22:50:36 +01:00
{
2013-12-24 10:50:46 +01:00
get
{
return this . m_count ;
}
set
{
this . m_count = value ;
}
2013-12-17 22:50:36 +01:00
}
2013-12-24 10:50:46 +01:00
/// <summary>
/// The size (capacity) of the stack.
/// </summary>
[SuppressMessage("Microsoft.Performance", "CA1811", Justification = "Kept for potential future usage.")]
public int Size
2013-12-17 22:50:36 +01:00
{
2013-12-24 10:50:46 +01:00
get
{
return this . m_size ;
}
2013-12-17 22:50:36 +01:00
}
2013-12-24 10:50:46 +01:00
/// <summary>
/// Returns the item at the requested index or null if index is out of bounds
/// </summary>
/// <param name="i">The index of the item to retrieve.</param>
/// <returns>The item at the requested index or null if index is out of bounds.</returns>
public object this [ int i ]
{
get
{
return ( i >= 0 && i < this . m_size ) ? m_items [ i ] : null ;
}
set
{
this . m_items [ i ] = value ;
}
}
2013-12-17 22:50:36 +01:00
/// <summary>
2013-12-24 10:50:46 +01:00
/// Removes and returns the item at the top of the stack
2013-12-17 22:50:36 +01:00
/// </summary>
2013-12-24 10:50:46 +01:00
/// <returns>The item at the top of the stack.</returns>
public object Pop ()
2013-12-17 22:50:36 +01:00
{
2013-12-24 10:50:46 +01:00
this . m_count --;
if ( this . m_count > 0 )
{
return m_items [ this . m_count - 1 ];
}
2013-12-17 22:50:36 +01:00
2013-12-24 10:50:46 +01:00
return null ;
}
2013-12-17 22:50:36 +01:00
2013-12-24 10:50:46 +01:00
/// <summary>
/// Pushes a new slot at the top of the stack.
/// </summary>
/// <returns>The object at the top of the stack.</returns>
/// <remarks>
/// This method tries to reuse a slot, if it returns null then
/// the user has to call the other Push method.
/// </remarks>
public object Push ()
{
if ( this . m_count == this . m_size )
{
int newsize = this . m_size + this . m_growth ;
object [] newarray = new object [ newsize ];
if ( this . m_items != null )
Array . Copy ( this . m_items , newarray , this . m_size );
2013-12-17 22:50:36 +01:00
2013-12-24 10:50:46 +01:00
this . m_size = newsize ;
this . m_items = newarray ;
}
return m_items [ this . m_count ++];
}
2013-12-17 22:50:36 +01:00
2013-12-24 10:50:46 +01:00
/// <summary>
/// Remove a specific item from the stack.
/// </summary>
/// <param name="i">The index of the item to remove.</param>
[SuppressMessage("Microsoft.Performance", "CA1811", Justification = "Kept for potential future usage.")]
public void RemoveAt ( int i )
{
this . m_items [ i ] = null ;
Array . Copy ( this . m_items , i + 1 , this . m_items , i , this . m_count - i - 1 );
this . m_count --;
}
}
2013-12-17 22:50:36 +01:00
2013-12-24 10:50:46 +01:00
/// <summary>
/// This class represents an attribute. The AttDef is assigned
/// from a validation process, and is used to provide default values.
/// </summary>
internal class Attribute
{
internal string Name ; // the atomized name.
internal AttDef DtdType ; // the AttDef of the attribute from the SGML DTD.
internal char QuoteChar ; // the quote character used for the attribute value.
private string m_literalValue ; // the attribute value
2013-12-17 22:50:36 +01:00
2013-12-24 10:50:46 +01:00
/// <summary>
/// Attribute objects are reused during parsing to reduce memory allocations,
/// hence the Reset method.
/// </summary>
public void Reset ( string name , string value , char quote )
{
this . Name = name ;
this . m_literalValue = value ;
this . QuoteChar = quote ;
this . DtdType = null ;
}
public string Value
{
get
{
if ( this . m_literalValue != null )
return this . m_literalValue ;
if ( this . DtdType != null )
return this . DtdType . Default ;
return null ;
}
/* set
{
this.m_literalValue = value;
}*/
}
public bool IsDefault
{
get
{
return ( this . m_literalValue == null );
}
}
}
/// <summary>
/// This class models an XML node, an array of elements in scope is maintained while parsing
/// for validation purposes, and these Node objects are reused to reduce object allocation,
/// hence the reset method.
/// </summary>
internal class Node
{
internal XmlNodeType NodeType ;
internal string Value ;
internal XmlSpace Space ;
internal string XmlLang ;
internal bool IsEmpty ;
internal string Name ;
internal ElementDecl DtdType ; // the DTD type found via validation
internal State CurrentState ;
internal bool Simulated ; // tag was injected into result stream.
HWStack attributes = new HWStack ( 10 );
/// <summary>
/// Attribute objects are reused during parsing to reduce memory allocations,
/// hence the Reset method.
/// </summary>
public void Reset ( string name , XmlNodeType nt , string value )
{
this . Value = value ;
this . Name = name ;
this . NodeType = nt ;
this . Space = XmlSpace . None ;
this . XmlLang = null ;
this . IsEmpty = true ;
this . attributes . Count = 0 ;
this . DtdType = null ;
}
public Attribute AddAttribute ( string name , string value , char quotechar , bool caseInsensitive )
{
Attribute a ;
// check for duplicates!
for ( int i = 0 , n = this . attributes . Count ; i < n ; i ++)
{
a = ( Attribute ) this . attributes [ i ];
if ( string . Equals ( a . Name , name , caseInsensitive ? StringComparison . OrdinalIgnoreCase : StringComparison . Ordinal ))
2013-12-17 22:50:36 +01:00
{
2013-12-24 10:50:46 +01:00
return null ;
}
}
// This code makes use of the high water mark for attribute objects,
// and reuses exisint Attribute objects to avoid memory allocation.
a = ( Attribute ) this . attributes . Push ();
if ( a == null )
{
a = new Attribute ();
this . attributes [ this . attributes . Count - 1 ] = a ;
}
a . Reset ( name , value , quotechar );
return a ;
}
2013-12-17 22:50:36 +01:00
2013-12-24 10:50:46 +01:00
[SuppressMessage("Microsoft.Performance", "CA1811", Justification = "Kept for potential future usage.")]
public void RemoveAttribute ( string name )
{
for ( int i = 0 , n = this . attributes . Count ; i < n ; i ++)
{
Attribute a = ( Attribute ) this . attributes [ i ];
if ( string . Equals ( a . Name , name , StringComparison . OrdinalIgnoreCase ))
{
this . attributes . RemoveAt ( i );
return ;
}
}
}
public void CopyAttributes ( Node n )
{
for ( int i = 0 , len = n . attributes . Count ; i < len ; i ++)
{
Attribute a = ( Attribute ) n . attributes [ i ];
Attribute na = this . AddAttribute ( a . Name , a . Value , a . QuoteChar , false );
na . DtdType = a . DtdType ;
}
}
public int AttributeCount
{
get
{
return this . attributes . Count ;
}
}
public int GetAttribute ( string name )
{
for ( int i = 0 , n = this . attributes . Count ; i < n ; i ++)
{
Attribute a = ( Attribute ) this . attributes [ i ];
if ( string . Equals ( a . Name , name , StringComparison . OrdinalIgnoreCase ))
{
return i ;
}
}
return - 1 ;
}
public Attribute GetAttribute ( int i )
{
if ( i >= 0 && i < this . attributes . Count )
{
Attribute a = ( Attribute ) this . attributes [ i ];
return a ;
}
return null ;
}
}
internal enum State
{
Initial , // The initial state (Read has not been called yet)
Markup , // Expecting text or markup
EndTag , // Positioned on an end tag
Attr , // Positioned on an attribute
AttrValue , // Positioned in an attribute value
Text , // Positioned on a Text node.
PartialTag , // Positioned on a text node, and we have hit a start tag
AutoClose , // We are auto-closing tags (this is like State.EndTag), but end tag was generated
CData , // We are on a CDATA type node, eg. <scipt> where we have special parsing rules.
PartialText ,
PseudoStartTag , // we pushed a pseudo-start tag, need to continue with previous start tag.
Eof
}
/// <summary>
/// SgmlReader is an XmlReader API over any SGML document (including built in
/// support for HTML).
/// </summary>
public class SgmlReader : XmlReader
{
/// <summary>
/// The value returned when a namespace is queried and none has been specified.
/// </summary>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1705", Justification = "SgmlReader's standards for constants are different to Microsoft's and in line with older C++ style constants naming conventions. Visually, constants using this style are more easily identifiable as such.")]
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1707", Justification = "SgmlReader's standards for constants are different to Microsoft's and in line with older C++ style constants naming conventions. Visually, constants using this style are more easily identifiable as such.")]
public const string UNDEFINED_NAMESPACE = "#unknown" ;
private SgmlDtd m_dtd ;
private Entity m_current ;
private State m_state ;
private char m_partial ;
private string m_endTag ;
private HWStack m_stack ;
private Node m_node ; // current node (except for attributes)
// Attributes are handled separately using these members.
private Attribute m_a ;
private int m_apos ; // which attribute are we positioned on in the collection.
private Uri m_baseUri ;
private StringBuilder m_sb ;
private StringBuilder m_name ;
private TextWriter m_log ;
private bool m_foundRoot ;
private bool m_ignoreDtd ;
// autoclose support
private Node m_newnode ;
private int m_poptodepth ;
private int m_rootCount ;
private bool m_isHtml ;
private string m_rootElementName ;
private string m_href ;
private Entity m_lastError ;
private string m_proxy ;
private TextReader m_inputStream ;
private string m_syslit ;
private string m_pubid ;
private string m_subset ;
private string m_docType ;
private WhitespaceHandling m_whitespaceHandling ;
private CaseFolding m_folding = CaseFolding . None ;
private bool m_stripDocType = true ;
//private string m_startTag;
private Dictionary < string , string > unknownNamespaces = new Dictionary < string , string >();
/// <summary>
/// Initialises a new instance of the SgmlReader class.
/// </summary>
public SgmlReader ()
{
Init ();
}
/// <summary>
/// Initialises a new instance of the SgmlReader class with an existing <see cref="XmlNameTable"/>, which is NOT used.
/// </summary>
/// <param name="nt">The nametable to use.</param>
public SgmlReader ( XmlNameTable nt )
{
Init ();
}
/// <summary>
/// Specify the SgmlDtd object directly. This allows you to cache the Dtd and share
/// it across multipl SgmlReaders. To load a DTD from a URL use the SystemLiteral property.
/// </summary>
public SgmlDtd Dtd
{
get
{
if ( this . m_dtd == null )
{
LazyLoadDtd ( this . m_baseUri );
2013-12-17 22:50:36 +01:00
}
2013-12-24 10:50:46 +01:00
return this . m_dtd ;
}
set
{
this . m_dtd = value ;
}
}
private void LazyLoadDtd ( Uri baseUri )
{
if ( this . m_dtd == null && ! this . m_ignoreDtd )
{
if ( string . IsNullOrEmpty ( this . m_syslit ))
2013-12-17 22:50:36 +01:00
{
2013-12-24 10:50:46 +01:00
if ( this . m_docType != null && StringUtilities . EqualsIgnoreCase ( this . m_docType , "html" ))
{
2014-04-04 10:39:26 +02:00
Assembly a = typeof ( SgmlReader ). GetTypeInfo (). Assembly ;
2013-12-24 10:50:46 +01:00
string name = a . FullName . Split ( ',' )[ 0 ] + ".Html.dtd" ;
Stream stm = a . GetManifestResourceStream ( name );
if ( stm != null )
2013-12-17 22:50:36 +01:00
{
2013-12-24 10:50:46 +01:00
StreamReader sr = new StreamReader ( stm );
this . m_dtd = SgmlDtd . Parse ( baseUri , "HTML" , sr , null , this . m_proxy , null );
}
}
}
else
{
if ( baseUri != null )
{
baseUri = new Uri ( baseUri , this . m_syslit );
}
else if ( this . m_baseUri != null )
{
baseUri = new Uri ( this . m_baseUri , this . m_syslit );
}
else
{
2013-12-17 22:50:36 +01:00
#if PORTABLE
2013-12-24 10:50:46 +01:00
throw new InvalidOperationException ( "Cannot load DTD without specifying base URI." );
2013-12-17 22:50:36 +01:00
#else
baseUri = new Uri ( new Uri ( Directory . GetCurrentDirectory () + "/" ), this . m_syslit );
#endif
2013-12-24 10:50:46 +01:00
}
this . m_dtd = SgmlDtd . Parse ( baseUri , this . m_docType , this . m_pubid , baseUri . AbsoluteUri , this . m_subset , this . m_proxy , null );
}
}
2013-12-17 22:50:36 +01:00
2013-12-24 10:50:46 +01:00
if ( this . m_dtd != null && this . m_dtd . Name != null )
{
switch ( this . CaseFolding )
{
case CaseFolding . ToUpper :
this . m_rootElementName = this . m_dtd . Name . ToUpperInvariant ();
break ;
case CaseFolding . ToLower :
this . m_rootElementName = this . m_dtd . Name . ToLowerInvariant ();
break ;
default :
this . m_rootElementName = this . m_dtd . Name ;
break ;
2013-12-17 22:50:36 +01:00
}
2013-12-24 10:50:46 +01:00
this . m_isHtml = StringUtilities . EqualsIgnoreCase ( this . m_dtd . Name , "html" );
}
}
2013-12-17 22:50:36 +01:00
2013-12-24 10:50:46 +01:00
/// <summary>
/// The name of root element specified in the DOCTYPE tag.
/// </summary>
public string DocType
{
get
{
return this . m_docType ;
}
set
{
this . m_docType = value ;
}
}
2013-12-17 22:50:36 +01:00
2013-12-24 10:50:46 +01:00
/// <summary>
/// The root element of the document.
/// </summary>
public string RootElementName
{
get
{
return m_rootElementName ;
}
}
2013-12-17 22:50:36 +01:00
2013-12-24 10:50:46 +01:00
/// <summary>
/// The PUBLIC identifier in the DOCTYPE tag
/// </summary>
public string PublicIdentifier
{
get
{
return this . m_pubid ;
}
set
{
this . m_pubid = value ;
}
}
2013-12-17 22:50:36 +01:00
2013-12-24 10:50:46 +01:00
/// <summary>
/// The SYSTEM literal in the DOCTYPE tag identifying the location of the DTD.
/// </summary>
public string SystemLiteral
{
get
{
return this . m_syslit ;
}
set
{
this . m_syslit = value ;
}
}
2013-12-17 22:50:36 +01:00
2013-12-24 10:50:46 +01:00
/// <summary>
/// The DTD internal subset in the DOCTYPE tag
/// </summary>
public string InternalSubset
{
get
{
return this . m_subset ;
}
set
{
this . m_subset = value ;
}
}
2013-12-17 22:50:36 +01:00
2013-12-24 10:50:46 +01:00
/// <summary>
/// The input stream containing SGML data to parse.
/// You must specify this property or the Href property before calling Read().
/// </summary>
public TextReader InputStream
{
get
{
return this . m_inputStream ;
}
set
{
this . m_inputStream = value ;
Init ();
}
}
2013-12-17 22:50:36 +01:00
2013-12-24 10:50:46 +01:00
/// <summary>
/// Sometimes you need to specify a proxy server in order to load data via HTTP
/// from outside the firewall. For example: "itgproxy:80".
/// </summary>
public string WebProxy
{
get
{
return this . m_proxy ;
}
set
{
this . m_proxy = value ;
}
}
2013-12-17 22:50:36 +01:00
2013-12-24 10:50:46 +01:00
/// <summary>
/// The base Uri is used to resolve relative Uri's like the SystemLiteral and
/// Href properties. This is a method because BaseURI is a read-only
/// property on the base XmlReader class.
/// </summary>
public void SetBaseUri ( string uri )
{
this . m_baseUri = new Uri ( uri );
}
/// <summary>
/// Specify the location of the input SGML document as a URL.
/// </summary>
public string Href
{
get
{
return this . m_href ;
}
set
{
this . m_href = value ;
Init ();
if ( this . m_baseUri == null )
2013-12-17 22:50:36 +01:00
{
2013-12-24 10:50:46 +01:00
if ( this . m_href . IndexOf ( "://" ) > 0 )
{
this . m_baseUri = new Uri ( this . m_href );
}
else
{
2013-12-17 22:50:36 +01:00
#if PORTABLE
2013-12-24 10:50:46 +01:00
throw new ArgumentException ( "Relative URLs are not supported." );
2013-12-17 22:50:36 +01:00
#else
this . m_baseUri = new Uri ( "file:///" + Directory . GetCurrentDirectory () + "//" );
#endif
2013-12-24 10:50:46 +01:00
}
2013-12-17 22:50:36 +01:00
}
2013-12-24 10:50:46 +01:00
}
}
2013-12-17 22:50:36 +01:00
2013-12-24 10:50:46 +01:00
/// <summary>
/// Whether to strip out the DOCTYPE tag from the output (default true)
/// </summary>
public bool StripDocType
{
get
{
return this . m_stripDocType ;
}
set
{
this . m_stripDocType = value ;
}
}
2013-12-17 22:50:36 +01:00
2013-12-24 10:50:46 +01:00
/// <summary>
/// Gets or sets a value indicating whether to ignore any DTD reference.
/// </summary>
/// <value><c>true</c> if DTD references should be ignored; otherwise, <c>false</c>.</value>
public bool IgnoreDtd
{
get { return m_ignoreDtd ; }
set { m_ignoreDtd = value ; }
}
2013-12-17 22:50:36 +01:00
2013-12-24 10:50:46 +01:00
/// <summary>
/// The case conversion behaviour while processing tags.
/// </summary>
public CaseFolding CaseFolding
{
get
{
return this . m_folding ;
}
set
{
this . m_folding = value ;
}
}
2013-12-17 22:50:36 +01:00
2013-12-24 10:50:46 +01:00
/// <summary>
/// DTD validation errors are written to this stream.
/// </summary>
public TextWriter ErrorLog
{
get
{
return this . m_log ;
}
set
{
this . m_log = value ;
}
}
2013-12-17 22:50:36 +01:00
2013-12-24 10:50:46 +01:00
/// <summary>
/// DTD validation errors are written to this log file.
/// </summary>
2013-12-17 22:50:36 +01:00
#if PORTABLE
2013-12-24 10:50:46 +01:00
[Obsolete("Not supported on the .NETPortable runtime.")]
2013-12-17 22:50:36 +01:00
#endif
2013-12-24 10:50:46 +01:00
public string ErrorLogFile
{
2013-12-17 22:50:36 +01:00
#if PORTABLE
2013-12-24 10:50:46 +01:00
get
{
throw new NotSupportedException ();
}
set
{
throw new NotSupportedException ();
}
2013-12-17 22:50:36 +01:00
#else
get
{
return this . m_errorLogFile ;
}
set
{
this . m_errorLogFile = value ;
this . m_log = new StreamWriter ( value );
}
#endif
2013-12-24 10:50:46 +01:00
}
private void Log ( string msg , params string [] args )
{
if ( ErrorLog != null )
{
string err = string . Format ( CultureInfo . CurrentUICulture , msg , args );
if ( this . m_lastError != this . m_current )
{
err = err + " " + this . m_current . Context ();
this . m_lastError = this . m_current ;
ErrorLog . WriteLine ( "### Error:" + err );
}
else
{
string path = "" ;
if ( this . m_current . ResolvedUri != null )
{
path = this . m_current . ResolvedUri . AbsolutePath ;
}
ErrorLog . WriteLine ( "### Error in {0}#{1}, line {2}, position {3}: {4}" , path , this . m_current . Name , this . m_current . Line , this . m_current . LinePosition , err );
}
}
}
private void Log ( string msg , char ch )
{
Log ( msg , ch . ToString ());
}
private void Init ()
{
this . m_state = State . Initial ;
this . m_stack = new HWStack ( 10 );
this . m_node = Push ( null , XmlNodeType . Document , null );
this . m_node . IsEmpty = false ;
this . m_sb = new StringBuilder ();
this . m_name = new StringBuilder ();
this . m_poptodepth = 0 ;
this . m_current = null ;
this . m_partial = '\0' ;
this . m_endTag = null ;
this . m_a = null ;
this . m_apos = 0 ;
this . m_newnode = null ;
this . m_rootCount = 0 ;
this . m_foundRoot = false ;
this . unknownNamespaces . Clear ();
}
private Node Push ( string name , XmlNodeType nt , string value )
{
Node result = ( Node ) this . m_stack . Push ();
if ( result == null )
{
result = new Node ();
this . m_stack [ this . m_stack . Count - 1 ] = result ;
}
result . Reset ( name , nt , value );
this . m_node = result ;
return result ;
}
private void SwapTopNodes ()
{
int top = this . m_stack . Count - 1 ;
if ( top > 0 )
{
Node n = ( Node ) this . m_stack [ top - 1 ];
this . m_stack [ top - 1 ] = this . m_stack [ top ];
this . m_stack [ top ] = n ;
}
}
private Node Push ( Node n )
{
// we have to do a deep clone of the Node object because
// it is reused in the stack.
Node n2 = Push ( n . Name , n . NodeType , n . Value );
n2 . DtdType = n . DtdType ;
n2 . IsEmpty = n . IsEmpty ;
n2 . Space = n . Space ;
n2 . XmlLang = n . XmlLang ;
n2 . CurrentState = n . CurrentState ;
n2 . CopyAttributes ( n );
this . m_node = n2 ;
return n2 ;
}
private void Pop ()
{
if ( this . m_stack . Count > 1 )
{
this . m_node = ( Node ) this . m_stack . Pop ();
}
}
private Node Top ()
{
int top = this . m_stack . Count - 1 ;
if ( top > 0 )
{
return ( Node ) this . m_stack [ top ];
}
return null ;
}
/// <summary>
/// The node type of the node currently being parsed.
/// </summary>
public override XmlNodeType NodeType
{
get
{
if ( this . m_state == State . Attr )
{
return XmlNodeType . Attribute ;
}
else if ( this . m_state == State . AttrValue )
{
return XmlNodeType . Text ;
}
else if ( this . m_state == State . EndTag || this . m_state == State . AutoClose )
{
return XmlNodeType . EndElement ;
2013-12-17 22:50:36 +01:00
}
2013-12-24 10:50:46 +01:00
return this . m_node . NodeType ;
}
}
/// <summary>
/// The name of the current node, if currently positioned on a node or attribute.
/// </summary>
public override string Name
{
get
{
string result = null ;
if ( this . m_state == State . Attr )
2013-12-17 22:50:36 +01:00
{
2013-12-24 10:50:46 +01:00
result = XmlConvert . EncodeName ( this . m_a . Name );
}
else if ( this . m_state != State . AttrValue )
{
result = this . m_node . Name ;
}
return result ;
}
}
/// <summary>
/// The local name of the current node, if currently positioned on a node or attribute.
/// </summary>
public override string LocalName
{
get
{
string result = Name ;
if ( result != null )
{
int colon = result . IndexOf ( ':' );
if ( colon != - 1 )
{
result = result . Substring ( colon + 1 );
}
}
return result ;
}
}
/// <summary>
/// The namespace of the current node, if currently positioned on a node or attribute.
/// </summary>
/// <remarks>
/// If not positioned on a node or attribute, <see cref="UNDEFINED_NAMESPACE"/> is returned.
/// </remarks>
[SuppressMessage("Microsoft.Performance", "CA1820", Justification = "Cannot use IsNullOrEmpty in a switch statement and swapping the elegance of switch for a load of 'if's is not worth it.")]
public override string NamespaceURI
{
get
{
// SGML has no namespaces, unless this turned out to be an xmlns attribute.
if ( this . m_state == State . Attr && string . Equals ( this . m_a . Name , "xmlns" , StringComparison . OrdinalIgnoreCase ))
{
return "http://www.w3.org/2000/xmlns/" ;
}
string prefix = Prefix ;
switch ( Prefix )
{
case "xmlns" :
return "http://www.w3.org/2000/xmlns/" ;
case "xml" :
return "http://www.w3.org/XML/1998/namespace" ;
case null : // Should never occur since Prefix never returns null
case "" :
if ( NodeType == XmlNodeType . Attribute )
2013-12-17 22:50:36 +01:00
{
2013-12-24 10:50:46 +01:00
// attributes without a prefix are never in any namespace
return string . Empty ;
}
else if ( NodeType == XmlNodeType . Element )
{
// check if a 'xmlns:prefix' attribute is defined
for ( int i = this . m_stack . Count - 1 ; i > 0 ; -- i )
{
Node node = this . m_stack [ i ] as Node ;
if (( node != null ) && ( node . NodeType == XmlNodeType . Element ))
2013-12-17 22:50:36 +01:00
{
2013-12-24 10:50:46 +01:00
int index = node . GetAttribute ( "xmlns" );
if ( index >= 0 )
{
string value = node . GetAttribute ( index ). Value ;
if ( value != null )
{
return value ;
}
}
}
}
}
return string . Empty ;
default :
{
string value ;
if (( NodeType == XmlNodeType . Attribute ) || ( NodeType == XmlNodeType . Element ))
{
// check if a 'xmlns:prefix' attribute is defined
string key = "xmlns:" + prefix ;
for ( int i = this . m_stack . Count - 1 ; i > 0 ; -- i )
{
Node node = this . m_stack [ i ] as Node ;
if (( node != null ) && ( node . NodeType == XmlNodeType . Element ))
{
int index = node . GetAttribute ( key );
if ( index >= 0 )
{
value = node . GetAttribute ( index ). Value ;
if ( value != null )
{
return value ;
}
}
}
}
}
// check if we've seen this prefix before
if (! unknownNamespaces . TryGetValue ( prefix , out value ))
{
if ( unknownNamespaces . Count > 0 )
{
value = UNDEFINED_NAMESPACE + unknownNamespaces . Count . ToString ();
2013-12-17 22:50:36 +01:00
}
else
{
2013-12-24 10:50:46 +01:00
value = UNDEFINED_NAMESPACE ;
2013-12-17 22:50:36 +01:00
}
2013-12-24 10:50:46 +01:00
unknownNamespaces [ prefix ] = value ;
}
return value ;
2013-12-17 22:50:36 +01:00
}
}
2013-12-24 10:50:46 +01:00
}
}
2013-12-17 22:50:36 +01:00
2013-12-24 10:50:46 +01:00
/// <summary>
/// The prefix of the current node's name.
/// </summary>
public override string Prefix
{
get
{
string result = Name ;
if ( result != null )
2013-12-17 22:50:36 +01:00
{
2013-12-24 10:50:46 +01:00
int colon = result . IndexOf ( ':' );
if ( colon != - 1 )
{
result = result . Substring ( 0 , colon );
}
else
{
result = string . Empty ;
}
2013-12-17 22:50:36 +01:00
}
2013-12-24 10:50:46 +01:00
return result ?? string . Empty ;
}
}
2013-12-17 22:50:36 +01:00
2013-12-24 10:50:46 +01:00
/// <summary>
/// Whether the current node has a value or not.
/// </summary>
public override bool HasValue
{
get
{
if ( this . m_state == State . Attr || this . m_state == State . AttrValue )
2013-12-17 22:50:36 +01:00
{
2013-12-24 10:50:46 +01:00
return true ;
2013-12-17 22:50:36 +01:00
}
2013-12-24 10:50:46 +01:00
return ( this . m_node . Value != null );
}
}
/// <summary>
/// The value of the current node.
/// </summary>
public override string Value
{
get
{
if ( this . m_state == State . Attr || this . m_state == State . AttrValue )
2013-12-17 22:50:36 +01:00
{
2013-12-24 10:50:46 +01:00
return this . m_a . Value ;
2013-12-17 22:50:36 +01:00
}
2013-12-24 10:50:46 +01:00
return this . m_node . Value ;
}
}
/// <summary>
/// Gets the depth of the current node in the XML document.
/// </summary>
/// <value>The depth of the current node in the XML document.</value>
public override int Depth
{
get
{
if ( this . m_state == State . Attr )
2013-12-17 22:50:36 +01:00
{
2013-12-24 10:50:46 +01:00
return this . m_stack . Count ;
2013-12-17 22:50:36 +01:00
}
2013-12-24 10:50:46 +01:00
else if ( this . m_state == State . AttrValue )
2013-12-17 22:50:36 +01:00
{
2013-12-24 10:50:46 +01:00
return this . m_stack . Count + 1 ;
2013-12-17 22:50:36 +01:00
}
2013-12-24 10:50:46 +01:00
return this . m_stack . Count - 1 ;
}
}
/// <summary>
/// Gets the base URI of the current node.
/// </summary>
/// <value>The base URI of the current node.</value>
public override string BaseURI
{
get
{
return this . m_baseUri == null ? "" : this . m_baseUri . AbsoluteUri ;
}
}
/// <summary>
/// Gets a value indicating whether the current node is an empty element (for example, <MyElement/>).
/// </summary>
public override bool IsEmptyElement
{
get
{
if ( this . m_state == State . Markup || this . m_state == State . Attr || this . m_state == State . AttrValue )
2013-12-17 22:50:36 +01:00
{
2013-12-24 10:50:46 +01:00
return this . m_node . IsEmpty ;
2013-12-17 22:50:36 +01:00
}
2013-12-24 10:50:46 +01:00
return false ;
}
}
2013-12-17 22:50:36 +01:00
2013-12-24 10:50:46 +01:00
/// <summary>
/// Gets a value indicating whether the current node is an attribute that was generated from the default value defined in the DTD or schema.
/// </summary>
/// <value>
/// true if the current node is an attribute whose value was generated from the default value defined in the DTD or
/// schema; false if the attribute value was explicitly set.
/// </value>
public override bool IsDefault
{
get
{
if ( this . m_state == State . Attr || this . m_state == State . AttrValue )
return this . m_a . IsDefault ;
2013-12-17 22:50:36 +01:00
2013-12-24 10:50:46 +01:00
return false ;
}
}
2013-12-17 22:50:36 +01:00
2013-12-24 10:50:46 +01:00
/// <summary>
/// Gets the quotation mark character used to enclose the value of an attribute node.
/// </summary>
/// <value>The quotation mark character (" or ') used to enclose the value of an attribute node.</value>
/// <remarks>
/// This property applies only to an attribute node.
/// </remarks>
2013-12-17 22:50:36 +01:00
#if PORTABLE
2013-12-24 10:50:46 +01:00
public char QuoteChar
2013-12-17 22:50:36 +01:00
#else
public override char QuoteChar
#endif
2013-12-24 10:50:46 +01:00
{
get
{
if ( this . m_a != null )
return this . m_a . QuoteChar ;
return '\0' ;
}
}
/// <summary>
/// Gets the current xml:space scope.
/// </summary>
/// <value>One of the <see cref="XmlSpace"/> values. If no xml:space scope exists, this property defaults to XmlSpace.None.</value>
public override XmlSpace XmlSpace
{
get
{
for ( int i = this . m_stack . Count - 1 ; i > 1 ; i --)
2013-12-17 22:50:36 +01:00
{
2013-12-24 10:50:46 +01:00
Node n = ( Node ) this . m_stack [ i ];
XmlSpace xs = n . Space ;
if ( xs != XmlSpace . None )
return xs ;
2013-12-17 22:50:36 +01:00
}
2013-12-24 10:50:46 +01:00
return XmlSpace . None ;
}
}
/// <summary>
/// Gets the current xml:lang scope.
/// </summary>
/// <value>The current xml:lang scope.</value>
public override string XmlLang
{
get
{
for ( int i = this . m_stack . Count - 1 ; i > 1 ; i --)
2013-12-17 22:50:36 +01:00
{
2013-12-24 10:50:46 +01:00
Node n = ( Node ) this . m_stack [ i ];
string xmllang = n . XmlLang ;
if ( xmllang != null )
return xmllang ;
2013-12-17 22:50:36 +01:00
}
2013-12-24 10:50:46 +01:00
return string . Empty ;
}
}
/// <summary>
/// Specifies how white space is handled.
/// </summary>
public WhitespaceHandling WhitespaceHandling
{
get
{
return this . m_whitespaceHandling ;
}
set
{
this . m_whitespaceHandling = value ;
}
}
/// <summary>
/// Gets the number of attributes on the current node.
/// </summary>
/// <value>The number of attributes on the current node.</value>
public override int AttributeCount
{
get
{
if ( this . m_state == State . Attr || this . m_state == State . AttrValue )
//For compatibility with mono
return this . m_node . AttributeCount ;
else if ( this . m_node . NodeType == XmlNodeType . Element || this . m_node . NodeType == XmlNodeType . DocumentType )
return this . m_node . AttributeCount ;
else
return 0 ;
}
}
/// <summary>
/// Gets the value of an attribute with the specified <see cref="Name"/>.
/// </summary>
/// <param name="name">The name of the attribute to retrieve.</param>
/// <returns>The value of the specified attribute. If the attribute is not found, a null reference (Nothing in Visual Basic) is returned. </returns>
public override string GetAttribute ( string name )
{
if ( this . m_state != State . Attr && this . m_state != State . AttrValue )
{
int i = this . m_node . GetAttribute ( name );
if ( i >= 0 )
return GetAttribute ( i );
}
return null ;
}
/// <summary>
/// Gets the value of the attribute with the specified <see cref="LocalName"/> and <see cref="NamespaceURI"/>.
/// </summary>
/// <param name="name">The local name of the attribute.</param>
/// <param name="namespaceURI">The namespace URI of the attribute.</param>
/// <returns>The value of the specified attribute. If the attribute is not found, a null reference (Nothing in Visual Basic) is returned. This method does not move the reader.</returns>
public override string GetAttribute ( string name , string namespaceURI )
{
return GetAttribute ( name ); // SGML has no namespaces.
}
/// <summary>
/// Gets the value of the attribute with the specified index.
/// </summary>
/// <param name="i">The index of the attribute.</param>
/// <returns>The value of the specified attribute. This method does not move the reader.</returns>
public override string GetAttribute ( int i )
{
if ( this . m_state != State . Attr && this . m_state != State . AttrValue )
{
Attribute a = this . m_node . GetAttribute ( i );
if ( a != null )
return a . Value ;
}
throw new ArgumentOutOfRangeException ( "i" );
}
/// <summary>
/// Gets the value of the attribute with the specified index.
/// </summary>
/// <param name="i">The index of the attribute.</param>
/// <returns>The value of the specified attribute. This method does not move the reader.</returns>
public override string this [ int i ]
{
get
{
return GetAttribute ( i );
}
}
/// <summary>
/// Gets the value of an attribute with the specified <see cref="Name"/>.
/// </summary>
/// <param name="name">The name of the attribute to retrieve.</param>
/// <returns>The value of the specified attribute. If the attribute is not found, a null reference (Nothing in Visual Basic) is returned. </returns>
public override string this [ string name ]
{
get
{
return GetAttribute ( name );
}
}
/// <summary>
/// Gets the value of the attribute with the specified <see cref="LocalName"/> and <see cref="NamespaceURI"/>.
/// </summary>
/// <param name="name">The local name of the attribute.</param>
/// <param name="namespaceURI">The namespace URI of the attribute.</param>
/// <returns>The value of the specified attribute. If the attribute is not found, a null reference (Nothing in Visual Basic) is returned. This method does not move the reader.</returns>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1023", Justification = "This design is that of Microsoft's XmlReader class and overriding its method is merely continuing the same design.")]
public override string this [ string name , string namespaceURI ]
{
get
{
return GetAttribute ( name , namespaceURI );
}
}
/// <summary>
/// Moves to the atttribute with the specified <see cref="Name"/>.
/// </summary>
/// <param name="name">The qualified name of the attribute.</param>
/// <returns>true if the attribute is found; otherwise, false. If false, the reader's position does not change.</returns>
public override bool MoveToAttribute ( string name )
{
int i = this . m_node . GetAttribute ( name );
if ( i >= 0 )
{
MoveToAttribute ( i );
return true ;
}
return false ;
}
/// <summary>
/// Moves to the attribute with the specified <see cref="LocalName"/> and <see cref="NamespaceURI"/>.
/// </summary>
/// <param name="name">The local name of the attribute.</param>
/// <param name="ns">The namespace URI of the attribute.</param>
/// <returns>true if the attribute is found; otherwise, false. If false, the reader's position does not change.</returns>
public override bool MoveToAttribute ( string name , string ns )
{
return MoveToAttribute ( name );
}
/// <summary>
/// Moves to the attribute with the specified index.
/// </summary>
/// <param name="i">The index of the attribute to move to.</param>
public override void MoveToAttribute ( int i )
{
Attribute a = this . m_node . GetAttribute ( i );
if ( a != null )
{
this . m_apos = i ;
this . m_a = a ;
//Make sure that AttrValue does not overwrite the preserved value
if ( this . m_state != State . Attr && this . m_state != State . AttrValue )
2013-12-17 22:50:36 +01:00
{
2013-12-24 10:50:46 +01:00
this . m_node . CurrentState = this . m_state ; //save current state.
2013-12-17 22:50:36 +01:00
}
2013-12-24 10:50:46 +01:00
this . m_state = State . Attr ;
return ;
}
throw new ArgumentOutOfRangeException ( "i" );
}
/// <summary>
/// Moves to the first attribute.
/// </summary>
/// <returns></returns>
public override bool MoveToFirstAttribute ()
{
if ( this . m_node . AttributeCount > 0 )
{
MoveToAttribute ( 0 );
return true ;
}
return false ;
}
/// <summary>
/// Moves to the next attribute.
/// </summary>
/// <returns>true if there is a next attribute; false if there are no more attributes.</returns>
/// <remarks>
/// If the current node is an element node, this method is equivalent to <see cref="MoveToFirstAttribute"/>. If <see cref="MoveToNextAttribute"/> returns true,
/// the reader moves to the next attribute; otherwise, the position of the reader does not change.
/// </remarks>
public override bool MoveToNextAttribute ()
{
if ( this . m_state != State . Attr && this . m_state != State . AttrValue )
{
return MoveToFirstAttribute ();
}
else if ( this . m_apos < this . m_node . AttributeCount - 1 )
{
MoveToAttribute ( this . m_apos + 1 );
return true ;
}
else
return false ;
}
/// <summary>
/// Moves to the element that contains the current attribute node.
/// </summary>
/// <returns>
/// true if the reader is positioned on an attribute (the reader moves to the element that owns the attribute); false if the reader is not positioned
/// on an attribute (the position of the reader does not change).
/// </returns>
public override bool MoveToElement ()
{
if ( this . m_state == State . Attr || this . m_state == State . AttrValue )
{
this . m_state = this . m_node . CurrentState ;
this . m_a = null ;
return true ;
}
else
return ( this . m_node . NodeType == XmlNodeType . Element );
}
/// <summary>
/// Gets whether the content is HTML or not.
/// </summary>
public bool IsHtml
{
get
{
return this . m_isHtml ;
}
}
/// <summary>
/// Returns the encoding of the current entity.
/// </summary>
/// <returns>The encoding of the current entity.</returns>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1024", Justification = "This method to get the encoding does not simply read a value, but potentially causes significant processing of the input stream.")]
public Encoding GetEncoding ()
{
if ( this . m_current == null )
{
OpenInput ();
}
return this . m_current . Encoding ;
}
private void OpenInput ()
{
LazyLoadDtd ( this . m_baseUri );
if ( this . Href != null )
{
this . m_current = new Entity ( "#document" , null , this . m_href , this . m_proxy );
}
else if ( this . m_inputStream != null )
{
this . m_current = new Entity ( "#document" , null , this . m_inputStream , this . m_proxy );
}
else
{
throw new InvalidOperationException ( "You must specify input either via Href or InputStream properties" );
}
this . m_current . IsHtml = this . IsHtml ;
this . m_current . Open ( null , this . m_baseUri );
if ( this . m_current . ResolvedUri != null )
this . m_baseUri = this . m_current . ResolvedUri ;
if ( this . m_current . IsHtml && this . m_dtd == null )
{
this . m_docType = "HTML" ;
LazyLoadDtd ( this . m_baseUri );
}
}
/// <summary>
/// Reads the next node from the stream.
/// </summary>
/// <returns>true if the next node was read successfully; false if there are no more nodes to read.</returns>
public override bool Read ()
{
if ( m_current == null )
{
OpenInput ();
}
if ( m_node . Simulated )
{
// return the next node
m_node . Simulated = false ;
this . m_node = Top ();
this . m_state = this . m_node . CurrentState ;
return true ;
}
bool foundnode = false ;
while (! foundnode )
{
switch ( this . m_state )
2013-12-17 22:50:36 +01:00
{
2013-12-24 10:50:46 +01:00
case State . Initial :
2013-12-17 22:50:36 +01:00
this . m_state = State . Markup ;
this . m_current . ReadChar ();
2013-12-24 10:50:46 +01:00
goto case State . Markup ;
case State . Eof :
if ( this . m_current . Parent != null )
2013-12-17 22:50:36 +01:00
{
2013-12-24 10:50:46 +01:00
this . m_current . Close ();
this . m_current = this . m_current . Parent ;
2013-12-17 22:50:36 +01:00
}
else
{
2013-12-24 10:50:46 +01:00
return false ;
2013-12-17 22:50:36 +01:00
}
2013-12-24 10:50:46 +01:00
break ;
case State . EndTag :
if ( string . Equals ( this . m_endTag , this . m_node . Name , StringComparison . OrdinalIgnoreCase ))
2013-12-17 22:50:36 +01:00
{
2013-12-24 10:50:46 +01:00
Pop (); // we're done!
this . m_state = State . Markup ;
goto case State . Markup ;
}
Pop (); // close one element
foundnode = true ; // return another end element.
break ;
case State . Markup :
if ( this . m_node . IsEmpty )
{
Pop ();
}
foundnode = ParseMarkup ();
break ;
case State . PartialTag :
Pop (); // remove text node.
this . m_state = State . Markup ;
foundnode = ParseTag ( this . m_partial );
break ;
case State . PseudoStartTag :
foundnode = ParseStartTag ( '<' );
break ;
case State . AutoClose :
Pop (); // close next node.
if ( this . m_stack . Count <= this . m_poptodepth )
{
this . m_state = State . Markup ;
if ( this . m_newnode != null )
{
Push ( this . m_newnode ); // now we're ready to start the new node.
this . m_newnode = null ;
this . m_state = State . Markup ;
}
else if ( this . m_node . NodeType == XmlNodeType . Document )
{
this . m_state = State . Eof ;
goto case State . Eof ;
}
}
foundnode = true ;
break ;
case State . CData :
foundnode = ParseCData ();
break ;
case State . Attr :
goto case State . AttrValue ;
case State . AttrValue :
this . m_state = State . Markup ;
goto case State . Markup ;
case State . Text :
Pop ();
goto case State . Markup ;
case State . PartialText :
if ( ParseText ( this . m_current . Lastchar , false ))
{
this . m_node . NodeType = XmlNodeType . Whitespace ;
2013-12-17 22:50:36 +01:00
}
2013-12-24 10:50:46 +01:00
foundnode = true ;
break ;
}
if ( foundnode && this . m_node . NodeType == XmlNodeType . Whitespace && this . m_whitespaceHandling == WhitespaceHandling . None )
{
// strip out whitespace (caller is probably pretty printing the XML).
foundnode = false ;
}
if (! foundnode && this . m_state == State . Eof && this . m_stack . Count > 1 )
{
this . m_poptodepth = 1 ;
this . m_state = State . AutoClose ;
this . m_node = Top ();
return true ;
}
}
if (! m_foundRoot && ( this . NodeType == XmlNodeType . Element ||
this . NodeType == XmlNodeType . Text ||
this . NodeType == XmlNodeType . CDATA ))
{
m_foundRoot = true ;
if ( this . IsHtml && ( this . NodeType != XmlNodeType . Element ||
! string . Equals ( this . LocalName , "html" , StringComparison . OrdinalIgnoreCase )))
{
// Simulate an HTML root element!
this . m_node . CurrentState = this . m_state ;
Node root = Push ( "html" , XmlNodeType . Element , null );
SwapTopNodes (); // make html the outer element.
this . m_node = root ;
root . Simulated = true ;
root . IsEmpty = false ;
this . m_state = State . Markup ;
//this.state = State.PseudoStartTag;
//this.startTag = name;
}
return true ;
}
return true ;
}
private bool ParseMarkup ()
{
char ch = this . m_current . Lastchar ;
if ( ch == '<' )
{
ch = this . m_current . ReadChar ();
return ParseTag ( ch );
}
else if ( ch != Entity . EOF )
{
if ( this . m_node . DtdType != null && this . m_node . DtdType . ContentModel . DeclaredContent == DeclaredContent . CDATA )
{
// e.g. SCRIPT or STYLE tags which contain unparsed character data.
this . m_partial = '\0' ;
this . m_state = State . CData ;
return false ;
}
else if ( ParseText ( ch , true ))
{
this . m_node . NodeType = XmlNodeType . Whitespace ;
}
return true ;
}
this . m_state = State . Eof ;
return false ;
}
private const string declterm = " \t\r\n><" ;
private bool ParseTag ( char ch )
{
if ( ch == '%' )
{
return ParseAspNet ();
}
else if ( ch == '!' )
{
ch = this . m_current . ReadChar ();
if ( ch == '-' )
{
return ParseComment ();
}
else if ( ch == '[' )
{
return ParseConditionalBlock ();
}
else if ( ch != '_' && ! char . IsLetter ( ch ))
{
// perhaps it's one of those nasty office document hacks like '<![if ! ie ]>'
string value = this . m_current . ScanToEnd ( this . m_sb , "Recovering" , ">" ); // skip it
Log ( "Ignoring invalid markup '<!" + value + ">" );
return false ;
}
else
{
string name = this . m_current . ScanToken ( this . m_sb , SgmlReader . declterm , false );
if ( string . Equals ( name , "DOCTYPE" , StringComparison . OrdinalIgnoreCase ))
{
ParseDocType ();
// In SGML DOCTYPE SYSTEM attribute is optional, but in XML it is required,
// therefore if there is no SYSTEM literal then add an empty one.
if ( this . GetAttribute ( "SYSTEM" ) == null && this . GetAttribute ( "PUBLIC" ) != null )
{
this . m_node . AddAttribute ( "SYSTEM" , "" , '"' , this . m_folding == CaseFolding . None );
}
if ( m_stripDocType )
{
return false ;
}
else
{
this . m_node . NodeType = XmlNodeType . DocumentType ;
return true ;
}
}
else
{
Log ( "Invalid declaration '<!{0}...'. Expecting '<!DOCTYPE' only." , name );
this . m_current . ScanToEnd ( null , "Recovering" , ">" ); // skip it
2013-12-17 22:50:36 +01:00
return false ;
2013-12-24 10:50:46 +01:00
}
}
}
else if ( ch == '?' )
{
this . m_current . ReadChar (); // consume the '?' character.
return ParsePI ();
}
else if ( ch == '/' )
{
return ParseEndTag ();
}
else
{
return ParseStartTag ( ch );
}
}
private string ScanName ( string terminators )
{
string name = this . m_current . ScanToken ( this . m_sb , terminators , false );
switch ( this . m_folding )
{
case CaseFolding . ToUpper :
name = name . ToUpperInvariant ();
break ;
case CaseFolding . ToLower :
name = name . ToLowerInvariant ();
break ;
}
return name ;
}
private static bool VerifyName ( string name )
{
try
{
XmlConvert . VerifyName ( name );
return true ;
}
catch ( XmlException )
{
return false ;
}
}
private const string tagterm = " \t\r\n=/><" ;
private const string aterm = " \t\r\n='\"/>" ;
private const string avterm = " \t\r\n>" ;
private bool ParseStartTag ( char ch )
{
string name = null ;
if ( m_state != State . PseudoStartTag )
{
if ( SgmlReader . tagterm . IndexOf ( ch ) >= 0 )
{
this . m_sb . Length = 0 ;
this . m_sb . Append ( '<' );
this . m_state = State . PartialText ;
return false ;
2013-12-17 22:50:36 +01:00
}
2013-12-24 10:50:46 +01:00
name = ScanName ( SgmlReader . tagterm );
}
else
{
// TODO: Changes by mindtouch mean that this.startTag is never non-null. The effects of this need checking.
//name = this.startTag;
m_state = State . Markup ;
}
Node n = Push ( name , XmlNodeType . Element , null );
n . IsEmpty = false ;
Validate ( n );
ch = this . m_current . SkipWhitespace ();
while ( ch != Entity . EOF && ch != '>' )
{
if ( ch == '/' )
2013-12-17 22:50:36 +01:00
{
2013-12-24 10:50:46 +01:00
n . IsEmpty = true ;
ch = this . m_current . ReadChar ();
if ( ch != '>' )
{
Log ( "Expected empty start tag '/>' sequence instead of '{0}'" , ch );
this . m_current . ScanToEnd ( null , "Recovering" , ">" );
return false ;
}
break ;
}
else if ( ch == '<' )
{
Log ( "Start tag '{0}' is missing '>'" , name );
break ;
2013-12-17 22:50:36 +01:00
}
2013-12-24 10:50:46 +01:00
string aname = ScanName ( SgmlReader . aterm );
ch = this . m_current . SkipWhitespace ();
if ( string . Equals ( aname , "," , StringComparison . OrdinalIgnoreCase ) ||
string . Equals ( aname , "=" , StringComparison . OrdinalIgnoreCase ) ||
string . Equals ( aname , ":" , StringComparison . OrdinalIgnoreCase ) ||
string . Equals ( aname , ";" , StringComparison . OrdinalIgnoreCase ))
2013-12-17 22:50:36 +01:00
{
2013-12-24 10:50:46 +01:00
continue ;
2013-12-17 22:50:36 +01:00
}
2013-12-24 10:50:46 +01:00
string value = null ;
char quote = '\0' ;
if ( ch == '=' || ch == '"' || ch == '\'' )
2013-12-17 22:50:36 +01:00
{
2013-12-24 10:50:46 +01:00
if ( ch == '=' )
{
this . m_current . ReadChar ();
ch = this . m_current . SkipWhitespace ();
}
if ( ch == '\'' || ch == '\"' )
{
quote = ch ;
value = ScanLiteral ( this . m_sb , ch );
}
else if ( ch != '>' )
{
string term = SgmlReader . avterm ;
value = this . m_current . ScanToken ( this . m_sb , term , false );
}
}
if ( ValidAttributeName ( aname ))
{
Attribute a = n . AddAttribute ( aname , value ?? aname , quote , this . m_folding == CaseFolding . None );
if ( a == null )
{
Log ( "Duplicate attribute '{0}' ignored" , aname );
}
else
{
ValidateAttribute ( n , a );
}
}
ch = this . m_current . SkipWhitespace ();
}
if ( ch == Entity . EOF )
{
this . m_current . Error ( "Unexpected EOF parsing start tag '{0}'" , name );
}
else if ( ch == '>' )
{
this . m_current . ReadChar (); // consume '>'
}
if ( this . Depth == 1 )
{
if ( this . m_rootCount == 1 )
{
// Hmmm, we found another root level tag, soooo, the only
// thing we can do to keep this a valid XML document is stop
this . m_state = State . Eof ;
return false ;
}
this . m_rootCount ++;
}
ValidateContent ( n );
return true ;
}
private bool ParseEndTag ()
{
this . m_state = State . EndTag ;
this . m_current . ReadChar (); // consume '/' char.
string name = this . ScanName ( SgmlReader . tagterm );
char ch = this . m_current . SkipWhitespace ();
if ( ch != '>' )
{
Log ( "Expected empty start tag '/>' sequence instead of '{0}'" , ch );
this . m_current . ScanToEnd ( null , "Recovering" , ">" );
}
this . m_current . ReadChar (); // consume '>'
this . m_endTag = name ;
// Make sure there's a matching start tag for it.
bool caseInsensitive = ( this . m_folding == CaseFolding . None );
this . m_node = ( Node ) this . m_stack [ this . m_stack . Count - 1 ];
for ( int i = this . m_stack . Count - 1 ; i > 0 ; i --)
{
Node n = ( Node ) this . m_stack [ i ];
if ( string . Equals ( n . Name , name , caseInsensitive ? StringComparison . OrdinalIgnoreCase : StringComparison . Ordinal ))
{
this . m_endTag = n . Name ;
return true ;
}
}
Log ( "No matching start tag for '</{0}>'" , name );
this . m_state = State . Markup ;
return false ;
}
private bool ParseAspNet ()
{
string value = "<%" + this . m_current . ScanToEnd ( this . m_sb , "AspNet" , "%>" ) + "%>" ;
Push ( null , XmlNodeType . CDATA , value );
return true ;
}
private bool ParseComment ()
{
char ch = this . m_current . ReadChar ();
if ( ch != '-' )
{
Log ( "Expecting comment '<!--' but found {0}" , ch );
this . m_current . ScanToEnd ( null , "Comment" , ">" );
return false ;
}
string value = this . m_current . ScanToEnd ( this . m_sb , "Comment" , "-->" );
// Make sure it's a valid comment!
int i = value . IndexOf ( "--" );
while ( i >= 0 )
{
int j = i + 2 ;
while ( j < value . Length && value [ j ] == '-' )
j ++;
if ( i > 0 )
{
value = value . Substring ( 0 , i - 1 ) + "-" + value . Substring ( j );
}
else
{
value = "-" + value . Substring ( j );
}
i = value . IndexOf ( "--" );
}
if ( value . Length > 0 && value [ value . Length - 1 ] == '-' )
{
value += " " ; // '-' cannot be last character
}
Push ( null , XmlNodeType . Comment , value );
return true ;
}
private const string cdataterm = "\t\r\n[]<>" ;
private bool ParseConditionalBlock ()
{
char ch = m_current . ReadChar (); // skip '['
ch = m_current . SkipWhitespace ();
string name = m_current . ScanToken ( m_sb , cdataterm , false );
if ( name . StartsWith ( "if " ))
{
// 'downlevel-revealed' comment (another atrocity of the IE team)
m_current . ScanToEnd ( null , "CDATA" , ">" );
return false ;
}
else if (! string . Equals ( name , "CDATA" , StringComparison . OrdinalIgnoreCase ))
{
Log ( "Expecting CDATA but found '{0}'" , name );
m_current . ScanToEnd ( null , "CDATA" , ">" );
return false ;
}
else
{
ch = m_current . SkipWhitespace ();
if ( ch != '[' )
{
Log ( "Expecting '[' but found '{0}'" , ch );
m_current . ScanToEnd ( null , "CDATA" , ">" );
return false ;
}
string value = m_current . ScanToEnd ( m_sb , "CDATA" , "]]>" );
Push ( null , XmlNodeType . CDATA , value );
return true ;
}
}
private const string dtterm = " \t\r\n>" ;
private void ParseDocType ()
{
char ch = this . m_current . SkipWhitespace ();
string name = this . ScanName ( SgmlReader . dtterm );
Push ( name , XmlNodeType . DocumentType , null );
ch = this . m_current . SkipWhitespace ();
if ( ch != '>' )
{
string subset = "" ;
string pubid = "" ;
string syslit = "" ;
if ( ch != '[' )
{
string token = this . m_current . ScanToken ( this . m_sb , SgmlReader . dtterm , false );
if ( string . Equals ( token , "PUBLIC" , StringComparison . OrdinalIgnoreCase ))
{
ch = this . m_current . SkipWhitespace ();
if ( ch == '\"' || ch == '\'' )
2013-12-17 22:50:36 +01:00
{
2013-12-24 10:50:46 +01:00
pubid = this . m_current . ScanLiteral ( this . m_sb , ch );
this . m_node . AddAttribute ( token , pubid , ch , this . m_folding == CaseFolding . None );
}
}
else if (! string . Equals ( token , "SYSTEM" , StringComparison . OrdinalIgnoreCase ))
{
Log ( "Unexpected token in DOCTYPE '{0}'" , token );
this . m_current . ScanToEnd ( null , "DOCTYPE" , ">" );
}
ch = this . m_current . SkipWhitespace ();
if ( ch == '\"' || ch == '\'' )
{
token = "SYSTEM" ;
syslit = this . m_current . ScanLiteral ( this . m_sb , ch );
this . m_node . AddAttribute ( token , syslit , ch , this . m_folding == CaseFolding . None );
}
ch = this . m_current . SkipWhitespace ();
}
if ( ch == '[' )
{
subset = this . m_current . ScanToEnd ( this . m_sb , "Internal Subset" , "]" );
this . m_node . Value = subset ;
}
ch = this . m_current . SkipWhitespace ();
if ( ch != '>' )
{
Log ( "Expecting end of DOCTYPE tag, but found '{0}'" , ch );
this . m_current . ScanToEnd ( null , "DOCTYPE" , ">" );
}
if ( this . m_dtd != null && ! string . Equals ( this . m_dtd . Name , name , StringComparison . OrdinalIgnoreCase ))
{
throw new InvalidOperationException ( "DTD does not match document type" );
}
this . m_docType = name ;
this . m_pubid = pubid ;
this . m_syslit = syslit ;
this . m_subset = subset ;
LazyLoadDtd ( this . m_current . ResolvedUri );
}
this . m_current . ReadChar ();
}
private const string piterm = " \t\r\n?" ;
private bool ParsePI ()
{
string name = this . m_current . ScanToken ( this . m_sb , SgmlReader . piterm , false );
string value = null ;
if ( this . m_current . Lastchar != '?' )
{
// Notice this is not "?>". This is because Office generates bogus PI's that end with "/>".
value = this . m_current . ScanToEnd ( this . m_sb , "Processing Instruction" , ">" );
value = value . TrimEnd ( '/' );
}
else
{
// error recovery.
value = this . m_current . ScanToEnd ( this . m_sb , "Processing Instruction" , ">" );
}
// check if the name has a prefix; if so, ignore it
int colon = name . IndexOf ( ':' );
if ( colon > 0 )
{
name = name . Substring ( colon + 1 );
}
// skip xml declarations, since these are generated in the output instead.
if (! string . Equals ( name , "xml" , StringComparison . OrdinalIgnoreCase ))
{
Push ( name , XmlNodeType . ProcessingInstruction , value );
return true ;
}
return false ;
}
private bool ParseText ( char ch , bool newtext )
{
bool ws = ! newtext || this . m_current . IsWhitespace ;
if ( newtext )
this . m_sb . Length = 0 ;
//this.sb.Append(ch);
//ch = this.current.ReadChar();
this . m_state = State . Text ;
while ( ch != Entity . EOF )
{
if ( ch == '<' )
{
ch = this . m_current . ReadChar ();
if ( ch == '/' || ch == '!' || ch == '?' || char . IsLetter ( ch ))
{
// Hit a tag, so return XmlNodeType.Text token
// and remember we partially started a new tag.
this . m_state = State . PartialTag ;
this . m_partial = ch ;
break ;
}
else
{
// not a tag, so just proceed.
this . m_sb . Append ( '<' );
this . m_sb . Append ( ch );
ws = false ;
ch = this . m_current . ReadChar ();
}
}
else if ( ch == '&' )
{
ExpandEntity ( this . m_sb , '<' );
ws = false ;
ch = this . m_current . Lastchar ;
}
else
{
if (! this . m_current . IsWhitespace )
ws = false ;
this . m_sb . Append ( ch );
ch = this . m_current . ReadChar ();
}
}
string value = this . m_sb . ToString ();
Push ( null , XmlNodeType . Text , value );
return ws ;
}
/// <summary>
/// Consumes and returns a literal block of text, expanding entities as it does so.
/// </summary>
/// <param name="sb">The string builder to use.</param>
/// <param name="quote">The delimiter for the literal.</param>
/// <returns>The consumed literal.</returns>
/// <remarks>
/// This version is slightly different from <see cref="Entity.ScanLiteral"/> in that
/// it also expands entities.
/// </remarks>
private string ScanLiteral ( StringBuilder sb , char quote )
{
sb . Length = 0 ;
char ch = this . m_current . ReadChar ();
while ( ch != Entity . EOF && ch != quote && ch != '>' )
{
if ( ch == '&' )
{
ExpandEntity ( sb , quote );
ch = this . m_current . Lastchar ;
}
else
{
sb . Append ( ch );
ch = this . m_current . ReadChar ();
}
}
if ( ch == quote )
{
this . m_current . ReadChar (); // consume end quote.
}
return sb . ToString ();
}
private bool ParseCData ()
{
// Like ParseText(), only it doesn't allow elements in the content.
// It allows comments and processing instructions and text only and
// text is not returned as text but CDATA (since it may contain angle brackets).
// And initial whitespace is ignored. It terminates when we hit the
// end tag for the current CDATA node (e.g. </style>).
bool ws = this . m_current . IsWhitespace ;
this . m_sb . Length = 0 ;
char ch = this . m_current . Lastchar ;
if ( this . m_partial != '\0' )
{
Pop (); // pop the CDATA
switch ( this . m_partial )
{
case '!' :
this . m_partial = ' ' ; // and pop the comment next time around
return ParseComment ();
case '?' :
this . m_partial = ' ' ; // and pop the PI next time around
return ParsePI ();
case '/' :
this . m_state = State . EndTag ;
return true ; // we are done!
case ' ' :
break ; // means we just needed to pop the Comment, PI or CDATA.
}
}
// if this.partial == '!' then parse the comment and return
// if this.partial == '?' then parse the processing instruction and return.
while ( ch != Entity . EOF )
{
if ( ch == '<' )
{
ch = this . m_current . ReadChar ();
if ( ch == '!' )
{
ch = this . m_current . ReadChar ();
if ( ch == '-' )
2013-12-17 22:50:36 +01:00
{
2013-12-24 10:50:46 +01:00
// return what CDATA we have accumulated so far
// then parse the comment and return to here.
if ( ws )
{
this . m_partial = ' ' ; // pop comment next time through
return ParseComment ();
}
else
{
// return what we've accumulated so far then come
// back in and parse the comment.
this . m_partial = '!' ;
break ;
}
2013-12-17 22:50:36 +01:00
#if FIX
} else if ( ch == '[' ){
// We are about to wrap this node as a CDATA block because of it's
// type in the DTD, but since we found a CDATA block in the input
// we have to parse it as a CDATA block, otherwise we will attempt
// to output nested CDATA blocks which of course is illegal.
if ( this . ParseConditionalBlock ()){
this . partial = ' ' ;
return true ;
}
#endif
}
else
{
2013-12-24 10:50:46 +01:00
// not a comment, so ignore it and continue on.
this . m_sb . Append ( '<' );
this . m_sb . Append ( '!' );
this . m_sb . Append ( ch );
ws = false ;
2013-12-17 22:50:36 +01:00
}
2013-12-24 10:50:46 +01:00
}
else if ( ch == '?' )
{
// processing instruction.
this . m_current . ReadChar (); // consume the '?' character.
if ( ws )
2013-12-17 22:50:36 +01:00
{
2013-12-24 10:50:46 +01:00
this . m_partial = ' ' ; // pop PI next time through
return ParsePI ();
2013-12-17 22:50:36 +01:00
}
2013-12-24 10:50:46 +01:00
else
{
this . m_partial = '?' ;
break ;
}
}
else if ( ch == '/' )
{
// see if this is the end tag for this CDATA node.
string temp = this . m_sb . ToString ();
if ( ParseEndTag () && string . Equals ( this . m_endTag , this . m_node . Name , StringComparison . OrdinalIgnoreCase ))
{
if ( ws || string . IsNullOrEmpty ( temp ))
{
// we are done!
return true ;
}
else
{
// return CDATA text then the end tag
this . m_partial = '/' ;
this . m_sb . Length = 0 ; // restore buffer!
this . m_sb . Append ( temp );
this . m_state = State . CData ;
break ;
}
}
else
{
// wrong end tag, so continue on.
this . m_sb . Length = 0 ; // restore buffer!
this . m_sb . Append ( temp );
this . m_sb . Append ( "</" + this . m_endTag + ">" );
ws = false ;
// NOTE (steveb): we have one character in the buffer that we need to process next
ch = this . m_current . Lastchar ;
continue ;
}
}
else
{
// must be just part of the CDATA block, so proceed.
this . m_sb . Append ( '<' );
this . m_sb . Append ( ch );
ws = false ;
}
2013-12-17 22:50:36 +01:00
}
2013-12-24 10:50:46 +01:00
else
{
if (! this . m_current . IsWhitespace && ws )
ws = false ;
this . m_sb . Append ( ch );
}
ch = this . m_current . ReadChar ();
}
// NOTE (steveb): check if we reached EOF, which means it's over
if ( ch == Entity . EOF )
{
this . m_state = State . Eof ;
return false ;
}
string value = this . m_sb . ToString ();
// NOTE (steveb): replace any nested CDATA sections endings
value = value . Replace ( "<![CDATA[" , string . Empty );
value = value . Replace ( "]]>" , string . Empty );
value = value . Replace ( "/**/" , string . Empty );
Push ( null , XmlNodeType . CDATA , value );
if ( this . m_partial == '\0' )
this . m_partial = ' ' ; // force it to pop this CDATA next time in.
return true ;
}
private void ExpandEntity ( StringBuilder sb , char terminator )
{
char ch = this . m_current . ReadChar ();
if ( ch == '#' )
{
string charent = this . m_current . ExpandCharEntity ();
sb . Append ( charent );
ch = this . m_current . Lastchar ;
}
else
{
this . m_name . Length = 0 ;
while ( ch != Entity . EOF &&
( char . IsLetter ( ch ) || ch == '_' || ch == '-' ) || (( this . m_name . Length > 0 ) && char . IsDigit ( ch )))
{
this . m_name . Append ( ch );
ch = this . m_current . ReadChar ();
}
string name = this . m_name . ToString ();
// TODO (steveb): don't lookup amp, gt, lt, quote
switch ( name )
{
case "amp" :
sb . Append ( "&" );
if ( ch != terminator && ch != '&' && ch != Entity . EOF )
ch = this . m_current . ReadChar ();
return ;
case "lt" :
sb . Append ( "<" );
if ( ch != terminator && ch != '&' && ch != Entity . EOF )
ch = this . m_current . ReadChar ();
return ;
case "gt" :
sb . Append ( ">" );
if ( ch != terminator && ch != '&' && ch != Entity . EOF )
ch = this . m_current . ReadChar ();
return ;
case "quot" :
sb . Append ( "\"" );
if ( ch != terminator && ch != '&' && ch != Entity . EOF )
ch = this . m_current . ReadChar ();
return ;
case "apos" :
sb . Append ( "'" );
if ( ch != terminator && ch != '&' && ch != Entity . EOF )
ch = this . m_current . ReadChar ();
return ;
}
if ( this . m_dtd != null && ! string . IsNullOrEmpty ( name ))
{
Entity e = ( Entity ) this . m_dtd . FindEntity ( name );
if ( e != null )
{
if ( e . IsInternal )
{
sb . Append ( e . Literal );
if ( ch != terminator && ch != '&' && ch != Entity . EOF )
ch = this . m_current . ReadChar ();
return ;
}
else
{
Entity ex = new Entity ( name , e . PublicId , e . Uri , this . m_current . Proxy );
e . Open ( this . m_current , new Uri ( e . Uri ));
this . m_current = ex ;
this . m_current . ReadChar ();
return ;
}
}
else
{
Log ( "Undefined entity '{0}'" , name );
}
}
// Entity is not defined, so just keep it in with the rest of the
// text.
sb . Append ( "&" );
sb . Append ( name );
if ( ch != terminator && ch != '&' && ch != Entity . EOF )
{
sb . Append ( ch );
ch = this . m_current . ReadChar ();
}
}
}
/// <summary>
/// Gets a value indicating whether the reader is positioned at the end of the stream.
/// </summary>
/// <value>true if the reader is positioned at the end of the stream; otherwise, false.</value>
public override bool EOF
{
get
{
return this . m_state == State . Eof ;
}
}
2013-12-17 22:50:36 +01:00
#if ! PORTABLE
public override void Close ()
{
(( IDisposable ) this ). Dispose ();
}
#endif
2013-12-24 10:50:46 +01:00
/// <summary>
/// Changes the <see cref="ReadState"/> to Closed.
/// </summary>
protected override void Dispose ( bool disposing )
{
base . Dispose ( disposing );
if (! disposing )
{
return ;
}
2013-12-17 22:50:36 +01:00
2013-12-24 10:50:46 +01:00
if ( this . m_current != null )
{
this . m_current . Close ();
this . m_current = null ;
}
2013-12-17 22:50:36 +01:00
2013-12-24 10:50:46 +01:00
if ( this . m_log != null )
{
this . m_log . Dispose ();
this . m_log = null ;
}
}
2013-12-17 22:50:36 +01:00
2013-12-24 10:50:46 +01:00
/// <summary>
/// Gets the state of the reader.
/// </summary>
/// <value>One of the ReadState values.</value>
public override ReadState ReadState
{
get
{
if ( this . m_state == State . Initial )
return ReadState . Initial ;
else if ( this . m_state == State . Eof )
return ReadState . EndOfFile ;
else
return ReadState . Interactive ;
}
}
2013-12-17 22:50:36 +01:00
2013-12-24 10:50:46 +01:00
/// <summary>
/// Reads the contents of an element or text node as a string.
/// </summary>
/// <returns>The contents of the element or an empty string.</returns>
2013-12-17 22:50:36 +01:00
#if PORTABLE
2013-12-24 10:50:46 +01:00
public override string ReadContentAsString ()
2013-12-17 22:50:36 +01:00
#else
public override string ReadString ()
#endif
2013-12-24 10:50:46 +01:00
{
if ( this . m_node . NodeType == XmlNodeType . Element )
{
this . m_sb . Length = 0 ;
while ( Read ())
2013-12-17 22:50:36 +01:00
{
2013-12-24 10:50:46 +01:00
switch ( this . NodeType )
{
case XmlNodeType . CDATA :
case XmlNodeType . SignificantWhitespace :
case XmlNodeType . Whitespace :
case XmlNodeType . Text :
this . m_sb . Append ( this . m_node . Value );
break ;
default :
return this . m_sb . ToString ();
}
2013-12-17 22:50:36 +01:00
}
2013-12-24 10:50:46 +01:00
return this . m_sb . ToString ();
}
2013-12-17 22:50:36 +01:00
2013-12-24 10:50:46 +01:00
return this . m_node . Value ;
}
2013-12-17 22:50:36 +01:00
2013-12-24 10:50:46 +01:00
/// <summary>
/// Reads all the content, including markup, as a string.
/// </summary>
/// <returns>
/// All the XML content, including markup, in the current node. If the current node has no children,
/// an empty string is returned. If the current node is neither an element nor attribute, an empty
/// string is returned.
/// </returns>
public override string ReadInnerXml ()
{
StringWriter sw = new StringWriter ( CultureInfo . InvariantCulture );
XmlWriter xw = XmlWriter . Create ( sw , new XmlWriterSettings { Indent = true , IndentChars = " " });
switch ( this . NodeType )
{
case XmlNodeType . Element :
Read ();
while (! this . EOF && this . NodeType != XmlNodeType . EndElement )
{
2013-12-17 22:50:36 +01:00
xw . WriteNode ( this , true );
2013-12-24 10:50:46 +01:00
}
Read (); // consume the end tag
break ;
case XmlNodeType . Attribute :
sw . Write ( this . Value );
break ;
default :
// return empty string according to XmlReader spec.
break ;
}
2013-12-17 22:50:36 +01:00
#if PORTABLE
2013-12-24 10:50:46 +01:00
xw . Dispose ();
2013-12-17 22:50:36 +01:00
#else
xw . Close ();
#endif
2013-12-24 10:50:46 +01:00
return sw . ToString ();
}
/// <summary>
/// Reads the content, including markup, representing this node and all its children.
/// </summary>
/// <returns>
/// If the reader is positioned on an element or an attribute node, this method returns all the XML content, including markup, of the current node and all its children; otherwise, it returns an empty string.
/// </returns>
public override string ReadOuterXml ()
{
StringWriter sw = new StringWriter ( CultureInfo . InvariantCulture );
XmlWriter xw = XmlWriter . Create ( sw , new XmlWriterSettings { Indent = true , IndentChars = " " });
xw . WriteNode ( this , true );
#if PORTABLE
xw . Dispose ();
#else
xw . Close ();
#endif
return sw . ToString ();
}
/// <summary>
/// Gets the XmlNameTable associated with this implementation.
/// </summary>
/// <value>The XmlNameTable enabling you to get the atomized version of a string within the node.</value>
public override XmlNameTable NameTable
{
get
{
return null ;
}
}
/// <summary>
/// Resolves a namespace prefix in the current element's scope.
/// </summary>
/// <param name="prefix">The prefix whose namespace URI you want to resolve. To match the default namespace, pass an empty string.</param>
/// <returns>The namespace URI to which the prefix maps or a null reference (Nothing in Visual Basic) if no matching prefix is found.</returns>
public override string LookupNamespace ( string prefix )
{
return null ; // there are no namespaces in SGML.
}
/// <summary>
/// Resolves the entity reference for EntityReference nodes.
/// </summary>
/// <exception cref="InvalidOperationException">SgmlReader does not resolve or return entities.</exception>
public override void ResolveEntity ()
{
// We never return any entity reference nodes, so this should never be called.
throw new InvalidOperationException ( "Not on an entity reference." );
}
/// <summary>
/// Parses the attribute value into one or more Text, EntityReference, or EndEntity nodes.
/// </summary>
/// <returns>
/// true if there are nodes to return. false if the reader is not positioned on an attribute node when the initial call is made or if all the
/// attribute values have been read. An empty attribute, such as, misc="", returns true with a single node with a value of string.Empty.
/// </returns>
public override bool ReadAttributeValue ()
{
if ( this . m_state == State . Attr )
{
this . m_state = State . AttrValue ;
return true ;
}
else if ( this . m_state == State . AttrValue )
{
return false ;
}
else
throw new InvalidOperationException ( "Not on an attribute." );
}
private void Validate ( Node node )
{
if ( this . m_dtd != null )
{
ElementDecl e = this . m_dtd . FindElement ( node . Name );
if ( e != null )
{
node . DtdType = e ;
if ( e . ContentModel . DeclaredContent == DeclaredContent . EMPTY )
node . IsEmpty = true ;
}
}
}
private static void ValidateAttribute ( Node node , Attribute a )
{
ElementDecl e = node . DtdType ;
if ( e != null )
{
AttDef ad = e . FindAttribute ( a . Name );
if ( ad != null )
{
a . DtdType = ad ;
}
}
}
private static bool ValidAttributeName ( string name )
{
try
{
XmlConvert . VerifyNMTOKEN ( name );
int index = name . IndexOf ( ':' );
if ( index >= 0 )
{
XmlConvert . VerifyNCName ( name . Substring ( index + 1 ));
2013-12-17 22:50:36 +01:00
}
2013-12-24 10:50:46 +01:00
return true ;
}
catch ( XmlException )
{
return false ;
}
catch ( ArgumentNullException )
{
// (steveb) this is probably a bug in XmlConvert.VerifyNCName when passing in an empty string
return false ;
}
}
private void ValidateContent ( Node node )
{
if ( node . NodeType == XmlNodeType . Element )
{
if (! VerifyName ( node . Name ))
2013-12-17 22:50:36 +01:00
{
2013-12-24 10:50:46 +01:00
Pop ();
Push ( null , XmlNodeType . Text , "<" + node . Name + ">" );
return ;
}
}
if ( this . m_dtd != null )
{
// See if this element is allowed inside the current element.
// If it isn't, then auto-close elements until we find one
// that it is allowed to be in.
string name = node . Name . ToUpperInvariant (); // DTD is in upper case
int i = 0 ;
int top = this . m_stack . Count - 2 ;
if ( node . DtdType != null )
{
// it is a known element, let's see if it's allowed in the
// current context.
for ( i = top ; i > 0 ; i --)
{
Node n = ( Node ) this . m_stack [ i ];
if ( n . IsEmpty )
continue ; // we'll have to pop this one
ElementDecl f = n . DtdType ;
if ( f != null )
2013-12-17 22:50:36 +01:00
{
2013-12-24 10:50:46 +01:00
if (( i == 2 ) && string . Equals ( f . Name , "BODY" , StringComparison . OrdinalIgnoreCase )) // NOTE (steveb): never close the BODY tag too early
break ;
else if ( string . Equals ( f . Name , this . m_dtd . Name , StringComparison . OrdinalIgnoreCase ))
break ; // can't pop the root element.
else if ( f . CanContain ( name , this . m_dtd ))
{
break ;
}
else if (! f . EndTagOptional )
{
// If the end tag is not optional then we can't
// auto-close it. We'll just have to live with the
// junk we've found and move on.
break ;
}
2013-12-17 22:50:36 +01:00
}
else
{
2013-12-24 10:50:46 +01:00
// Since we don't understand this tag anyway,
// we might as well allow this content!
break ;
2013-12-17 22:50:36 +01:00
}
2013-12-24 10:50:46 +01:00
}
2013-12-17 22:50:36 +01:00
}
2013-12-24 10:50:46 +01:00
if ( i == 0 )
2013-12-17 22:50:36 +01:00
{
2013-12-24 10:50:46 +01:00
// Tag was not found or is not allowed anywhere, ignore it and
// continue on.
return ;
2013-12-17 22:50:36 +01:00
}
2013-12-24 10:50:46 +01:00
else if ( i < top )
2013-12-17 22:50:36 +01:00
{
2013-12-24 10:50:46 +01:00
Node n = ( Node ) this . m_stack [ top ];
if ( i == top - 1 && string . Equals ( name , n . Name , StringComparison . OrdinalIgnoreCase ))
{
// e.g. p not allowed inside p, not an interesting error.
}
else
{
2013-12-17 22:50:36 +01:00
#if DEBUG
2013-12-24 10:50:46 +01:00
string closing = "" ;
for ( int k = top ; k >= i + 1 ; k --)
{
if ( closing != "" ) closing += "," ;
Node n2 = ( Node ) this . m_stack [ k ];
closing += "<" + n2 . Name + ">" ;
2013-12-17 22:50:36 +01:00
}
2013-12-24 10:50:46 +01:00
Log ( "Element '{0}' not allowed inside '{1}', closing {2}." , name , n . Name , closing );
#endif
}
this . m_state = State . AutoClose ;
this . m_newnode = node ;
Pop (); // save this new node until we pop the others
this . m_poptodepth = i + 1 ;
2013-12-17 22:50:36 +01:00
}
2013-12-24 10:50:46 +01:00
}
2013-12-17 22:50:36 +01:00
}
2013-12-24 10:50:46 +01:00
}
2013-12-17 22:50:36 +01:00
}