context: using System;
  using System.IO;
  using System.Collections;
  using System.Text;
  using System.Net;
  using System.Xml;
  using System.Globalization;
  
  namespace BaiRong.Core.Text.Sgml
  {
      public enum LiteralType
      {
          CDATA, SDATA, PI
      };
  
      public class Entity
      {
          public const Char EOF = (char)65535;
          public string Proxy;
          public string Name;
          public bool Internal;
          public string PublicId;
          public string Uri;
          public string Literal;
          public LiteralType LiteralType;
          public Entity Parent;
          public bool Html;
          public int Line;
          public char Lastchar;
          public bool IsWhitespace;
  
          Encoding encoding;
          Uri resolvedUri;
          TextReader stm;
          bool weOwnTheStream;
          int lineStart;
          int absolutePos;
  
          public Entity(string name, string pubid, string uri, string proxy)
          {
              Name = name;
              PublicId = pubid;
              Uri = uri;
              Proxy = proxy;
              Html = (name != null && StringUtilities.EqualsIgnoreCase(name, "html"));
          }
  
          public Entity(string name, string literal)
          {
              Name = name;
              Literal = literal;
              Internal = true;
          }
  
          public Entity(string name, Uri baseUri, TextReader stm, string proxy)
          {
              Name = name;
              Internal = true;
              this.stm = stm;
              resolvedUri = baseUri;
              Proxy = proxy;
              Html = (string.Compare(name, "html", true, CultureInfo.InvariantCulture) == 0);
          }
  
          public Uri ResolvedUri
          {
              get
              {
                  if (resolvedUri != null) return resolvedUri;
                  else if (Parent != null) return Parent.ResolvedUri;
                  return null;
              }
          }
  
          public int LinePosition => absolutePos - lineStart + 1;
  
          public char ReadChar()
          {
              var ch = (char)stm.Read();
              if (ch == 0)
              {
                  // convert nulls to whitespace, since they are not valid in XML anyway.
                  ch = ' ';
              }
              absolutePos++;
              if (ch == 0xa)
              {
                  IsWhitespace = true;
                  lineStart = absolutePos + 1;
                  Line++;
              }
              else if (ch == ' ' || ch == '\t')
              {
                  IsWhitespace = true;
                  if (Lastchar == 0xd)
                  {
                      lineStart = absolutePos;
                      Line++;
                  }
              }
              else if (ch == 0xd)
              {
                  IsWhitespace = true;
              }
              else
              {
                  IsWhitespace = false;
                  if (Lastchar == 0xd)
                  {
                      Line++;
                      lineStart = absolutePos;
                  }
              }
              Lastchar = ch;
              return ch;
          }
  
          public void Open(Entity parent, Uri baseUri)
          {
              Parent = parent;
              if (parent != null) Html = parent.Html;
              Line = 1;
              if (Internal)
              {
                  if (Literal != null)
                      stm = new StringReader(Literal);
              }
              else if (Uri == null)
              {
                  Error("Unresolvable entity '{0}'", Name);
              }
              else
              {
                  if (baseUri != null)
                  {
                      resolvedUri = new Uri(baseUri, Uri);
                  }
                  else
                  {
                      resolvedUri = new Uri(Uri);
                  }
  
                  Stream stream = null;
                  var e = Encoding.Default;
                  switch (resolvedUri.Scheme)
                  {
                      case "file":
                          {
                              var path = resolvedUri.LocalPath;
                              stream = new FileStream(path, FileMode.Open, FileAccess.Read);
                          }
                          break;
                      default:
                          //Console.WriteLine("Fetching:" + ResolvedUri.AbsoluteUri);
                          var wr = (HttpWebRequest)WebRequest.Create(ResolvedUri);
                          wr.UserAgent = "Mozilla/4.0 (compatible;);";
                          wr.Timeout = 10000; // in case this is running in an ASPX page.
                          if (Proxy != null) wr.Proxy = new WebProxy(Proxy);
                          wr.PreAuthenticate = false;
                          // Pass the credentials of the process. 
                          wr.Credentials = CredentialCache.DefaultCredentials;
  
                          var resp = wr.GetResponse();
                          var actual = resp.ResponseUri;
                          if (actual.AbsoluteUri != resolvedUri.AbsoluteUri)
                          {
                              resolvedUri = actual;
                          }
                          var contentType = resp.ContentType.ToLower();
                          var mimeType = contentType;
                          var i = contentType.IndexOf(';');
                          if (i >= 0)
                          {
                              mimeType = contentType.Substring(0, i);
                          }
                          if (StringUtilities.EqualsIgnoreCase(mimeType, "text/html"))
                          {
                              Html = true;
                          }
  
                          i = contentType.IndexOf("charset");
                          e = Encoding.Default;
                          if (i >= 0)
                          {
                              var j = contentType.IndexOf("=", i);
                              var k = contentType.IndexOf(";", j);
                              if (k < 0) k = contentType.Length;
                              if (j > 0)
                              {
                                  j++;
                                  var charset = contentType.Substring(j, k - j).Trim();
                                  try
                                  {
                                      e = Encoding.GetEncoding(charset);
                                  }
                                  catch (Exception)
                                  {
                                  }
                              }
                          }
                          stream = resp.GetResponseStream();
                          break;
  
                  }
                  weOwnTheStream = true;
                  var html = new HtmlStream(stream, e);
                  encoding = html.Encoding;
                  stm = html;
              }
          }
  
          public Encoding GetEncoding()
          {
              return encoding;
          }
  
          public void Close()
          {
              if (weOwnTheStream)
                  stm.Close();
          }
  
          public char SkipWhitespace()
          {
              var ch = Lastchar;
              while (ch != EOF && (ch == ' ' || ch == '\r' || ch == '\n' || ch == '\t'))
              {
                  ch = ReadChar();
              }
              return ch;
          }
  
          public string ScanToken(StringBuilder sb, string term, bool nmtoken)
          {
              sb.Length = 0;
              var ch = Lastchar;
              if (nmtoken && ch != '_' && !Char.IsLetter(ch))
              {
                  throw new Exception(
                      $"Invalid name start character '{ch}'");
              }
              while (ch != EOF && term.IndexOf(ch) < 0)
              {
                  if (!nmtoken || ch == '_' || ch == '.' || ch == '-' || ch == ':' || Char.IsLetterOrDigit(ch))
                  {
                      sb.Append(ch);
                  }
                  else
                  {
                      throw new Exception(
                          $"Invalid name character '{ch}'");
                  }
                  ch = ReadChar();
              }
              return sb.ToString();
          }
  
          public string ScanLiteral(StringBuilder sb, char quote)
          {
              sb.Length = 0;
              var ch = ReadChar();
              while (ch != EOF && ch != quote)
              {
                  if (ch == '&')
                  {
                      ch = ReadChar();
                      if (ch == '#')
                      {
                          var charent = ExpandCharEntity();
                          sb.Append(charent);
                          ch = Lastchar;
                      }
                      else
                      {
                          sb.Append('&');
                          sb.Append(ch);
                          ch = ReadChar();
                      }
                  }
                  else
                  {
                      sb.Append(ch);
                      ch = ReadChar();
                  }
              }
              ReadChar(); // consume end quote.           
              return sb.ToString();
          }
  
          public string ScanToEnd(StringBuilder sb, string type, string terminators)
          {
              if (sb != null) sb.Length = 0;
              var start = Line;
              // This method scans over a chunk of text looking for the
              // termination sequence specified by the 'terminators' parameter.
              var ch = ReadChar();
              var state = 0;
              var next = terminators[state];
              while (ch != EOF)
              {
                  if (ch == next)
                  {
                      state++;
                      if (state >= terminators.Length)
                      {
                          // found it!
                          break;
                      }
                      next = terminators[state];
                  }
                  else if (state > 0)
                  {
                      // char didn't match, so go back and see how much does still match.
                      var i = state - 1;
                      var newstate = 0;
                      while (i >= 0 && newstate == 0)
                      {
                          if (terminators[i] == ch)
                          {
                              // character is part of the terminators pattern, ok, so see if we can
                              // match all the way back to the beginning of the pattern.
                              var j = 1;
                              while (i - j >= 0)
                              {
                                  if (terminators[i - j] != terminators[state - j])
                                      break;
                                  j++;
                              }
                              if (j > i)
                              {
                                  newstate = i + 1;
                              }
                          }
                          else
                          {
                              i--;
                          }
                      }
                      if (sb != null)
                      {
                          i = (i < 0) ? 1 : 0;
                          for (var k = 0; k <= state - newstate - i; k++)
                          {
                              sb.Append(terminators[k]);
                          }
                          if (i > 0) // see if we've matched this char or not
                              sb.Append(ch); // if not then append it to buffer.
                      }
                      state = newstate;
                      next = terminators[newstate];
                  }
                  else
                  {
                      if (sb != null) sb.Append(ch);
                  }
                  ch = ReadChar();
              }
              if (ch == 0) Error(type + " starting on line {0} was never closed", start);
              ReadChar(); // consume last char in termination sequence.
              if (sb != null) return sb.ToString();
              return "";
          }
  
          public string ExpandCharEntity()
          {
              var ch = ReadChar();
              var v = 0;
              if (ch == 'x')
              {
                  ch = ReadChar();
                  for (; ch != EOF && ch != ';'; ch = ReadChar())
                  {
                      var p = 0;
                      if (ch >= '0' && ch <= '9')
                      {
                          p = (int)(ch - '0');
                      }
                      else if (ch >= 'a' && ch <= 'f')
                      {
                          p = (int)(ch - 'a') + 10;
                      }
                      else if (ch >= 'A' && ch <= 'F')
                      {
                          p = (int)(ch - 'A') + 10;
                      }
                      else
                      {
                          break;//we must be done!
                          //Error("Hex digit out of range '{0}'", (int)ch);
                      }
                      v = (v * 16) + p;
                  }
              }
              else
              {
                  for (; ch != EOF && ch != ';'; ch = ReadChar())
                  {
                      if (ch >= '0' && ch <= '9')
                      {
                          v = (v * 10) + (int)(ch - '0');
                      }
                      else
                      {
                          break; // we must be done!
                          //Error("Decimal digit out of range '{0}'", (int)ch);
                      }
                  }
              }
              if (ch == 0)
              {
                  Error("Premature {0} parsing entity reference", ch);
              }
              else if (ch == ';')
              {
                  ReadChar();
              }
              // HACK ALERT: IE and Netscape map the unicode characters 
              if (Html && v >= 0x80 & v <= 0x9F)
              {
                  // This range of control characters is mapped to Windows-1252!
                  var size = CtrlMap.Length;
                  var i = v - 0x80;
                  var unicode = CtrlMap[i];
                  return Convert.ToChar(unicode).ToString();
              }
              return Convert.ToChar(v).ToString();
          }
  
          static int[] CtrlMap = new int[] {
                                               // This is the windows-1252 mapping of the code points 0x80 through 0x9f.
                                               8364, 129, 8218, 402, 8222, 8230, 8224, 8225, 710, 8240, 352, 8249, 338, 141,
                                               381, 143, 144, 8216, 8217, 8220, 8221, 8226, 8211, 8212, 732, 8482, 353, 8250, 
                                               339, 157, 382, 376
                                           };
  
          public void Error(string msg)
          {
              throw new Exception(msg);
          }
  
          public void Error(string msg, char ch)
          {
              var str = (ch == EOF) ? "EOF" : Char.ToString(ch);
              throw new Exception(String.Format(msg, str));
          }
  
          public void Error(string msg, int x)
          {
              throw new Exception(String.Format(msg, x));
          }
  
          public void Error(string msg, string arg)
          {
              throw new Exception(String.Format(msg, arg));
          }
  
          public string Context()
          {
              var p = this;
              var sb = new StringBuilder();
              while (p != null)
              {
                  string msg;
                  if (p.Internal)
                  {
                      msg = $"\nReferenced on line {p.Line}, position {p.LinePosition} of internal entity '{p.Name}'";
                  }
                  else
                  {
                      msg =
                          $"\nReferenced on line {p.Line}, position {p.LinePosition} of '{p.Name}' entity at [{p.ResolvedUri.AbsolutePath}]";
                  }
                  sb.Append(msg);
                  p = p.Parent;
              }
              return sb.ToString();
          }
  
          public static bool IsLiteralType(string token)
          {
              return (token == "CDATA" || token == "SDATA" || token == "PI");
          }
  
          public void SetLiteralType(string token)
          {
              switch (token)
              {
                  case "CDATA":
                      LiteralType = LiteralType.CDATA;
                      break;
                  case "SDATA":
                      LiteralType = LiteralType.SDATA;
                      break;
                  case "PI":
                      LiteralType = LiteralType.PI;
                      break;
              }
          }
      }
  
      // This class decodes an HTML/XML stream correctly.
      internal class HtmlStream : TextReader
      {
          Stream stm;
          byte[] rawBuffer;
          int rawPos;
          int rawUsed;
          Encoding encoding;
          Decoder decoder;
          char[] buffer;
          int used;
          int pos;
          private const int BUFSIZE = 16384;
          private const int EOF = -1;
  
          public HtmlStream(Stream stm, Encoding defaultEncoding)
          {
              if (defaultEncoding == null) defaultEncoding = Encoding.UTF8; // default is UTF8
              if (!stm.CanSeek)
              {
                  // Need to be able to seek to sniff correctly.
                  stm = CopyToMemoryStream(stm);
              }
              this.stm = stm;
              rawBuffer = new Byte[BUFSIZE];
              rawUsed = stm.Read(rawBuffer, 0, 4); // maximum byte order mark
              buffer = new char[BUFSIZE];
  
              // Check byte order marks
              decoder = AutoDetectEncoding(rawBuffer, ref rawPos, rawUsed);
              var bom = rawPos;
              if (decoder == null)
              {
                  decoder = defaultEncoding.GetDecoder();
                  rawUsed += stm.Read(rawBuffer, 4, BUFSIZE - 4);
                  DecodeBlock();
                  // Now sniff to see if there is an XML declaration or HTML <META> tag.
                  var sd = SniffEncoding();
                  if (sd != null)
                  {
                      decoder = sd;
                  }
              }
  
              // Reset to get ready for Read()
              this.stm.Seek(0, SeekOrigin.Begin);
              pos = used = 0;
              // skip bom
              if (bom > 0)
              {
                  stm.Read(rawBuffer, 0, bom);
              }
              rawPos = rawUsed = 0;
  
          }
  
          public Encoding Encoding => encoding;
  
          Stream CopyToMemoryStream(Stream s)
          {
              var size = 100000; // large heap is more efficient
              var buffer = new byte[size];
              int len;
              var r = new MemoryStream();
  
gt:             while ((len = s.Read(buffer, 0, size)) > 0)