PageRenderTime 56ms CodeModel.GetById 15ms RepoModel.GetById 1ms app.codeStats 0ms

/NBoilerpipePortable/Util/SGML/SgmlParser.cs

https://github.com/hippiehunter/Baconography
C# | 3222 lines | 2856 code | 121 blank | 245 comment | 356 complexity | 8cdcc85c0288b0626b85742f57e3d3e3 MD5 | raw file

Large files files are truncated, but you can click here to view the full file

  1. /*
  2. *
  3. * Copyright (c) 2007-2013 MindTouch. All rights reserved.
  4. * www.mindtouch.com oss@mindtouch.com
  5. *
  6. * For community documentation and downloads visit wiki.developer.mindtouch.com;
  7. * please review the licensing section.
  8. *
  9. * Licensed under the Apache License, Version 2.0 (the "License");
  10. * you may not use this file except in compliance with the License.
  11. * You may obtain a copy of the License at
  12. *
  13. * http://www.apache.org/licenses/LICENSE-2.0
  14. *
  15. * Unless required by applicable law or agreed to in writing, software
  16. * distributed under the License is distributed on an "AS IS" BASIS,
  17. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  18. * See the License for the specific language governing permissions and
  19. * limitations under the License.
  20. *
  21. */
  22. using System;
  23. using System.Collections;
  24. using System.Collections.Generic;
  25. using System.Diagnostics.CodeAnalysis;
  26. using System.Globalization;
  27. using System.IO;
  28. using System.Net;
  29. using System.Runtime.Serialization;
  30. using System.Text;
  31. using System.Xml;
  32. namespace Sgml {
  33. /// <summary>
  34. /// Thrown if any errors occur while parsing the source.
  35. /// </summary>
  36. public class SgmlParseException : Exception
  37. {
  38. private string m_entityContext;
  39. /// <summary>
  40. /// Instantiates a new instance of SgmlParseException with no specific error information.
  41. /// </summary>
  42. public SgmlParseException()
  43. {
  44. }
  45. /// <summary>
  46. /// Instantiates a new instance of SgmlParseException with an error message describing the problem.
  47. /// </summary>
  48. /// <param name="message">A message describing the error that occurred</param>
  49. public SgmlParseException(string message)
  50. : base(message)
  51. {
  52. }
  53. /// <summary>
  54. /// Instantiates a new instance of SgmlParseException with an error message describing the problem.
  55. /// </summary>
  56. /// <param name="message">A message describing the error that occurred</param>
  57. /// <param name="e">The entity on which the error occurred.</param>
  58. public SgmlParseException(string message, Entity e)
  59. : base(message)
  60. {
  61. if (e != null)
  62. m_entityContext = e.Context();
  63. }
  64. /// <summary>
  65. /// Instantiates a new instance of SgmlParseException with an error message describing the problem.
  66. /// </summary>
  67. /// <param name="message">A message describing the error that occurred</param>
  68. /// <param name="innerException">The original exception that caused the problem.</param>
  69. public SgmlParseException(string message, Exception innerException)
  70. : base(message, innerException)
  71. {
  72. }
  73. /// <summary>
  74. /// Contextual information detailing the entity on which the error occurred.
  75. /// </summary>
  76. public string EntityContext
  77. {
  78. get
  79. {
  80. return m_entityContext;
  81. }
  82. }
  83. }
  84. /// <summary>
  85. /// The different types of literal text returned by the SgmlParser.
  86. /// </summary>
  87. public enum LiteralType
  88. {
  89. /// <summary>
  90. /// CDATA text literals.
  91. /// </summary>
  92. [SuppressMessage("Microsoft.Naming", "CA1705", Justification = "This capitalisation is appropriate since the value it represents has all upper-case capitalisation.")]
  93. CDATA,
  94. /// <summary>
  95. /// SDATA entities.
  96. /// </summary>
  97. [SuppressMessage("Microsoft.Naming", "CA1705", Justification = "This capitalisation is appropriate since the value it represents has all upper-case capitalisation.")]
  98. SDATA,
  99. /// <summary>
  100. /// The contents of a Processing Instruction.
  101. /// </summary>
  102. [SuppressMessage("Microsoft.Naming", "CA1705", Justification = "This capitalisation is appropriate since the value it represents has all upper-case capitalisation.")]
  103. PI
  104. };
  105. /// <summary>
  106. /// An Entity declared in a DTD.
  107. /// </summary>
  108. public class Entity : IDisposable
  109. {
  110. /// <summary>
  111. /// The character indicating End Of File.
  112. /// </summary>
  113. [SuppressMessage("Microsoft.Naming", "CA1705", Justification = "The capitalisation is correct since EOF is an acronym.")]
  114. public const char EOF = (char)65535;
  115. private string m_proxy;
  116. private string m_name;
  117. private bool m_isInternal;
  118. private string m_publicId;
  119. private string m_uri;
  120. private string m_literal;
  121. private LiteralType m_literalType;
  122. private Entity m_parent;
  123. private bool m_isHtml;
  124. private int m_line;
  125. private char m_lastchar;
  126. private bool m_isWhitespace;
  127. private Encoding m_encoding;
  128. private Uri m_resolvedUri;
  129. private TextReader m_stm;
  130. private bool m_weOwnTheStream;
  131. private int m_lineStart;
  132. private int m_absolutePos;
  133. /// <summary>
  134. /// Initialises a new instance of an Entity declared in a DTD.
  135. /// </summary>
  136. /// <param name="name">The name of the entity.</param>
  137. /// <param name="pubid">The public id of the entity.</param>
  138. /// <param name="uri">The uri of the entity.</param>
  139. /// <param name="proxy">The proxy server to use when retrieving any web content.</param>
  140. public Entity(string name, string pubid, string uri, string proxy)
  141. {
  142. m_name = name;
  143. m_publicId = pubid;
  144. m_uri = uri;
  145. m_proxy = proxy;
  146. m_isHtml = (name != null && StringUtilities.EqualsIgnoreCase(name, "html"));
  147. }
  148. /// <summary>
  149. /// Initialises a new instance of an Entity declared in a DTD.
  150. /// </summary>
  151. /// <param name="name">The name of the entity.</param>
  152. /// <param name="literal">The literal value of the entity.</param>
  153. public Entity(string name, string literal)
  154. {
  155. m_name = name;
  156. m_literal = literal;
  157. m_isInternal = true;
  158. }
  159. /// <summary>
  160. /// Initialises a new instance of an Entity declared in a DTD.
  161. /// </summary>
  162. /// <param name="name">The name of the entity.</param>
  163. /// <param name="baseUri">The baseUri for the entity to read from the TextReader.</param>
  164. /// <param name="stm">The TextReader to read the entity from.</param>
  165. /// <param name="proxy">The proxy server to use when retrieving any web content.</param>
  166. public Entity(string name, Uri baseUri, TextReader stm, string proxy)
  167. {
  168. m_name = name;
  169. m_isInternal = true;
  170. m_stm = stm;
  171. m_resolvedUri = baseUri;
  172. m_proxy = proxy;
  173. m_isHtml = string.Equals(name, "html", StringComparison.OrdinalIgnoreCase);
  174. }
  175. /// <summary>
  176. /// The name of the entity.
  177. /// </summary>
  178. public string Name
  179. {
  180. get
  181. {
  182. return m_name;
  183. }
  184. }
  185. /// <summary>
  186. /// True if the entity is the html element entity.
  187. /// </summary>
  188. public bool IsHtml
  189. {
  190. get
  191. {
  192. return m_isHtml;
  193. }
  194. set
  195. {
  196. m_isHtml = value;
  197. }
  198. }
  199. /// <summary>
  200. /// The public identifier of this entity.
  201. /// </summary>
  202. public string PublicId
  203. {
  204. get
  205. {
  206. return m_publicId;
  207. }
  208. }
  209. /// <summary>
  210. /// The Uri that is the source for this entity.
  211. /// </summary>
  212. public string Uri
  213. {
  214. get
  215. {
  216. return m_uri;
  217. }
  218. }
  219. /// <summary>
  220. /// The resolved location of the DTD this entity is from.
  221. /// </summary>
  222. public Uri ResolvedUri
  223. {
  224. get
  225. {
  226. if (this.m_resolvedUri != null)
  227. return this.m_resolvedUri;
  228. else if (m_parent != null)
  229. return m_parent.ResolvedUri;
  230. else
  231. return null;
  232. }
  233. }
  234. /// <summary>
  235. /// Gets the parent Entity of this Entity.
  236. /// </summary>
  237. public Entity Parent
  238. {
  239. get
  240. {
  241. return m_parent;
  242. }
  243. }
  244. /// <summary>
  245. /// The last character read from the input stream for this entity.
  246. /// </summary>
  247. public char Lastchar
  248. {
  249. get
  250. {
  251. return m_lastchar;
  252. }
  253. }
  254. /// <summary>
  255. /// The line on which this entity was defined.
  256. /// </summary>
  257. public int Line
  258. {
  259. get
  260. {
  261. return m_line;
  262. }
  263. }
  264. /// <summary>
  265. /// The index into the line where this entity is defined.
  266. /// </summary>
  267. public int LinePosition
  268. {
  269. get
  270. {
  271. return this.m_absolutePos - this.m_lineStart + 1;
  272. }
  273. }
  274. /// <summary>
  275. /// Whether this entity is an internal entity or not.
  276. /// </summary>
  277. /// <value>true if this entity is internal, otherwise false.</value>
  278. public bool IsInternal
  279. {
  280. get
  281. {
  282. return m_isInternal;
  283. }
  284. }
  285. /// <summary>
  286. /// The literal value of this entity.
  287. /// </summary>
  288. public string Literal
  289. {
  290. get
  291. {
  292. return m_literal;
  293. }
  294. }
  295. /// <summary>
  296. /// The <see cref="LiteralType"/> of this entity.
  297. /// </summary>
  298. public LiteralType LiteralType
  299. {
  300. get
  301. {
  302. return m_literalType;
  303. }
  304. }
  305. /// <summary>
  306. /// Whether the last char read for this entity is a whitespace character.
  307. /// </summary>
  308. public bool IsWhitespace
  309. {
  310. get
  311. {
  312. return m_isWhitespace;
  313. }
  314. }
  315. /// <summary>
  316. /// The proxy server to use when making web requests to resolve entities.
  317. /// </summary>
  318. public string Proxy
  319. {
  320. get
  321. {
  322. return m_proxy;
  323. }
  324. }
  325. /// <summary>
  326. /// Reads the next character from the DTD stream.
  327. /// </summary>
  328. /// <returns>The next character from the DTD stream.</returns>
  329. public char ReadChar()
  330. {
  331. char ch = (char)this.m_stm.Read();
  332. if (ch == 0)
  333. {
  334. // convert nulls to whitespace, since they are not valid in XML anyway.
  335. ch = ' ';
  336. }
  337. this.m_absolutePos++;
  338. if (ch == 0xa)
  339. {
  340. m_isWhitespace = true;
  341. this.m_lineStart = this.m_absolutePos + 1;
  342. this.m_line++;
  343. }
  344. else if (ch == ' ' || ch == '\t')
  345. {
  346. m_isWhitespace = true;
  347. if (m_lastchar == 0xd)
  348. {
  349. this.m_lineStart = this.m_absolutePos;
  350. m_line++;
  351. }
  352. }
  353. else if (ch == 0xd)
  354. {
  355. m_isWhitespace = true;
  356. }
  357. else
  358. {
  359. m_isWhitespace = false;
  360. if (m_lastchar == 0xd)
  361. {
  362. m_line++;
  363. this.m_lineStart = this.m_absolutePos;
  364. }
  365. }
  366. m_lastchar = ch;
  367. return ch;
  368. }
  369. /// <summary>
  370. /// Begins processing an entity.
  371. /// </summary>
  372. /// <param name="parent">The parent of this entity.</param>
  373. /// <param name="baseUri">The base Uri for processing this entity within.</param>
  374. public void Open(Entity parent, Uri baseUri)
  375. {
  376. this.m_parent = parent;
  377. if (parent != null)
  378. this.m_isHtml = parent.IsHtml;
  379. this.m_line = 1;
  380. if (m_isInternal)
  381. {
  382. if (this.m_literal != null)
  383. this.m_stm = new StringReader(this.m_literal);
  384. }
  385. else if (this.m_uri == null)
  386. {
  387. this.Error("Unresolvable entity '{0}'", this.m_name);
  388. }
  389. else
  390. {
  391. if (baseUri != null)
  392. {
  393. this.m_resolvedUri = new Uri(baseUri, this.m_uri);
  394. }
  395. else
  396. {
  397. this.m_resolvedUri = new Uri(this.m_uri);
  398. }
  399. Stream stream = null;
  400. Encoding e = Encoding.UTF8;
  401. //this stuff should be happening but i dont quite know whats going on
  402. throw new NotImplementedException();
  403. this.m_weOwnTheStream = true;
  404. HtmlStream html = new HtmlStream(stream, e);
  405. this.m_encoding = html.Encoding;
  406. this.m_stm = html;
  407. }
  408. }
  409. /// <summary>
  410. /// Gets the character encoding for this entity.
  411. /// </summary>
  412. public Encoding Encoding
  413. {
  414. get
  415. {
  416. return this.m_encoding;
  417. }
  418. }
  419. /// <summary>
  420. /// Closes the reader from which the entity is being read.
  421. /// </summary>
  422. public void Close()
  423. {
  424. if (this.m_weOwnTheStream)
  425. this.m_stm.Dispose();
  426. }
  427. /// <summary>
  428. /// Returns the next character after any whitespace.
  429. /// </summary>
  430. /// <returns>The next character that is not whitespace.</returns>
  431. public char SkipWhitespace()
  432. {
  433. char ch = m_lastchar;
  434. while (ch != Entity.EOF && (ch == ' ' || ch == '\r' || ch == '\n' || ch == '\t'))
  435. {
  436. ch = ReadChar();
  437. }
  438. return ch;
  439. }
  440. /// <summary>
  441. /// Scans a token from the input stream and returns the result.
  442. /// </summary>
  443. /// <param name="sb">The <see cref="StringBuilder"/> to use to process the token.</param>
  444. /// <param name="term">A set of characters to look for as terminators for the token.</param>
  445. /// <param name="nmtoken">true if the token should be a NMToken, otherwise false.</param>
  446. /// <returns>The scanned token.</returns>
  447. public string ScanToken(StringBuilder sb, string term, bool nmtoken)
  448. {
  449. if (sb == null)
  450. throw new ArgumentNullException("sb");
  451. if (term == null)
  452. throw new ArgumentNullException("term");
  453. sb.Length = 0;
  454. char ch = m_lastchar;
  455. if (nmtoken && ch != '_' && !char.IsLetter(ch))
  456. {
  457. throw new SgmlParseException(string.Format(CultureInfo.CurrentUICulture, "Invalid name start character '{0}'", ch));
  458. }
  459. while (ch != Entity.EOF && term.IndexOf(ch) < 0)
  460. {
  461. if (!nmtoken || ch == '_' || ch == '.' || ch == '-' || ch == ':' || char.IsLetterOrDigit(ch)) {
  462. sb.Append(ch);
  463. }
  464. else {
  465. throw new SgmlParseException(
  466. string.Format(CultureInfo.CurrentUICulture, "Invalid name character '{0}'", ch));
  467. }
  468. ch = ReadChar();
  469. }
  470. return sb.ToString();
  471. }
  472. /// <summary>
  473. /// Read a literal from the input stream.
  474. /// </summary>
  475. /// <param name="sb">The <see cref="StringBuilder"/> to use to build the literal.</param>
  476. /// <param name="quote">The delimiter for the literal.</param>
  477. /// <returns>The literal scanned from the input stream.</returns>
  478. public string ScanLiteral(StringBuilder sb, char quote)
  479. {
  480. if (sb == null)
  481. throw new ArgumentNullException("sb");
  482. sb.Length = 0;
  483. char ch = ReadChar();
  484. while (ch != Entity.EOF && ch != quote)
  485. {
  486. if (ch == '&')
  487. {
  488. ch = ReadChar();
  489. if (ch == '#')
  490. {
  491. string charent = ExpandCharEntity();
  492. sb.Append(charent);
  493. ch = this.m_lastchar;
  494. }
  495. else
  496. {
  497. sb.Append('&');
  498. sb.Append(ch);
  499. ch = ReadChar();
  500. }
  501. }
  502. else
  503. {
  504. sb.Append(ch);
  505. ch = ReadChar();
  506. }
  507. }
  508. ReadChar(); // consume end quote.
  509. return sb.ToString();
  510. }
  511. /// <summary>
  512. /// Reads input until the end of the input stream or until a string of terminator characters is found.
  513. /// </summary>
  514. /// <param name="sb">The <see cref="StringBuilder"/> to use to build the string.</param>
  515. /// <param name="type">The type of the element being read (only used in reporting errors).</param>
  516. /// <param name="terminators">The string of terminator characters to look for.</param>
  517. /// <returns>The string read from the input stream.</returns>
  518. public string ScanToEnd(StringBuilder sb, string type, string terminators)
  519. {
  520. if (terminators == null)
  521. throw new ArgumentNullException("terminators");
  522. if (sb != null)
  523. sb.Length = 0;
  524. int start = m_line;
  525. // This method scans over a chunk of text looking for the
  526. // termination sequence specified by the 'terminators' parameter.
  527. char ch = ReadChar();
  528. int state = 0;
  529. char next = terminators[state];
  530. while (ch != Entity.EOF)
  531. {
  532. if (ch == next)
  533. {
  534. state++;
  535. if (state >= terminators.Length)
  536. {
  537. // found it!
  538. break;
  539. }
  540. next = terminators[state];
  541. }
  542. else if (state > 0)
  543. {
  544. // char didn't match, so go back and see how much does still match.
  545. int i = state - 1;
  546. int newstate = 0;
  547. while (i >= 0 && newstate == 0)
  548. {
  549. if (terminators[i] == ch)
  550. {
  551. // character is part of the terminators pattern, ok, so see if we can
  552. // match all the way back to the beginning of the pattern.
  553. int j = 1;
  554. while (i - j >= 0)
  555. {
  556. if (terminators[i - j] != terminators[state - j])
  557. break;
  558. j++;
  559. }
  560. if (j > i)
  561. {
  562. newstate = i + 1;
  563. }
  564. }
  565. else
  566. {
  567. i--;
  568. }
  569. }
  570. if (sb != null)
  571. {
  572. i = (i < 0) ? 1 : 0;
  573. for (int k = 0; k <= state - newstate - i; k++)
  574. {
  575. sb.Append(terminators[k]);
  576. }
  577. if (i > 0) // see if we've matched this char or not
  578. sb.Append(ch); // if not then append it to buffer.
  579. }
  580. state = newstate;
  581. next = terminators[newstate];
  582. }
  583. else
  584. {
  585. if (sb != null)
  586. sb.Append(ch);
  587. }
  588. ch = ReadChar();
  589. }
  590. if (ch == 0)
  591. Error(type + " starting on line {0} was never closed", start);
  592. ReadChar(); // consume last char in termination sequence.
  593. if (sb != null)
  594. return sb.ToString();
  595. else
  596. return string.Empty;
  597. }
  598. /// <summary>
  599. /// Expands a character entity to be read from the input stream.
  600. /// </summary>
  601. /// <returns>The string for the character entity.</returns>
  602. public string ExpandCharEntity()
  603. {
  604. string value;
  605. int v = ReadNumericEntityCode(out value);
  606. if(v == -1)
  607. {
  608. return value;
  609. }
  610. // HACK ALERT: IE and Netscape map the unicode characters
  611. if (this.m_isHtml && v >= 0x80 & v <= 0x9F)
  612. {
  613. // This range of control characters is mapped to Windows-1252!
  614. int i = v - 0x80;
  615. int unicode = CtrlMap[i];
  616. return Convert.ToChar(unicode).ToString();
  617. }
  618. if (0xD800 <= v && v <= 0xDBFF)
  619. {
  620. // high surrogate
  621. if (m_lastchar == '&')
  622. {
  623. char ch = ReadChar();
  624. if (ch == '#')
  625. {
  626. string value2;
  627. int v2 = ReadNumericEntityCode(out value2);
  628. if(v2 == -1)
  629. {
  630. return value + ";" + value2;
  631. }
  632. if (0xDC00 <= v2 && v2 <= 0xDFFF)
  633. {
  634. // low surrogate
  635. v = char.ConvertToUtf32((char)v, (char)v2);
  636. }
  637. }
  638. else
  639. {
  640. Error("Premature {0} parsing surrogate pair", ch);
  641. }
  642. }
  643. else
  644. {
  645. Error("Premature {0} parsing surrogate pair", m_lastchar);
  646. }
  647. }
  648. // NOTE (steveb): we need to use ConvertFromUtf32 to allow for extended numeric encodings
  649. return char.ConvertFromUtf32(v);
  650. }
  651. private int ReadNumericEntityCode(out string value)
  652. {
  653. int v = 0;
  654. char ch = ReadChar();
  655. value = "&#";
  656. if (ch == 'x')
  657. {
  658. bool sawHexDigit = false;
  659. value += "x";
  660. ch = ReadChar();
  661. for (; ch != Entity.EOF && ch != ';'; ch = ReadChar())
  662. {
  663. int p = 0;
  664. if (ch >= '0' && ch <= '9')
  665. {
  666. p = (int)(ch - '0');
  667. sawHexDigit = true;
  668. }
  669. else if (ch >= 'a' && ch <= 'f')
  670. {
  671. p = (int)(ch - 'a') + 10;
  672. sawHexDigit = true;
  673. }
  674. else if (ch >= 'A' && ch <= 'F')
  675. {
  676. p = (int)(ch - 'A') + 10;
  677. sawHexDigit = true;
  678. }
  679. else
  680. {
  681. break; //we must be done!
  682. //Error("Hex digit out of range '{0}'", (int)ch);
  683. }
  684. value += ch;
  685. v = (v*16) + p;
  686. }
  687. if (!sawHexDigit)
  688. {
  689. return -1;
  690. }
  691. }
  692. else
  693. {
  694. bool sawDigit = false;
  695. for (; ch != Entity.EOF && ch != ';'; ch = ReadChar())
  696. {
  697. if (ch >= '0' && ch <= '9')
  698. {
  699. v = (v*10) + (int)(ch - '0');
  700. sawDigit = true;
  701. }
  702. else
  703. {
  704. break; // we must be done!
  705. //Error("Decimal digit out of range '{0}'", (int)ch);
  706. }
  707. value += ch;
  708. }
  709. if (!sawDigit)
  710. {
  711. return -1;
  712. }
  713. }
  714. if (ch == 0)
  715. {
  716. Error("Premature {0} parsing entity reference", ch);
  717. }
  718. else if (ch == ';')
  719. {
  720. ReadChar();
  721. }
  722. return v;
  723. }
  724. static int[] CtrlMap = new int[] {
  725. // This is the windows-1252 mapping of the code points 0x80 through 0x9f.
  726. 8364, 129, 8218, 402, 8222, 8230, 8224, 8225, 710, 8240, 352, 8249, 338, 141,
  727. 381, 143, 144, 8216, 8217, 8220, 8221, 8226, 8211, 8212, 732, 8482, 353, 8250,
  728. 339, 157, 382, 376
  729. };
  730. /// <summary>
  731. /// Raise a processing error.
  732. /// </summary>
  733. /// <param name="msg">The error message to use in the exception.</param>
  734. /// <exception cref="SgmlParseException">Always thrown.</exception>
  735. public void Error(string msg)
  736. {
  737. throw new SgmlParseException(msg, this);
  738. }
  739. /// <summary>
  740. /// Raise a processing error.
  741. /// </summary>
  742. /// <param name="msg">The error message to use in the exception.</param>
  743. /// <param name="ch">The unexpected character causing the error.</param>
  744. /// <exception cref="SgmlParseException">Always thrown.</exception>
  745. public void Error(string msg, char ch)
  746. {
  747. string str = (ch == Entity.EOF) ? "EOF" : char.ToString(ch);
  748. throw new SgmlParseException(string.Format(CultureInfo.CurrentUICulture, msg, str), this);
  749. }
  750. /// <summary>
  751. /// Raise a processing error.
  752. /// </summary>
  753. /// <param name="msg">The error message to use in the exception.</param>
  754. /// <param name="x">The value causing the error.</param>
  755. /// <exception cref="SgmlParseException">Always thrown.</exception>
  756. public void Error(string msg, int x)
  757. {
  758. throw new SgmlParseException(string.Format(CultureInfo.CurrentUICulture, msg, x), this);
  759. }
  760. /// <summary>
  761. /// Raise a processing error.
  762. /// </summary>
  763. /// <param name="msg">The error message to use in the exception.</param>
  764. /// <param name="arg">The argument for the error.</param>
  765. /// <exception cref="SgmlParseException">Always thrown.</exception>
  766. public void Error(string msg, string arg)
  767. {
  768. throw new SgmlParseException(string.Format(CultureInfo.CurrentUICulture, msg, arg), this);
  769. }
  770. /// <summary>
  771. /// Returns a string giving information on how the entity is referenced and declared, walking up the parents until the top level parent entity is found.
  772. /// </summary>
  773. /// <returns>Contextual information for the entity.</returns>
  774. public string Context()
  775. {
  776. Entity p = this;
  777. StringBuilder sb = new StringBuilder();
  778. while (p != null)
  779. {
  780. string msg;
  781. if (p.m_isInternal)
  782. {
  783. msg = string.Format(CultureInfo.InvariantCulture, "\nReferenced on line {0}, position {1} of internal entity '{2}'", p.m_line, p.LinePosition, p.m_name);
  784. }
  785. else {
  786. msg = string.Format(CultureInfo.InvariantCulture, "\nReferenced on line {0}, position {1} of '{2}' entity at [{3}]", p.m_line, p.LinePosition, p.m_name, p.ResolvedUri.AbsolutePath);
  787. }
  788. sb.Append(msg);
  789. p = p.Parent;
  790. }
  791. return sb.ToString();
  792. }
  793. /// <summary>
  794. /// Checks whether a token denotes a literal entity or not.
  795. /// </summary>
  796. /// <param name="token">The token to check.</param>
  797. /// <returns>true if the token is "CDATA", "SDATA" or "PI", otherwise false.</returns>
  798. public static bool IsLiteralType(string token)
  799. {
  800. return string.Equals(token, "CDATA", StringComparison.OrdinalIgnoreCase) ||
  801. string.Equals(token, "SDATA", StringComparison.OrdinalIgnoreCase) ||
  802. string.Equals(token, "PI", StringComparison.OrdinalIgnoreCase);
  803. }
  804. /// <summary>
  805. /// Sets the entity to be a literal of the type specified.
  806. /// </summary>
  807. /// <param name="token">One of "CDATA", "SDATA" or "PI".</param>
  808. public void SetLiteralType(string token)
  809. {
  810. switch (token)
  811. {
  812. case "CDATA":
  813. this.m_literalType = LiteralType.CDATA;
  814. break;
  815. case "SDATA":
  816. this.m_literalType = LiteralType.SDATA;
  817. break;
  818. case "PI":
  819. this.m_literalType = LiteralType.PI;
  820. break;
  821. }
  822. }
  823. #region IDisposable Members
  824. /// <summary>
  825. /// The finalizer for the Entity class.
  826. /// </summary>
  827. ~Entity()
  828. {
  829. Dispose(false);
  830. }
  831. /// <summary>
  832. /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
  833. /// </summary>
  834. public void Dispose()
  835. {
  836. Dispose(true);
  837. GC.SuppressFinalize(this);
  838. }
  839. /// <summary>
  840. /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
  841. /// </summary>
  842. /// <param name="isDisposing">true if this method has been called by user code, false if it has been called through a finalizer.</param>
  843. protected virtual void Dispose(bool isDisposing)
  844. {
  845. if (isDisposing)
  846. {
  847. if (m_stm != null)
  848. {
  849. m_stm.Dispose();
  850. m_stm = null;
  851. }
  852. }
  853. }
  854. #endregion
  855. }
  856. // This class decodes an HTML/XML stream correctly.
  857. internal class HtmlStream : TextReader
  858. {
  859. private Stream stm;
  860. private byte[] rawBuffer;
  861. private int rawPos;
  862. private int rawUsed;
  863. private Encoding m_encoding;
  864. private Decoder m_decoder;
  865. private char[] m_buffer;
  866. private int used;
  867. private int pos;
  868. private const int BUFSIZE = 16384;
  869. private const int EOF = -1;
  870. public HtmlStream(Stream stm, Encoding defaultEncoding)
  871. {
  872. if (defaultEncoding == null) defaultEncoding = Encoding.UTF8; // default is UTF8
  873. if (!stm.CanSeek){
  874. // Need to be able to seek to sniff correctly.
  875. stm = CopyToMemoryStream(stm);
  876. }
  877. this.stm = stm;
  878. rawBuffer = new Byte[BUFSIZE];
  879. rawUsed = stm.Read(rawBuffer, 0, 4); // maximum byte order mark
  880. this.m_buffer = new char[BUFSIZE];
  881. // Check byte order marks
  882. this.m_decoder = AutoDetectEncoding(rawBuffer, ref rawPos, rawUsed);
  883. int bom = rawPos;
  884. if (this.m_decoder == null)
  885. {
  886. this.m_decoder = defaultEncoding.GetDecoder();
  887. rawUsed += stm.Read(rawBuffer, 4, BUFSIZE-4);
  888. DecodeBlock();
  889. // Now sniff to see if there is an XML declaration or HTML <META> tag.
  890. Decoder sd = SniffEncoding();
  891. if (sd != null) {
  892. this.m_decoder = sd;
  893. }
  894. }
  895. // Reset to get ready for Read()
  896. this.stm.Seek(0, SeekOrigin.Begin);
  897. this.pos = this.used = 0;
  898. // skip bom
  899. if (bom>0){
  900. stm.Read(this.rawBuffer, 0, bom);
  901. }
  902. this.rawPos = this.rawUsed = 0;
  903. }
  904. public Encoding Encoding
  905. {
  906. get
  907. {
  908. return this.m_encoding;
  909. }
  910. }
  911. private static Stream CopyToMemoryStream(Stream s)
  912. {
  913. int size = 100000; // large heap is more efficient
  914. byte[] copyBuff = new byte[size];
  915. int len;
  916. MemoryStream r = new MemoryStream();
  917. while ((len = s.Read(copyBuff, 0, size)) > 0)
  918. r.Write(copyBuff, 0, len);
  919. r.Seek(0, SeekOrigin.Begin);
  920. s.Dispose();
  921. return r;
  922. }
  923. internal void DecodeBlock() {
  924. // shift current chars to beginning.
  925. if (pos > 0) {
  926. if (pos < used) {
  927. System.Array.Copy(m_buffer, pos, m_buffer, 0, used - pos);
  928. }
  929. used -= pos;
  930. pos = 0;
  931. }
  932. int len = m_decoder.GetCharCount(rawBuffer, rawPos, rawUsed - rawPos);
  933. int available = m_buffer.Length - used;
  934. if (available < len) {
  935. char[] newbuf = new char[m_buffer.Length + len];
  936. System.Array.Copy(m_buffer, pos, newbuf, 0, used - pos);
  937. m_buffer = newbuf;
  938. }
  939. used = pos + m_decoder.GetChars(rawBuffer, rawPos, rawUsed - rawPos, m_buffer, pos);
  940. rawPos = rawUsed; // consumed the whole buffer!
  941. }
  942. internal static Decoder AutoDetectEncoding(byte[] buffer, ref int index, int length) {
  943. if (4 <= (length - index)) {
  944. uint w = (uint)buffer[index + 0] << 24 | (uint)buffer[index + 1] << 16 | (uint)buffer[index + 2] << 8 | (uint)buffer[index + 3];
  945. // see if it's a 4-byte encoding
  946. switch (w) {
  947. case 0xfefffeff:
  948. index += 4;
  949. return new Ucs4DecoderBigEngian();
  950. case 0xfffefffe:
  951. index += 4;
  952. return new Ucs4DecoderLittleEndian();
  953. case 0x3c000000:
  954. goto case 0xfefffeff;
  955. case 0x0000003c:
  956. goto case 0xfffefffe;
  957. }
  958. w >>= 8;
  959. if (w == 0xefbbbf) {
  960. index += 3;
  961. return Encoding.UTF8.GetDecoder();
  962. }
  963. w >>= 8;
  964. switch (w) {
  965. case 0xfeff:
  966. index += 2;
  967. return UnicodeEncoding.BigEndianUnicode.GetDecoder();
  968. case 0xfffe:
  969. index += 2;
  970. return new UnicodeEncoding(false, false).GetDecoder();
  971. case 0x3c00:
  972. goto case 0xfeff;
  973. case 0x003c:
  974. goto case 0xfffe;
  975. }
  976. }
  977. return null;
  978. }
  979. private int ReadChar() {
  980. // Read only up to end of current buffer then stop.
  981. if (pos < used) return m_buffer[pos++];
  982. return EOF;
  983. }
  984. private int PeekChar() {
  985. int ch = ReadChar();
  986. if (ch != EOF) {
  987. pos--;
  988. }
  989. return ch;
  990. }
  991. private bool SniffPattern(string pattern) {
  992. int ch = PeekChar();
  993. if (ch != pattern[0]) return false;
  994. for (int i = 0, n = pattern.Length; ch != EOF && i < n; i++) {
  995. ch = ReadChar();
  996. char m = pattern[i];
  997. if (ch != m) {
  998. return false;
  999. }
  1000. }
  1001. return true;
  1002. }
  1003. private void SniffWhitespace() {
  1004. char ch = (char)PeekChar();
  1005. while (ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n') {
  1006. int i = pos;
  1007. ch = (char)ReadChar();
  1008. if (ch != ' ' && ch != '\t' && ch != '\r' && ch != '\n')
  1009. pos = i;
  1010. }
  1011. }
  1012. private string SniffLiteral() {
  1013. int quoteChar = PeekChar();
  1014. if (quoteChar == '\'' || quoteChar == '"') {
  1015. ReadChar();// consume quote char
  1016. int i = this.pos;
  1017. int ch = ReadChar();
  1018. while (ch != EOF && ch != quoteChar) {
  1019. ch = ReadChar();
  1020. }
  1021. return (pos>i) ? new string(m_buffer, i, pos - i - 1) : "";
  1022. }
  1023. return null;
  1024. }
  1025. private string SniffAttribute(string name) {
  1026. SniffWhitespace();
  1027. string id = SniffName();
  1028. if (string.Equals(name, id, StringComparison.OrdinalIgnoreCase)) {
  1029. SniffWhitespace();
  1030. if (SniffPattern("=")) {
  1031. SniffWhitespace();
  1032. return SniffLiteral();
  1033. }
  1034. }
  1035. return null;
  1036. }
  1037. private string SniffAttribute(out string name) {
  1038. SniffWhitespace();
  1039. name = SniffName();
  1040. if (name != null){
  1041. SniffWhitespace();
  1042. if (SniffPattern("=")) {
  1043. SniffWhitespace();
  1044. return SniffLiteral();
  1045. }
  1046. }
  1047. return null;
  1048. }
  1049. private void SniffTerminator(string term) {
  1050. int ch = ReadChar();
  1051. int i = 0;
  1052. int n = term.Length;
  1053. while (i < n && ch != EOF) {
  1054. if (term[i] == ch) {
  1055. i++;
  1056. if (i == n) break;
  1057. } else {
  1058. i = 0; // reset.
  1059. }
  1060. ch = ReadChar();
  1061. }
  1062. }
  1063. internal Decoder SniffEncoding()
  1064. {
  1065. Decoder decoder = null;
  1066. if (SniffPattern("<?xml"))
  1067. {
  1068. string version = SniffAttribute("version");
  1069. if (version != null)
  1070. {
  1071. string encoding = SniffAttribute("encoding");
  1072. if (encoding != null)
  1073. {
  1074. try
  1075. {
  1076. Encoding enc = Encoding.GetEncoding(encoding);
  1077. if (enc != null)
  1078. {
  1079. this.m_encoding = enc;
  1080. return enc.GetDecoder();
  1081. }
  1082. }
  1083. catch (ArgumentException)
  1084. {
  1085. // oh well then.
  1086. }
  1087. }
  1088. SniffTerminator(">");
  1089. }
  1090. }
  1091. if (decoder == null) {
  1092. return SniffMeta();
  1093. }
  1094. return null;
  1095. }
  1096. internal Decoder SniffMeta()
  1097. {
  1098. int i = ReadChar();
  1099. while (i != EOF)
  1100. {
  1101. char ch = (char)i;
  1102. if (ch == '<')
  1103. {
  1104. string name = SniffName();
  1105. if (name != null && StringUtilities.EqualsIgnoreCase(name, "meta"))
  1106. {
  1107. string httpequiv = null;
  1108. string content = null;
  1109. while (true)
  1110. {
  1111. string value = SniffAttribute(out name);
  1112. if (name == null)
  1113. break;
  1114. if (StringUtilities.EqualsIgnoreCase(name, "http-equiv"))
  1115. {
  1116. httpequiv = value;
  1117. }
  1118. else if (StringUtilities.EqualsIgnoreCase(name, "content"))
  1119. {
  1120. content = value;
  1121. }
  1122. }
  1123. if (httpequiv != null && StringUtilities.EqualsIgnoreCase(httpequiv, "content-type") && content != null)
  1124. {
  1125. int j = content.IndexOf("charset");
  1126. if (j >= 0)
  1127. {
  1128. //charset=utf-8
  1129. j = content.IndexOf("=", j);
  1130. if (j >= 0)
  1131. {
  1132. j++;
  1133. int k = content.IndexOf(";", j);
  1134. if (k<0) k = content.Length;
  1135. string charset = content.Substring(j, k-j).Trim();
  1136. try
  1137. {
  1138. Encoding e = Encoding.GetEncoding(charset);
  1139. this.m_encoding = e;
  1140. return e.GetDecoder();
  1141. } catch (ArgumentException) {}
  1142. }
  1143. }
  1144. }
  1145. }
  1146. }
  1147. i = ReadChar();
  1148. }
  1149. return null;
  1150. }
  1151. internal string SniffName()
  1152. {
  1153. int c = PeekChar();
  1154. if (c == EOF)
  1155. return null;
  1156. char ch = (char)c;
  1157. int start = pos;
  1158. while (pos < used - 1 && (char.IsLetterOrDigit(ch) || ch == '-' || ch == '_' || ch == ':'))
  1159. ch = m_buffer[++pos];
  1160. if (start == pos)
  1161. return null;
  1162. return new string(m_buffer, start, pos - start);
  1163. }
  1164. [SuppressMessage("Microsoft.Performance", "CA1811", Justification = "Kept for potential future usage.")]
  1165. internal void SkipWhitespace()
  1166. {
  1167. char ch = (char)PeekChar();
  1168. while (pos < used - 1 && (ch == ' ' || ch == '\r' || ch == '\n'))
  1169. ch = m_buffer[++pos];
  1170. }
  1171. [SuppressMessage("Microsoft.Performance", "CA1811", Justification = "Kept for potential future usage.")]
  1172. internal void SkipTo(char what)
  1173. {
  1174. char ch = (char)PeekChar();
  1175. while (pos < used - 1 && (ch != what))
  1176. ch = m_buffer[++pos];
  1177. }
  1178. [SuppressMessage("Microsoft.Performance", "CA1811", Justification = "Kept for potential future usage.")]
  1179. internal string ParseAttribute()
  1180. {
  1181. SkipTo('=');
  1182. if (pos < used)
  1183. {
  1184. pos++;
  1185. SkipWhitespace();
  1186. if (pos < used) {
  1187. char quote = m_buffer[pos];
  1188. pos++;
  1189. int start = pos;
  1190. SkipTo(quote);
  1191. if (pos < used) {
  1192. string result = new string(m_buffer, start, pos - start);
  1193. pos++;
  1194. return result;
  1195. }
  1196. }
  1197. }
  1198. return null;
  1199. }
  1200. public override int Peek() {
  1201. int result = Read();
  1202. if (result != EOF) {
  1203. pos--;
  1204. }
  1205. return result;
  1206. }
  1207. public override int Read()
  1208. {
  1209. if (pos == used)
  1210. {
  1211. rawUsed = stm.Read(rawBuffer, 0, rawBuffer.Length);
  1212. rawPos = 0;
  1213. if (rawUsed == 0) return EOF;
  1214. DecodeBlock();
  1215. }
  1216. if (pos < used) return m_buffer[pos++];
  1217. return -1;
  1218. }
  1219. public override int Read(char[] buffer, int start, int length) {
  1220. if (pos == used) {
  1221. rawUsed = stm.Read(rawBuffer, 0, rawBuffer.Length);
  1222. rawPos = 0;
  1223. if (rawUsed == 0) return -1;
  1224. DecodeBlock();
  1225. }
  1226. if (pos < used) {
  1227. length = Math.Min(used - pos, length);
  1228. Array.Copy(this.m_buffer, pos, buffer, start, length);
  1229. pos += length;
  1230. return length;
  1231. }
  1232. return 0;
  1233. }
  1234. public override int ReadBlock(char[] data, int index, int count)
  1235. {
  1236. return Read(data, index, count);
  1237. }
  1238. // Read up to end of line, or full buffer, whichever comes first.
  1239. [SuppressMessage("Microsoft.Performance", "CA1811", Justification = "Kept for potential future usage.")]
  1240. public int ReadLine(char[] buffer, int start, int length)
  1241. {
  1242. int i = 0;
  1243. int ch = ReadChar();
  1244. while (ch != EOF) {
  1245. buffer[i+start] = (char)ch;
  1246. i++;
  1247. if (i+start == length)
  1248. break; // buffer is full
  1249. if (ch == '\r' ) {
  1250. if (PeekChar() == '\n') {
  1251. ch = ReadChar();
  1252. buffer[i + start] = (char)ch;
  1253. i++;
  1254. }
  1255. break;
  1256. } else if (ch == '\n') {
  1257. break;
  1258. }
  1259. ch = ReadChar();
  1260. }
  1261. return i;
  1262. }
  1263. public override string ReadToEnd() {
  1264. char[] buffer = new char[100000]; // large block heap is more efficient
  1265. int len = 0;
  1266. StringBuilder sb = new StringBuilder();
  1267. while ((len = Read(buffer, 0, buffer.Length)) > 0) {
  1268. sb.Append(buffer, 0, len);
  1269. }
  1270. return sb.ToString();
  1271. }
  1272. protected override void Dispose(bool disposing)
  1273. {
  1274. if(stm != null)
  1275. stm.Dispose();
  1276. base.Dispose(disposing);
  1277. }
  1278. }
  1279. internal abstract class Ucs4Decoder : Decoder {
  1280. internal byte[] temp = new byte[4];
  1281. internal int tempBytes = 0;
  1282. public override int GetCharCount(byte[] bytes, int index, int count) {
  1283. return (count + tempBytes) / 4;
  1284. }
  1285. internal abstract int GetFullChars(byte[] bytes, int byteIndex, int byteCount, char[] chars, int charIndex);
  1286. public override int GetChars(byte[] bytes, int byteIndex, int byteCount, char[] chars, int charIndex) {
  1287. int i = tempBytes;
  1288. if (tempBytes > 0) {
  1289. for (; i < 4; i++) {
  1290. temp[i] = bytes[byteIndex];
  1291. byteIndex++;
  1292. byteCount--;
  1293. }
  1294. i = 1;
  1295. GetFullChars(temp, 0, 4, chars, charIndex);
  1296. charIndex++;
  1297. } else
  1298. i = 0;
  1299. i = GetFullChars(bytes, byteIndex, byteCount, chars, charIndex) + i;
  1300. int j = (tempBytes + byteCount) % 4;
  1301. byteCount += byteIndex;
  1302. byteIndex = byteCount - j;
  1303. tempBytes = 0;
  1304. if (byteIndex >= 0)
  1305. for (; byteIndex < byteCount; byteIndex++) {
  1306. temp[tempBytes] = bytes[byteIndex];
  1307. tempBytes++;
  1308. }
  1309. return i;
  1310. }
  1311. internal static char UnicodeToUTF16(UInt32 code) {
  1312. byte lowerByte, higherByte;
  1313. lowerByte = (byte)(0xD7C0 + (code >> 10));
  1314. higherByte = (byte)(0xDC00 | code & 0x3ff);
  1315. return ((char)((higherByte << 8) | lowerByte));
  1316. }
  1317. }
  1318. internal class Ucs4DecoderBigEngian : Ucs4Decoder {
  1319. internal override int GetFullChars(byte[] bytes, int byteIndex, int byteCount, char[] chars, int charIndex) {
  1320. UInt32 code;
  1321. int i, j;
  1322. byteCount += byteIndex;
  1323. for (i = byteIndex, j = charIndex; i + 3 < byteCount; ) {
  1324. code = (UInt32)(((bytes[i + 3]) << 24) | (bytes[i + 2] << 16) | (bytes[i + 1] << 8) | (bytes[i]));
  1325. if (code > 0x10FFFF) {
  1326. throw new SgmlParseException(string.Format(CultureInfo.CurrentUICulture, "Invalid character 0x{0:x} in encoding", code));
  1327. } else if (code > 0xFFFF) {
  1328. chars[j] = UnicodeToUTF16(code);
  1329. j++;
  1330. } else {
  1331. if (code >= 0xD800 && code <= 0xDFFF) {
  1332. throw new SgmlParseException(string.Format(CultureInfo.CurrentUICulture, "Invalid character 0x{0:x} in encoding", code));
  1333. } else {
  1334. chars[j] = (char)code;
  1335. }
  1336. }
  1337. j++;
  1338. i += 4;
  1339. }
  1340. return j - charIndex;
  1341. }
  1342. }
  1343. internal class Ucs4DecoderLittleEndian : Ucs4Decoder {
  1344. internal override int GetFullChars(byte[] bytes, int byteIndex, int byteCount, char[] chars,

Large files files are truncated, but you can click here to view the full file