Subversion Repositories spacemission

Rev

Blame | Last modification | View Log | RSS feed

  1. Unit MarkdownDaringFireball;
  2.  
  3. {
  4.   This code was translated from TxtMark (https://github.com/rjeschke/txtmark)
  5.  
  6.   Copyright (C) 2011-2015 RenĂ© Jeschke <rene_jeschke@yahoo.de>
  7.   Copyright (C) 2015+ Grahame Grieve <grahameg@gmail.com> (pascal port)
  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. {$IFDEF FPC}{$MODE DELPHI}{$ENDIF}
  23.  
  24. interface
  25.  
  26. uses
  27.   SysUtils, StrUtils, Classes, Character, TypInfo, Math,
  28.   MarkdownProcessor;
  29.  
  30. type
  31.   THTMLElement = (heNONE, hea, heabbr, heacronym, headdress, heapplet, hearea, heb, hebase, hebasefont, hebdo, hebig, heblockquote, hebody, hebr, hebutton, hecaption, hecite,
  32.     hecode, hecol, hecolgroup, hedd, hedel, hedfn, hediv, hedl, hedt, heem, hefieldset, hefont, heform, heframe, heframeset, heh1, heh2, heh3, heh4, heh5, heh6, hehead, hehr,
  33.     hehtml, hei, heiframe, heimg, heinput, heins, hekbd, helabel, helegend, heli, helink, hemap, hemeta, henoscript, heobject, heol, heoptgroup, heoption, hep, heparam, hepre, heq,
  34.     hes, hesamp, hescript, heselect, hesmall, hespan, hestrike, hestrong, hestyle, hesub, hesup, hetable, hetbody, hetd, hetextarea, hetfoot, heth, hethead, hetitle, hetr, hett,
  35.     heu, heul, hevar);
  36.  
  37. const
  38.   // pstfix
  39.   ENTITY_NAMES: array[0..249] of String = ('&Acirc;', '&acirc;', '&acute;', '&AElig;', '&aelig;', '&Agrave;', '&agrave;', '&alefsym;', '&Alpha;', '&alpha;', '&amp;', '&and;', '&ang;',
  40.     '&apos;', '&Aring;', '&aring;', '&asymp;', '&Atilde;', '&atilde;', '&Auml;', '&auml;', '&bdquo;', '&Beta;', '&beta;', '&brvbar;', '&bull;', '&cap;', '&Ccedil;', '&ccedil;',
  41.     '&cedil;', '&cent;', '&Chi;', '&chi;', '&circ;', '&clubs;', '&cong;', '&copy;', '&crarr;', '&cup;', '&curren;', '&Dagger;', '&dagger;', '&dArr;', '&darr;', '&deg;', '&Delta;',
  42.     '&delta;', '&diams;', '&divide;', '&Eacute;', '&eacute;', '&Ecirc;', '&ecirc;', '&Egrave;', '&egrave;', '&empty;', '&emsp;', '&ensp;', '&Epsilon;', '&epsilon;', '&equiv;',
  43.     '&Eta;', '&eta;', '&ETH;', '&eth;', '&Euml;', '&euml;', '&euro;', '&exist;', '&fnof;', '&forall;', '&frac12;', '&frac14;', '&frac34;', '&frasl;', '&Gamma;', '&gamma;', '&ge;',
  44.     '&gt;', '&hArr;', '&harr;', '&hearts;', '&hellip;', '&Iacute;', '&iacute;', '&Icirc;', '&icirc;', '&iexcl;', '&Igrave;', '&igrave;', '&image;', '&infin;', '&int;', '&Iota;',
  45.     '&iota;', '&iquest;', '&isin;', '&Iuml;', '&iuml;', '&Kappa;', '&kappa;', '&Lambda;', '&lambda;', '&lang;', '&laquo;', '&lArr;', '&larr;', '&lceil;', '&ldquo;', '&le;',
  46.     '&lfloor;', '&lowast;', '&loz;', '&lrm;', '&lsaquo;', '&lsquo;', '&lt;', '&macr;', '&mdash;', '&micro;', '&middot;', '&minus;', '&Mu;', '&mu;', '&nabla;', '&nbsp;', '&ndash;',
  47.     '&ne;', '&ni;', '&not;', '&notin;', '&nsub;', '&Ntilde;', '&ntilde;', '&Nu;', '&nu;', '&Oacute;', '&oacute;', '&Ocirc;', '&ocirc;', '&OElig;', '&oelig;', '&Ograve;',
  48.     '&ograve;', '&oline;', '&Omega;', '&omega;', '&Omicron;', '&omicron;', '&oplus;', '&or;', '&ordf;', '&ordm;', '&Oslash;', '&oslash;', '&Otilde;', '&otilde;', '&otimes;',
  49.     '&Ouml;', '&ouml;', '&para;', '&part;', '&permil;', '&perp;', '&Phi;', '&phi;', '&Pi;', '&pi;', '&piv;', '&plusmn;', '&pound;', '&Prime;', '&prime;', '&prod;', '&prop;',
  50.     '&Psi;', '&psi;', '&quot;', '&radic;', '&rang;', '&raquo;', '&rArr;', '&rarr;', '&rceil;', '&rdquo;', '&real;', '&reg;', '&rfloor;', '&Rho;', '&rho;', '&rlm;', '&rsaquo;',
  51.     '&rsquo;', '&sbquo;', '&Scaron;', '&scaron;', '&sdot;', '&sect;', '&shy;', '&Sigma;', '&sigma;', '&sigmaf;', '&sim;', '&spades;', '&sub;', '&sube;', '&sum;', '&sup;', '&sup1;',
  52.     '&sup2;', '&sup3;', '&supe;', '&szlig;', '&Tau;', '&tau;', '&there4;', '&Theta;', '&theta;', '&thetasym;', '&thinsp;', '&thorn;', '&tilde;', '&times;', '&trade;', '&Uacute;',
  53.     '&uacute;', '&uArr;', '&uarr;', '&Ucirc;', '&ucirc;', '&Ugrave;', '&ugrave;', '&uml;', '&upsih;', '&Upsilon;', '&upsilon;', '&Uuml;', '&uuml;', '&weierp;', '&Xi;', '&xi;',
  54.     '&Yacute;', '&yacute;', '&yen;', '&Yuml;', '&yuml;', '&Zeta;', '&zeta;', '&zwj;', '&zwnj;');
  55.  
  56.   // Characters corresponding to ENTITY_NAMES. */
  57.   // pstfix
  58.   ENTITY_CHARS: array[0..249] of integer = ($00C2, $00E2, $00B4, $00C6, $00E6, $00C0, $00E0, $2135, $0391, $03B1, $0026, $2227, $2220, ord(''''), $00C5, $00E5, $2248, $00C3, $00E3, $00C4,
  59.     $00E4, $201E, $0392, $03B2, $00A6, $2022, $2229, $00C7, $00E7, $00B8, $00A2, $03A7, $03C7, $02C6, $2663, $2245, $00A9, $21B5, $222A, $00A4, $2021, $2020, $21D3, $2193, $00B0,
  60.     $0394, $03B4, $2666, $00F7, $00C9, $00E9, $00CA, $00EA, $00C8, $00E8, $2205, $2003, $2002, $0395, $03B5, $2261, $0397, $03B7, $00D0, $00F0, $00CB, $00EB, $20AC, $2203, $0192,
  61.     $2200, $00BD, $00BC, $00BE, $2044, $0393, $03B3, $2265, $003E, $21D4, $2194, $2665, $2026, $00CD, $00ED, $00CE, $00EE, $00A1, $00CC, $00EC, $2111, $221E, $222B, $0399, $03B9,
  62.     $00BF, $2208, $00CF, $00EF, $039A, $03BA, $039B, $03BB, $2329, $00AB, $21D0, $2190, $2308, $201C, $2264, $230A, $2217, $25CA, $200E, $2039, $2018, $003C, $00AF, $2014, $00B5,
  63.     $00B7, $2212, $039C, $03BC, $2207, $00A0, $2013, $2260, $220B, $00AC, $2209, $2284, $00D1, $00F1, $039D, $03BD, $00D3, $00F3, $00D4, $00F4, $0152, $0153, $00D2, $00F2, $203E,
  64.     $03A9, $03C9, $039F, $03BF, $2295, $2228, $00AA, $00BA, $00D8, $00F8, $00D5, $00F5, $2297, $00D6, $00F6, $00B6, $2202, $2030, $22A5, $03A6, $03C6, $03A0, $03C0, $03D6, $00B1,
  65.     $00A3, $2033, $2032, $220F, $221D, $03A8, $03C8, $0022, $221A, $232A, $00BB, $21D2, $2192, $2309, $201D, $211C, $00AE, $230B, $03A1, $03C1, $200F, $203A, $2019, $201A, $0160,
  66.     $0161, $22C5, $00A7, $00AD, $03A3, $03C3, $03C2, $223C, $2660, $2282, $2286, $2211, $2283, $00B9, $00B2, $00B3, $2287, $00DF, $03A4, $03C4, $2234, $0398, $03B8, $03D1, $00DE,
  67.     $00FE, $02DC, $00D7, $2122, $00DA, $00FA, $21D1, $2191, $00DB, $00FB, $00D9, $00F9, $00A8, $03D2, $03A5, $03C5, $00DC, $00FC, $2118, $039E, $03BE, $00DD, $00FD, $00A5, $0178,
  68.     $00FF, $0396, $03B6, $200D, $200C);
  69.  
  70.   LINK_PREFIXES: array[0..3] of String = ('http', 'https', 'ftp', 'ftps');
  71.  
  72.   BLOCK_ELEMENTS: set of THTMLElement = [headdress, heblockquote, hedel, hediv, hedl, hefieldset, heform, heh1, heh2, heh3, heh4, heh5, heh6, hehr, heins, henoscript, heol, hep,
  73.     hepre, hetable, heul];
  74.  
  75.   UNSAFE_ELEMENTS: set of THTMLElement = [heapplet, hehead, hehtml, hebody, heframe, heframeset, heiframe, hescript, heobject];
  76.  
  77.   BUFFER_INCREMENT_SIZE = 1024;
  78.  
  79. Type
  80.   TReader = class
  81.   private
  82.     FValue: String;
  83.     FCursor: integer;
  84.   public
  85.     Constructor Create(source: String);
  86.     function read: char;
  87.   end;
  88.  
  89. {$IFDEF FPC}
  90.   TStringBuilder = class
  91.   private
  92.     FContent : String;
  93.     FLength : Integer;
  94.     FBufferSize : integer;
  95.     function GetChar(index: integer): char;
  96.   public
  97.     Constructor Create;
  98.  
  99.     procedure Clear;
  100.     procedure Append(value : String); overload;
  101.     procedure Append(value : integer); overload;
  102.     procedure Append(value : TStringBuilder); overload;
  103.     property ch[index : integer] : char read GetChar; default;
  104.     function toString : String; override;
  105.     Property Length : Integer Read FLength;
  106.   end;
  107. {$ENDIF}
  108.  
  109.   TUtils = class
  110.   public
  111.     // Skips spaces in the given String. return The new position or -1 if EOL has been reached.
  112.     class function skipSpaces(s: String; start: integer): integer;
  113.  
  114.     // Process the given escape sequence. return The new position.
  115.     class function escape(out_: TStringBuilder; ch: char; position: integer): integer;
  116.  
  117.     // Reads characters until any 'end' character is encountered. return The new position or -1 if no 'end' char was found.
  118.     class function readUntil(out_: TStringBuilder; s: String; start: integer; cend: TSysCharSet): integer; overload;
  119.  
  120.     // Reads characters until the 'end' character is encountered. return The new position or -1 if no 'end' char was found.
  121.     class function readUntil(out_: TStringBuilder; s: String; start: integer; cend: char): integer; overload;
  122.  
  123.     // Reads a markdown link. return The new position or -1 if this is no valid markdown link.
  124.     class function readMdLink(out_: TStringBuilder; s: String; start: integer): integer;
  125.     class function readMdLinkId(out_: TStringBuilder; s: String; start: integer): integer;
  126.  
  127.     // Reads characters until any 'end' character is encountered, ignoring escape sequences.
  128.     class function readRawUntil(out_: TStringBuilder; s: String; start: integer; cend: TSysCharSet): integer; overload;
  129.  
  130.     // Reads characters until the end character is encountered, taking care of HTML/XML strings.
  131.     class function readRawUntil(out_: TStringBuilder; s: String; start: integer; cend: char): integer; overload;
  132.  
  133.     // Reads characters until any 'end' character is encountered, ignoring escape sequences.
  134.     class function readXMLUntil(out_: TStringBuilder; s: String; start: integer; cend: TSysCharSet): integer;
  135.  
  136.     // Appends the given string encoding special HTML characters.
  137.     class procedure appendCode(out_: TStringBuilder; s: String; start: integer; e: integer);
  138.  
  139.     // Appends the given string encoding special HTML characters (used in HTML
  140.     class procedure appendValue(out_: TStringBuilder; s: String; start: integer; e: integer);
  141.  
  142.     // Append the given char as a decimal HTML entity.
  143.     class procedure appendDecEntity(out_: TStringBuilder; value: char);
  144.  
  145.     // Append the given char as a hexadecimal HTML entity.
  146.     class procedure appendHexEntity(out_: TStringBuilder; value: char);
  147.  
  148.     // Appends the given mailto link using obfuscation.
  149.     class procedure appendMailto(out_: TStringBuilder; s: String; start: integer; e: integer);
  150.  
  151.     // Extracts the tag from an XML element.
  152.     class procedure getXMLTag(out_: TStringBuilder; bin: TStringBuilder); overload;
  153.  
  154.     // Extracts the tag from an XML element.
  155.     class procedure getXMLTag(out_: TStringBuilder; s: String); overload;
  156.  
  157.     // Reads an XML element.
  158.     // return The new position or -1 if this is no valid XML element.
  159.     class function readXML(out_: TStringBuilder; s: String; start: integer; safeMode: boolean): integer;
  160.  
  161.     // Appends the given string to the given StringBuilder, replacing '&amp;', '&lt;' and '&gt;' by their respective HTML entities.
  162.     class procedure codeEncode(out_: TStringBuilder; value: String; offset: integer);
  163.  
  164.     // Removes trailing <code>`</code> or <code>~</code> and trims spaces.
  165.     class function getMetaFromFence(fenceLine: String): String;
  166.   end;
  167.  
  168.   THTML = class
  169.   public
  170.     class function isLinkPrefix(s: String): boolean;
  171.     class function isEntity(s: String): boolean;
  172.     class function isUnsafeHtmlElement(s: String): boolean;
  173.     class function isHtmlBlockElement(s: String): boolean;
  174.   end;
  175.  
  176.   TDecorator = class
  177.   private
  178.   public
  179.     procedure openParagraph(out_: TStringBuilder); virtual;
  180.     procedure closeParagraph(out_: TStringBuilder); virtual;
  181.  
  182.     procedure openBlockQuote(out_: TStringBuilder); virtual;
  183.     procedure closeBlockQuote(out_: TStringBuilder); virtual;
  184.  
  185.     procedure openCodeBlock(out_: TStringBuilder); virtual;
  186.     procedure closeCodeBlock(out_: TStringBuilder); virtual;
  187.  
  188.     procedure openCodeSpan(out_: TStringBuilder); virtual;
  189.     procedure closeCodeSpan(out_: TStringBuilder); virtual;
  190.  
  191.     procedure openHeadline(out_: TStringBuilder; level: integer); virtual;
  192.     procedure closeHeadline(out_: TStringBuilder; level: integer); virtual;
  193.  
  194.     procedure openStrong(out_: TStringBuilder); virtual;
  195.     procedure closeStrong(out_: TStringBuilder); virtual;
  196.  
  197.     procedure openEmphasis(out_: TStringBuilder); virtual;
  198.     procedure closeEmphasis(out_: TStringBuilder); virtual;
  199.  
  200.     procedure openSuper(out_: TStringBuilder); virtual;
  201.     procedure closeSuper(out_: TStringBuilder); virtual;
  202.  
  203.     procedure openOrderedList(out_: TStringBuilder); virtual;
  204.     procedure closeOrderedList(out_: TStringBuilder); virtual;
  205.  
  206.     procedure openUnOrderedList(out_: TStringBuilder); virtual;
  207.     procedure closeUnOrderedList(out_: TStringBuilder); virtual;
  208.  
  209.     procedure openListItem(out_: TStringBuilder); virtual;
  210.     procedure closeListItem(out_: TStringBuilder); virtual;
  211.  
  212.     procedure horizontalRuler(out_: TStringBuilder); virtual;
  213.  
  214.     procedure openLink(out_: TStringBuilder); virtual;
  215.     procedure closeLink(out_: TStringBuilder); virtual;
  216.  
  217.     procedure openImage(out_: TStringBuilder); virtual;
  218.     procedure closeImage(out_: TStringBuilder); virtual;
  219.   end;
  220.  
  221.   TSpanEmitter = class
  222.   public
  223.     procedure emitSpan(out_: TStringBuilder; content: String); virtual; abstract;
  224.   end;
  225.  
  226.   TBlockEmitter = class
  227.   public
  228.     procedure emitBlock(out_: TStringBuilder; lines: TStringList; meta: String); virtual; abstract;
  229.   end;
  230.  
  231.   TConfiguration = class
  232.   private
  233.     Fdecorator: TDecorator;
  234.     FsafeMode: boolean;
  235.     FallowSpacesInFencedDelimiters: boolean;
  236.     FforceExtendedProfile: boolean;
  237.     FcodeBlockEmitter: TBlockEmitter;
  238.     FpanicMode: boolean;
  239.     FspecialLinkEmitter: TSpanEmitter;
  240.   public
  241.     Constructor Create(safe : boolean);
  242.     Destructor Destroy; override;
  243.  
  244.     property safeMode: boolean read FsafeMode write FsafeMode;
  245.     property panicMode: boolean read FpanicMode write FpanicMode;
  246.     property decorator: TDecorator read Fdecorator write Fdecorator;
  247.     property codeBlockEmitter: TBlockEmitter read FcodeBlockEmitter write FcodeBlockEmitter;
  248.     property forceExtendedProfile: boolean read FforceExtendedProfile write FforceExtendedProfile;
  249.     property allowSpacesInFencedDelimiters: boolean read FallowSpacesInFencedDelimiters write FallowSpacesInFencedDelimiters;
  250.     property specialLinkEmitter: TSpanEmitter read FspecialLinkEmitter write FspecialLinkEmitter;
  251.   end;
  252.  
  253.   TLineType = (
  254.     // Empty line. */
  255.     ltEMPTY,
  256.     // Undefined content. */
  257.     ltOTHER,
  258.     // A markdown headline. */
  259.     ltHEADLINE, ltHEADLINE1, ltHEADLINE2,
  260.     // A code block line. */
  261.     ltCODE,
  262.     // A list. */
  263.     ltULIST, ltOLIST,
  264.     // A block quote. */
  265.     ltBQUOTE,
  266.     // A horizontal ruler. */
  267.     ltHR,
  268.     // Start of a XML block. */
  269.     ltXML,
  270.     // Fenced code block start/end */
  271.     ltFENCED_CODE);
  272.  
  273.   TLine = class
  274.   private
  275.     FXmlEndLine: TLine;
  276.     FPrevEmpty: boolean;
  277.     FPrevious: TLine;
  278.     FPosition: integer;
  279.     FValue: string;
  280.     FIsEmpty: boolean;
  281.     FTrailing: integer;
  282.     FNextEmpty: boolean;
  283.     FLeading: integer;
  284.     FNext: TLine;
  285.     function countChars(ch: char): integer;
  286.     function countCharsStart(ch: char; allowSpaces: boolean): integer;
  287.     function readXMLComment(firstLine: TLine; start: integer): integer;
  288.     function checkHTML(): boolean;
  289.  
  290.   public
  291.     Constructor Create;
  292.     Destructor Destroy; Override;
  293.  
  294.     // Current cursor position.
  295.     property position: integer read FPosition write FPosition;
  296.     // Leading and trailing spaces.
  297.     property leading: integer read FLeading write FLeading;
  298.     property trailing: integer read FTrailing write FTrailing;
  299.     // Is this line empty?
  300.     property isEmpty: boolean read FIsEmpty write FIsEmpty;
  301.     // This line's value.
  302.     property value: string read FValue write FValue;
  303.     // Previous and next line.
  304.     property previous: TLine read FPrevious write FPrevious;
  305.     property next: TLine read FNext write FNext;
  306.  
  307.     // Is previous/next line empty?
  308.     property prevEmpty: boolean read FPrevEmpty write FPrevEmpty;
  309.     property nextEmpty: boolean read FNextEmpty write FNextEmpty;
  310.  
  311.     // Final line of a XML block.
  312.     property xmlEndLine: TLine read FXmlEndLine write FXmlEndLine;
  313.  
  314.     procedure Init;
  315.     procedure InitLeading;
  316.     function skipSpaces: boolean;
  317.     function readUntil(chend: TSysCharSet): String;
  318.     procedure setEmpty;
  319.     function getLineType(configuration: TConfiguration): TLineType;
  320.     function stripID: String;
  321.  
  322.   end;
  323.  
  324.   TLinkRef = class
  325.   private
  326.     FLink: String;
  327.     FTitle: String;
  328.     FIsAbbrev: boolean;
  329.   public
  330.     Constructor Create(link, title: String; isAbbrev: boolean);
  331.  
  332.     property link: String read FLink write FLink;
  333.     property title: String read FTitle write FTitle;
  334.     property isAbbrev: boolean read FIsAbbrev write FIsAbbrev;
  335.   end;
  336.  
  337.   TBlockType = (
  338.     // Unspecified. Used for root block and list items without paragraphs.
  339.     btNONE,
  340.     // A block quote.
  341.     btBLOCKQUOTE,
  342.     // A code block.
  343.     btCODE,
  344.     // A fenced code block.
  345.     btFENCED_CODE,
  346.     // A headline.
  347.     btHEADLINE,
  348.     // A list item.
  349.     btLIST_ITEM,
  350.     // An ordered list.
  351.     btORDERED_LIST,
  352.     // A paragraph.
  353.     btPARAGRAPH,
  354.     // A horizontal ruler.
  355.     btRULER,
  356.     // An unordered list.
  357.     btUNORDERED_LIST,
  358.     // A XML block.
  359.     btXML);
  360.  
  361.   TBlock = class
  362.   private
  363.     FType: TBlockType;
  364.     FId: String;
  365.     FBlocks: TBlock;
  366.     FBlockTail: TBlock;
  367.     FLines: TLine;
  368.     FLineTail: TLine;
  369.     FHlDepth: integer;
  370.     FNext: TBlock;
  371.     FMeta: String;
  372.  
  373.     procedure AppendLine(line: TLine);
  374.     function split(line: TLine): TBlock;
  375.     procedure removeListIndent(config: TConfiguration);
  376.     function removeLeadingEmptyLines: boolean;
  377.     procedure removeTrailingEmptyLines;
  378.     procedure transfromHeadline;
  379.     procedure expandListParagraphs;
  380.     function hasLines: boolean;
  381.     procedure removeSurroundingEmptyLines;
  382.     procedure removeBlockQuotePrefix;
  383.     procedure removeLine(line: TLine);
  384.   public
  385.     Constructor Create;
  386.     Destructor Destroy; Override;
  387.  
  388.     // This block's type.
  389.     property type_: TBlockType read FType write FType;
  390.  
  391.     property lines: TLine read FLines;
  392.     property lineTail: TLine read FLineTail;
  393.  
  394.     // child blocks.
  395.     property blocks: TBlock read FBlocks;
  396.     property blockTail: TBlock read FBlockTail;
  397.  
  398.     // Next block.
  399.     property next: TBlock read FNext write FNext;
  400.     // Depth of headline BlockType.
  401.     property hlDepth: integer read FHlDepth write FHlDepth;
  402.     // ID for headlines and list items
  403.     property id: String read FId write FId;
  404.     // Block meta information
  405.     property meta: String read FMeta write FMeta;
  406.  
  407.   end;
  408.  
  409.   TMarkToken = (
  410.     // No token.
  411.     mtNONE,
  412.     // &#x2a;
  413.     mtEM_STAR, // x*x
  414.     // _
  415.     mtEM_UNDERSCORE, // x_x
  416.     // &#x2a;&#x2a;
  417.     mtSTRONG_STAR, // x**x
  418.     // __
  419.     mtSTRONG_UNDERSCORE, // x__x
  420.     // `
  421.     mtCODE_SINGLE, // `
  422.     // ``
  423.     mtCODE_DOUBLE, // ``
  424.     // [
  425.     mtLINK, // [
  426.     // &lt;
  427.     mtHTML, // <
  428.     // ![
  429.     mtIMAGE, // ![
  430.     // &amp;
  431.     mtENTITY, // &
  432.     // \
  433.     mtESCAPE, // \x
  434.     // Extended: ^
  435.     mtSUPER, // ^
  436.     // Extended: (C)
  437.     mtX_COPY, // (C)
  438.     // Extended: (R)
  439.     mtX_REG, // (R)
  440.     // Extended: (TM)
  441.     mtX_TRADE, // (TM)
  442.     // Extended: &lt;&lt;
  443.     mtX_LAQUO, // <<
  444.     // Extended: >>
  445.     mtX_RAQUO, // >>
  446.     // Extended: --
  447.     mtX_NDASH, // --
  448.     // Extended: ---
  449.     mtX_MDASH, // ---
  450.     // Extended: &#46;&#46;&#46;
  451.     mtX_HELLIP, // ...
  452.     // Extended: "x
  453.     mtX_RDQUO, // "
  454.     // Extended: x"
  455.     mtX_LDQUO, // "
  456.     // [[
  457.     mtX_LINK_OPEN, // [[
  458.     // ]]
  459.     mtX_LINK_CLOSE // ]]
  460.     );
  461.  
  462.   // Emitter class responsible for generating HTML output.
  463.   TEmitter = class
  464.   private
  465.     linkRefs: TStringList;
  466.     FConfig: TConfiguration;
  467.     FuseExtensions: boolean;
  468.     procedure emitCodeLines(out_: TStringBuilder; lines: TLine; meta: String; removeIndent: boolean);
  469.     procedure emitRawLines(out_: TStringBuilder; lines: TLine);
  470.     procedure emitMarkedLines(out_: TStringBuilder; lines: TLine);
  471.     function findToken(s: String; start: integer; token: TMarkToken): integer;
  472.     function getToken(s: String; position: integer): TMarkToken;
  473.     function checkLink(out_: TStringBuilder; s: String; start: integer; token: TMarkToken): integer;
  474.     function recursiveEmitLine(out_: TStringBuilder; s: String; start: integer; token: TMarkToken): integer;
  475.     function checkHTML(out_: TStringBuilder; s: String; start: integer): integer;
  476.     class function checkEntity(out_: TStringBuilder; s: String; start: integer): integer;
  477.     class function whitespaceToSpace(c: char): char;
  478.   public
  479.     Constructor Create(config: TConfiguration);
  480.     Destructor Destroy; override;
  481.  
  482.     procedure addLinkRef(key: String; linkRef: TLinkRef);
  483.     procedure emit(out_: TStringBuilder; root: TBlock);
  484.     procedure emitLines(out_: TStringBuilder; block: TBlock);
  485.  
  486.   end;
  487.  
  488.   TMarkdownDaringFireball = class(TMarkdownProcessor)
  489.   private
  490.     FConfig: TConfiguration;
  491.     Femitter: TEmitter;
  492.     FuseExtensions: boolean;
  493.     function readLines(reader : TReader): TBlock;
  494.     procedure initListBlock(root: TBlock);
  495.     procedure recurse(root: TBlock; listMode: boolean);
  496.  
  497.   protected
  498.     function GetAllowUnSafe: boolean; override;
  499.     procedure SetAllowUnSafe(const value: boolean); override;
  500.   public
  501.     Constructor Create;
  502.     Destructor Destroy; override;
  503.  
  504.     function process(source: String): String; override;
  505.  
  506.     property config: TConfiguration read FConfig;
  507.   end;
  508.  
  509. implementation
  510.  
  511. Function StringsContains(Const aNames: Array Of String; Const sName: String): boolean;
  512. var
  513.   i: integer;
  514. Begin
  515.   for i := 0 to length(aNames) - 1 do
  516.     if sName <> aNames[i] then
  517.       exit(true);
  518.   result := false;
  519. End;
  520.  
  521. function StringToShortString(const S: String) : ShortString;
  522. var
  523.   i : integer;
  524. begin
  525.   SetLength(result, min(s.Length, 255));
  526.   for i := 1 to length(result) do
  527.     result[i] := AnsiChar(s[i]);
  528. end;
  529.  
  530.  
  531. function StringToEnum(ATypeInfo: PTypeInfo; const AStr: String; defValue: integer): integer;
  532. var
  533.   LTypeData: PTypeData;
  534.   LPChar: PAnsiChar;
  535.   LValue: ShortString;
  536. begin
  537.   LValue := StringToShortString(AStr);
  538.  
  539.   if ATypeInfo^.Kind = tkEnumeration then
  540.   begin
  541.     LTypeData := GetTypeData(ATypeInfo);
  542.     if LTypeData^.MinValue <> 0 then
  543.       exit(defValue);
  544.     LPChar := @LTypeData^.NameList[0];
  545.     result := 0;
  546.     while (result <= LTypeData^.MaxValue) and (ShortString(pointer(LPChar)^) <> LValue) do
  547.     begin
  548.       inc(LPChar, ord(LPChar^) + 1); // move to next string
  549.       inc(result);
  550.     end;
  551.     if result > LTypeData^.MaxValue then
  552.       exit(defValue);
  553.   end
  554.   else
  555.     exit(defValue);
  556. end;
  557.  
  558. { TMarkdownDaringFireball }
  559.  
  560. constructor TMarkdownDaringFireball.Create;
  561. begin
  562.   inherited Create;
  563.   FConfig := TConfiguration.Create(true);
  564.   Femitter := TEmitter.Create(config);
  565. end;
  566.  
  567. destructor TMarkdownDaringFireball.Destroy;
  568. begin
  569.   FConfig.Free;
  570.   Femitter.Free;
  571.   inherited;
  572. end;
  573.  
  574. function TMarkdownDaringFireball.GetAllowUnSafe: boolean;
  575. begin
  576.   result := not FConfig.safeMode;
  577. end;
  578.  
  579. procedure TMarkdownDaringFireball.initListBlock(root: TBlock);
  580. var
  581.   line: TLine;
  582.   t: TLineType;
  583. begin
  584.   line := root.lines;
  585.   line := line.next;
  586.   while (line <> nil) do
  587.   begin
  588.     t := line.getLineType(FConfig);
  589.     if ((t = ltOLIST) or (t = ltULIST) or (not line.isEmpty and (line.prevEmpty and (line.leading = 0) and not((t = ltOLIST) or (t = ltULIST))))) then
  590.       root.split(line.previous).type_ := btLIST_ITEM;
  591.     line := line.next;
  592.   end;
  593.   root.split(root.lineTail).type_ := btLIST_ITEM;
  594. end;
  595.  
  596. function TMarkdownDaringFireball.process(source: String): String;
  597. var
  598.   out_: TStringBuilder;
  599.   parent, block: TBlock;
  600.   rdr : TReader;
  601. begin
  602.   FuseExtensions := config.forceExtendedProfile;
  603.   rdr := TReader.Create(source);
  604.   try
  605.     out_ := TStringBuilder.Create;
  606.     try
  607.       parent := readLines(rdr);
  608.       try
  609.         parent.removeSurroundingEmptyLines;
  610.         recurse(parent, false);
  611.         block := parent.blocks;
  612.         while (block <> nil) do
  613.         begin
  614.           Femitter.emit(out_, block);
  615.           block := block.next;
  616.         end;
  617.         result := out_.ToString;
  618.       finally
  619.         parent.Free;
  620.       end;
  621.     finally
  622.       out_.Free;
  623.     end;
  624.   finally
  625.     rdr.Free;
  626.   end;
  627. end;
  628.  
  629. function TMarkdownDaringFireball.readLines(reader : TReader): TBlock;
  630. var
  631.   block: TBlock;
  632.   sb: TStringBuilder;
  633.   c, ch: char;
  634.   position, np: integer;
  635.   eol, isLinkRef, lineAdded: boolean;
  636.   lastLinkRef, lr: TLinkRef;
  637.   line: TLine;
  638.   id, link, comment: String;
  639. begin
  640.   block := TBlock.Create;
  641.   sb := TStringBuilder.Create;
  642.   try
  643.     c := reader.read();
  644.     lastLinkRef := nil;
  645.     while (c <> #0) do
  646.     begin
  647.       sb.Clear;
  648.       position := 0;
  649.       eol := false;
  650.       while (not eol) do
  651.       begin
  652.         case c of
  653.           #0:
  654.             eol := true;
  655.           #10:
  656.             begin
  657.               c := reader.read();
  658.               if (c = #13) then
  659.                 c := reader.read();
  660.               eol := true;
  661.             end;
  662.           #13:
  663.             begin
  664.               c := reader.read();
  665.               if (c = #10) then
  666.                 c := reader.read();
  667.               eol := true;
  668.             end;
  669.           #9:
  670.             begin
  671.               np := position + (4 - (position and 3));
  672.               while (position < np) do
  673.               begin
  674.                 sb.append(' ');
  675.                 inc(position);
  676.               end;
  677.               c := reader.read();
  678.             end;
  679.         else
  680.           if (c <> '<') or (not FConfig.panicMode) then
  681.           begin
  682.             inc(position);
  683.             sb.append(c);
  684.           end
  685.           else
  686.           begin
  687.             inc(position, 4);
  688.             sb.append('&lt;');
  689.           end;
  690.           c := reader.read();
  691.         end;
  692.       end;
  693.  
  694.       lineAdded := false;
  695.       line := TLine.Create;
  696.       try
  697.         line.value := sb.ToString();
  698.         line.Init();
  699.  
  700.         // Check for link definitions
  701.         isLinkRef := false;
  702.         id := '';
  703.         link := '';
  704.         comment := '';
  705.         if (not line.isEmpty) and (line.leading < 4) and (line.value[1 + line.leading] = '[') then
  706.         begin
  707.           line.position := line.leading + 1;
  708.           // Read ID up to ']'
  709.           id := line.readUntil([']']);
  710.           // Is ID valid and are there any more characters?
  711.           if (id <> '') and (line.position + 2 < Length(line.value)) then
  712.           begin
  713.             // Check for ':' ([...]:...)
  714.             if (line.value[1 + line.position + 1] = ':') then
  715.             begin
  716.               line.position := line.position + 2;
  717.               line.skipSpaces();
  718.               // Check for link syntax
  719.               if (line.value[1 + line.position] = '<') then
  720.               begin
  721.                 line.position := line.position + 1;
  722.                 link := line.readUntil(['>']);
  723.                 line.position := line.position + 1;
  724.               end
  725.               else
  726.                 link := line.readUntil([' ', #10]);
  727.  
  728.               // Is link valid?
  729.               if (link <> '') then
  730.               begin
  731.                 // Any non-whitespace characters following?
  732.                 if (line.skipSpaces()) then
  733.                 begin
  734.                   ch := line.value[1 + line.position];
  735.                   // Read comment
  736.                   if (ch = '"') or (ch = '''') or (ch = '(') then
  737.                   begin
  738.                     line.position := line.position + 1;
  739.                     if ch = '(' then
  740.                       comment := line.readUntil([')'])
  741.                     else
  742.                       comment := line.readUntil([ch]);
  743.                     // Valid linkRef only if comment is valid
  744.                     if (comment <> '') then
  745.                       isLinkRef := true;
  746.                   end;
  747.                 end
  748.                 else
  749.                   isLinkRef := true;
  750.               end;
  751.             end;
  752.           end;
  753.         end;
  754.  
  755.         if (isLinkRef) then
  756.         begin
  757.           if (LowerCase(id) = '$profile$') then
  758.           begin
  759.             FuseExtensions := LowerCase(link) = 'extended';
  760.             Femitter.FuseExtensions := FuseExtensions;
  761.             lastLinkRef := nil;
  762.           end
  763.           else
  764.           begin
  765.             // Store linkRef and skip line
  766.             lr := TLinkRef.Create(link, comment, (comment <> '') and (Length(link) = 1) and (link[1 + 1] = '*'));
  767.             Femitter.addLinkRef(id, lr);
  768.             if (comment = '') then
  769.               lastLinkRef := lr;
  770.           end;
  771.         end
  772.         else
  773.         begin
  774.           comment := '';
  775.           // Check for multi-line linkRef
  776.           if (not line.isEmpty and (lastLinkRef <> nil)) then
  777.           begin
  778.             line.position := line.leading;
  779.             ch := line.value[1 + line.position];
  780.             if (ch = '"') or (ch = '''') or (ch = '(') then
  781.             begin
  782.               line.position := line.position + 1;
  783.               if ch = '(' then
  784.                 comment := line.readUntil([')'])
  785.               else
  786.                 comment := line.readUntil([ch]);
  787.             end;
  788.             if (comment <> '') then
  789.               lastLinkRef.title := comment;
  790.             lastLinkRef := nil;
  791.           end;
  792.  
  793.           // No multi-line linkRef, store line
  794.           if (comment = '') then
  795.           begin
  796.             line.position := 0;
  797.             block.AppendLine(line);
  798.             lineAdded := true;
  799.           end;
  800.         end;
  801.       finally
  802.         if not lineAdded then
  803.           line.Free;
  804.       end;
  805.     end;
  806.     result := block;
  807.   finally
  808.     sb.Free;
  809.   end;
  810. end;
  811.  
  812. procedure TMarkdownDaringFireball.recurse(root: TBlock; listMode: boolean);
  813. var
  814.   block, list: TBlock;
  815.   line: TLine;
  816.   type_, t: TLineType;
  817.   wasEmpty: boolean;
  818.   bt: TBlockType;
  819. begin
  820.   line := root.lines;
  821.   if (listMode) then
  822.   begin
  823.     root.removeListIndent(FConfig);
  824.     if (FuseExtensions and (root.lines <> nil) and (root.lines.getLineType(FConfig) <> ltCODE)) then
  825.       root.id := root.lines.stripID();
  826.   end;
  827.  
  828.   while (line <> nil) and line.isEmpty do
  829.     line := line.next;
  830.   if (line = nil) then
  831.     exit;
  832.  
  833.   while (line <> nil) do
  834.   begin
  835.     type_ := line.getLineType(FConfig);
  836.     case type_ of
  837.       ltOTHER:
  838.         begin
  839.           wasEmpty := line.prevEmpty;
  840.           while (line <> nil) and (not line.isEmpty) do
  841.           begin
  842.             t := line.getLineType(FConfig);
  843.             if (listMode or FuseExtensions) and (t in [ltOLIST, ltULIST]) then
  844.               break;
  845.             if (FuseExtensions and (t in [ltCODE, ltFENCED_CODE])) then
  846.               break;
  847.             if (t in [ltHEADLINE, ltHEADLINE1, ltHEADLINE2, ltHR, ltBQUOTE, ltXML]) then
  848.               break;
  849.             line := line.next;
  850.           end;
  851.  
  852.           if (line <> nil) and not line.isEmpty then
  853.           begin
  854.             if (listMode and not wasEmpty) then
  855.               bt := btNONE
  856.             else
  857.               bt := btPARAGRAPH;
  858.             if line = nil then
  859.               root.split(root.lineTail).type_ := bt
  860.             else
  861.               root.split(line.previous).type_ := bt;
  862.             root.removeLeadingEmptyLines();
  863.           end
  864.           else
  865.           begin
  866.             if (listMode and ((line = nil) or (not line.isEmpty)) and not wasEmpty) then
  867.               bt := btNONE
  868.             else
  869.               bt := btPARAGRAPH;
  870.             root.removeLeadingEmptyLines();
  871.             if (line <> nil) then
  872.               root.split(line.previous).type_ := bt
  873.             else
  874.               root.split(root.lineTail).type_ := bt;
  875.           end;
  876.           line := root.lines;
  877.         end;
  878.       ltCODE:
  879.         begin
  880.           while (line <> nil) and (line.isEmpty or (line.leading > 3)) do
  881.             line := line.next;
  882.           if (line <> nil) then
  883.             block := root.split(line.previous)
  884.           else
  885.             block := root.split(root.lineTail);
  886.           block.type_ := btCODE;
  887.           block.removeSurroundingEmptyLines();
  888.         end;
  889.       ltXML:
  890.         begin
  891.           if (line.previous <> nil) then
  892.             // FIXME ... this looks wrong
  893.             root.split(line.previous);
  894.           root.split(line.xmlEndLine).type_ := btXML;
  895.           root.removeLeadingEmptyLines();
  896.           line := root.lines;
  897.         end;
  898.       ltBQUOTE:
  899.         begin
  900.           while (line <> nil) do
  901.           begin
  902.             if (not line.isEmpty and (line.prevEmpty and (line.leading = 0) and (line.getLineType(FConfig) <> ltBQUOTE))) then
  903.               break;
  904.             line := line.next;
  905.           end;
  906.           if line <> nil then
  907.             block := root.split(line.previous)
  908.           else
  909.             block := root.split(root.lineTail);
  910.           block.type_ := btBLOCKQUOTE;
  911.           block.removeSurroundingEmptyLines();
  912.           block.removeBlockQuotePrefix();
  913.           recurse(block, false);
  914.           line := root.lines;
  915.         end;
  916.       ltHR:
  917.         begin
  918.           if (line.previous <> nil) then
  919.             // FIXME ... this looks wrong
  920.             root.split(line.previous);
  921.           root.split(line).type_ := btRULER;
  922.           root.removeLeadingEmptyLines();
  923.           line := root.lines;
  924.         end;
  925.       ltFENCED_CODE:
  926.         begin
  927.           line := line.next;
  928.           while (line <> nil) do
  929.           begin
  930.             if (line.getLineType(FConfig) = ltFENCED_CODE) then
  931.               break;
  932.             // TODO ... is this really necessary? Maybe add a special flag?
  933.             line := line.next;
  934.           end;
  935.           if (line <> nil) then
  936.             line := line.next;
  937.           if line <> nil then
  938.             block := root.split(line.previous)
  939.           else
  940.             block := root.split(root.lineTail);
  941.           block.type_ := btFENCED_CODE;
  942.           block.meta := TUtils.getMetaFromFence(block.lines.value);
  943.           block.lines.setEmpty();
  944.           if (block.lineTail.getLineType(FConfig) = ltFENCED_CODE) then
  945.             block.lineTail.setEmpty();
  946.           block.removeSurroundingEmptyLines();
  947.         end;
  948.       ltHEADLINE, ltHEADLINE1, ltHEADLINE2:
  949.         begin
  950.           if (line.previous <> nil) then
  951.             root.split(line.previous);
  952.           if (type_ <> ltHEADLINE) then
  953.             line.next.setEmpty();
  954.           block := root.split(line);
  955.           block.type_ := btHEADLINE;
  956.           if (type_ <> ltHEADLINE) then
  957.             if type_ = ltHEADLINE1 then
  958.               block.hlDepth := 1
  959.             else
  960.               block.hlDepth := 2;
  961.           if (FuseExtensions) then
  962.             block.id := block.lines.stripID();
  963.           block.transfromHeadline();
  964.           root.removeLeadingEmptyLines();
  965.           line := root.lines;
  966.         end;
  967.       ltOLIST, ltULIST:
  968.         begin
  969.           while (line <> nil) do
  970.           begin
  971.             t := line.getLineType(FConfig);
  972.             if (not line.isEmpty and (line.prevEmpty and (line.leading = 0) and (not(t = type_)))) then
  973.               break;
  974.             line := line.next;
  975.           end;
  976.           if line <> nil then
  977.             list := root.split(line.previous)
  978.           else
  979.             list := root.split(root.lineTail);
  980.           if type_ = ltOLIST then
  981.             list.type_ := btORDERED_LIST
  982.           else
  983.             list.type_ := btUNORDERED_LIST;
  984.           list.lines.prevEmpty := false;
  985.           list.lineTail.nextEmpty := false;
  986.           list.removeSurroundingEmptyLines();
  987.           list.lineTail.nextEmpty := false;
  988.           list.lines.prevEmpty := list.lineTail.nextEmpty;
  989.           initListBlock(list);
  990.           block := list.blocks;
  991.           while (block <> nil) do
  992.           begin
  993.             recurse(block, true);
  994.             block := block.next;
  995.           end;
  996.           list.expandListParagraphs();
  997.         end
  998.     else
  999.       line := line.next;
  1000.     end;
  1001.   end;
  1002. end;
  1003.  
  1004. procedure TMarkdownDaringFireball.SetAllowUnSafe(const value: boolean);
  1005. begin
  1006.   FConfig.safeMode := not value;
  1007. end;
  1008.  
  1009. { TLine }
  1010.  
  1011. constructor TLine.Create;
  1012. begin
  1013.   inherited;
  1014.   FIsEmpty := true;
  1015. end;
  1016.  
  1017. destructor TLine.Destroy;
  1018. begin
  1019.   FNext.Free;
  1020.   inherited;
  1021. end;
  1022.  
  1023. { TConfiguration }
  1024.  
  1025. constructor TConfiguration.Create(safe : boolean);
  1026. begin
  1027.   inherited Create;
  1028.   FallowSpacesInFencedDelimiters := true;
  1029.   Fdecorator := TDecorator.Create;
  1030.   FsafeMode := safe;
  1031. end;
  1032.  
  1033. destructor TConfiguration.Destroy;
  1034. begin
  1035.   FcodeBlockEmitter.Free;
  1036.   Fdecorator.Free;
  1037.   FspecialLinkEmitter.Free;
  1038.   inherited;
  1039. end;
  1040.  
  1041. { TDecorator }
  1042.  
  1043. procedure TDecorator.openParagraph(out_: TStringBuilder);
  1044. begin
  1045.   out_.append('<p>');
  1046. end;
  1047.  
  1048. procedure TDecorator.closeParagraph(out_: TStringBuilder);
  1049. begin
  1050.   out_.append('</p>'#10);
  1051. end;
  1052.  
  1053. procedure TDecorator.openBlockQuote(out_: TStringBuilder);
  1054. begin
  1055.   out_.append('<blockquote>');
  1056. end;
  1057.  
  1058. procedure TDecorator.closeBlockQuote(out_: TStringBuilder);
  1059. begin
  1060.   out_.append('</blockquote>'#10);
  1061. end;
  1062.  
  1063. procedure TDecorator.openCodeBlock(out_: TStringBuilder);
  1064. begin
  1065.   out_.append('<pre><code>');
  1066. end;
  1067.  
  1068. procedure TDecorator.closeCodeBlock(out_: TStringBuilder);
  1069. begin
  1070.   out_.append('</code></pre>'#10);
  1071. end;
  1072.  
  1073. procedure TDecorator.openCodeSpan(out_: TStringBuilder);
  1074. begin
  1075.   out_.append('<code>');
  1076. end;
  1077.  
  1078. procedure TDecorator.closeCodeSpan(out_: TStringBuilder);
  1079. begin
  1080.   out_.append('</code>');
  1081. end;
  1082.  
  1083. procedure TDecorator.openHeadline(out_: TStringBuilder; level: integer);
  1084. begin
  1085.   out_.append('<h');
  1086.   out_.append(level);
  1087. end;
  1088.  
  1089. procedure TDecorator.closeHeadline(out_: TStringBuilder; level: integer);
  1090. begin
  1091.   out_.append('</h');
  1092.   out_.append(level);
  1093.   out_.append('>'#10);
  1094. end;
  1095.  
  1096. procedure TDecorator.openStrong(out_: TStringBuilder);
  1097. begin
  1098.   out_.append('<strong>');
  1099. end;
  1100.  
  1101. procedure TDecorator.closeStrong(out_: TStringBuilder);
  1102. begin
  1103.   out_.append('</strong>');
  1104. end;
  1105.  
  1106. procedure TDecorator.openEmphasis(out_: TStringBuilder);
  1107. begin
  1108.   out_.append('<em>');
  1109. end;
  1110.  
  1111. procedure TDecorator.closeEmphasis(out_: TStringBuilder);
  1112. begin
  1113.   out_.append('</em>');
  1114. end;
  1115.  
  1116. procedure TDecorator.openSuper(out_: TStringBuilder);
  1117. begin
  1118.   out_.append('<sup>');
  1119. end;
  1120.  
  1121. procedure TDecorator.closeSuper(out_: TStringBuilder);
  1122. begin
  1123.   out_.append('</sup>');
  1124. end;
  1125.  
  1126. procedure TDecorator.openOrderedList(out_: TStringBuilder);
  1127. begin
  1128.   out_.append('<ol>'#10);
  1129. end;
  1130.  
  1131. procedure TDecorator.closeOrderedList(out_: TStringBuilder);
  1132. begin
  1133.   out_.append('</ol>'#10);
  1134. end;
  1135.  
  1136. procedure TDecorator.openUnOrderedList(out_: TStringBuilder);
  1137. begin
  1138.   out_.append('<ul>'#10);
  1139. end;
  1140.  
  1141. procedure TDecorator.closeUnOrderedList(out_: TStringBuilder);
  1142. begin
  1143.   out_.append('</ul>'#10);
  1144. end;
  1145.  
  1146. procedure TDecorator.openListItem(out_: TStringBuilder);
  1147. begin
  1148.   out_.append('<li');
  1149. end;
  1150.  
  1151. procedure TDecorator.closeListItem(out_: TStringBuilder);
  1152. begin
  1153.   out_.append('</li>'#10);
  1154. end;
  1155.  
  1156. procedure TDecorator.horizontalRuler(out_: TStringBuilder);
  1157. begin
  1158.   out_.append('<hr/>'#10);
  1159. end;
  1160.  
  1161. procedure TDecorator.openLink(out_: TStringBuilder);
  1162. begin
  1163.   out_.append('<a');
  1164. end;
  1165.  
  1166. procedure TDecorator.closeLink(out_: TStringBuilder);
  1167. begin
  1168.   out_.append('</a>');
  1169. end;
  1170.  
  1171. procedure TDecorator.openImage(out_: TStringBuilder);
  1172. begin
  1173.   out_.append('<img');
  1174. end;
  1175.  
  1176. procedure TDecorator.closeImage(out_: TStringBuilder);
  1177. begin
  1178.   out_.append(' />');
  1179. end;
  1180.  
  1181. { TEmitter }
  1182.  
  1183. constructor TEmitter.Create(config: TConfiguration);
  1184. begin
  1185.   inherited Create;
  1186.   FConfig := config;
  1187.   linkRefs := TStringList.Create;
  1188.   linkRefs.Sorted := true;
  1189.   linkRefs.Duplicates := dupError;
  1190. end;
  1191.  
  1192. destructor TEmitter.Destroy;
  1193. var
  1194.   i : integer;
  1195. begin
  1196.   for i := 0 to linkRefs.Count - 1 do
  1197.     linkRefs.Objects[i].Free;
  1198.   linkRefs.Free;
  1199.   inherited;
  1200. end;
  1201.  
  1202. procedure TEmitter.addLinkRef(key: String; linkRef: TLinkRef);
  1203. var
  1204.   k : String;
  1205.   i : integer;
  1206. begin
  1207.   k := LowerCase(key);
  1208.   if linkRefs.find(k, i) then
  1209.   begin
  1210.     linkRefs.Objects[i].Free;
  1211.     linkRefs.Objects[i] := linkRef;
  1212.   end
  1213.   else
  1214.     linkRefs.AddObject(k, linkRef);
  1215. end;
  1216.  
  1217. procedure TEmitter.emit(out_: TStringBuilder; root: TBlock);
  1218. var
  1219.   block: TBlock;
  1220. begin
  1221.   root.removeSurroundingEmptyLines();
  1222.  
  1223.   case root.type_ of
  1224.     btRULER:
  1225.       begin
  1226.         FConfig.decorator.horizontalRuler(out_);
  1227.         exit;
  1228.       end;
  1229.     btNONE, btXML:
  1230.       ; // nothing
  1231.     btHEADLINE:
  1232.       begin
  1233.         FConfig.decorator.openHeadline(out_, root.hlDepth);
  1234.         if (FuseExtensions and (root.id <> '')) then
  1235.         begin
  1236.           out_.append(' id="');
  1237.           TUtils.appendCode(out_, root.id, 0, Length(root.id));
  1238.           out_.append('"');
  1239.         end;
  1240.         out_.append('>');
  1241.       end;
  1242.     btPARAGRAPH:
  1243.       FConfig.decorator.openParagraph(out_);
  1244.     btCODE, btFENCED_CODE:
  1245.       if (FConfig.codeBlockEmitter = nil) then
  1246.         FConfig.decorator.openCodeBlock(out_);
  1247.     btBLOCKQUOTE:
  1248.       FConfig.decorator.openBlockQuote(out_);
  1249.     btUNORDERED_LIST:
  1250.       FConfig.decorator.openUnOrderedList(out_);
  1251.     btORDERED_LIST:
  1252.       FConfig.decorator.openOrderedList(out_);
  1253.     btLIST_ITEM:
  1254.       begin
  1255.         FConfig.decorator.openListItem(out_);
  1256.         if (FuseExtensions and (root.id <> '')) then
  1257.         begin
  1258.           out_.append(' id="');
  1259.           TUtils.appendCode(out_, root.id, 0, Length(root.id));
  1260.           out_.append('"');
  1261.         end;
  1262.         out_.append('>');
  1263.       end;
  1264.   end;
  1265.  
  1266.   if (root.hasLines()) then
  1267.     emitLines(out_, root)
  1268.   else
  1269.   begin
  1270.     block := root.blocks;
  1271.     while (block <> nil) do
  1272.     begin
  1273.       emit(out_, block);
  1274.       block := block.next;
  1275.     end;
  1276.   end;
  1277.  
  1278.   case (root.type_) of
  1279.     btRULER, btNONE, btXML:
  1280.       ; // nothing
  1281.     btHEADLINE:
  1282.       FConfig.decorator.closeHeadline(out_, root.hlDepth);
  1283.     btPARAGRAPH:
  1284.       FConfig.decorator.closeParagraph(out_);
  1285.     btCODE, btFENCED_CODE:
  1286.       if (FConfig.codeBlockEmitter = nil) then
  1287.         FConfig.decorator.closeCodeBlock(out_);
  1288.     btBLOCKQUOTE:
  1289.       FConfig.decorator.closeBlockQuote(out_);
  1290.     btUNORDERED_LIST:
  1291.       FConfig.decorator.closeUnOrderedList(out_);
  1292.     btORDERED_LIST:
  1293.       FConfig.decorator.closeOrderedList(out_);
  1294.     btLIST_ITEM:
  1295.       FConfig.decorator.closeListItem(out_);
  1296.   end;
  1297. end;
  1298.  
  1299. procedure TEmitter.emitLines(out_: TStringBuilder; block: TBlock);
  1300. begin
  1301.   case (block.type_) of
  1302.     btCODE:
  1303.       emitCodeLines(out_, block.lines, block.meta, true);
  1304.     btFENCED_CODE:
  1305.       emitCodeLines(out_, block.lines, block.meta, false);
  1306.     btXML:
  1307.       emitRawLines(out_, block.lines);
  1308.   else
  1309.     emitMarkedLines(out_, block.lines);
  1310.   end;
  1311. end;
  1312.  
  1313. function TEmitter.findToken(s: String; start: integer; token: TMarkToken): integer;
  1314. var
  1315.   position: integer;
  1316. begin
  1317.   position := start;
  1318.   while (position < Length(s)) do
  1319.   begin
  1320.     if getToken(s, position) = token then
  1321.       exit(position);
  1322.     inc(position);
  1323.   end;
  1324.   result := -1;
  1325. end;
  1326.  
  1327. function TEmitter.checkLink(out_: TStringBuilder; s: String; start: integer; token: TMarkToken): integer;
  1328. var
  1329.   isAbbrev, useLt, hasLink: boolean;
  1330.   position, oldPos, i: integer;
  1331.   temp: TStringBuilder;
  1332.   name, link, comment, id: String;
  1333.   lr: TLinkRef;
  1334. begin
  1335.   isAbbrev := false;
  1336.   if (token = mtLINK) then
  1337.     position := start + 1
  1338.   else
  1339.     position := start + 2;
  1340.   temp := TStringBuilder.Create;
  1341.   try
  1342.     position := TUtils.readMdLinkId(temp, s, position);
  1343.     if (position < start) then
  1344.       exit(-1);
  1345.     name := temp.ToString();
  1346.     link := '';
  1347.     hasLink := false;
  1348.     comment := '';
  1349.     oldPos := position;
  1350.     inc(position);
  1351.     position := TUtils.skipSpaces(s, position);
  1352.     if (position < start) then
  1353.     begin
  1354.       if linkRefs.find(LowerCase(name), i) then
  1355.       begin
  1356.         lr := TLinkRef(linkRefs.Objects[i]);
  1357.         isAbbrev := lr.isAbbrev;
  1358.         link := lr.link;
  1359.         hasLink := true;
  1360.         comment := lr.title;
  1361.         position := oldPos;
  1362.       end
  1363.       else
  1364.         exit(-1);
  1365.     end
  1366.     else if (s[1 + position] = '(') then
  1367.     begin
  1368.       inc(position);
  1369.       position := TUtils.skipSpaces(s, position);
  1370.       if (position < start) then
  1371.         exit(-1);
  1372.       temp.Clear;
  1373.       useLt := s[1 + position] = '<';
  1374.       if useLt then
  1375.         position := TUtils.readUntil(temp, s, position + 1, '>')
  1376.       else
  1377.         position := TUtils.readMdLink(temp, s, position);
  1378.       if (position < start) then
  1379.         exit(-1);
  1380.       if (useLt) then
  1381.         inc(position);
  1382.       link := temp.ToString();
  1383.       hasLink := true;
  1384.       if (s[1 + position] = ' ') then
  1385.       begin
  1386.         position := TUtils.skipSpaces(s, position);
  1387.         if (position > start) and (s[1 + position] = '"') then
  1388.         begin
  1389.           inc(position);
  1390.           temp.Clear;
  1391.           position := TUtils.readUntil(temp, s, position, '"');
  1392.           if (position < start) then
  1393.             exit(-1);
  1394.           comment := temp.ToString();
  1395.           inc(position);
  1396.           position := TUtils.skipSpaces(s, position);
  1397.           if (position = -1) then
  1398.             exit(-1);
  1399.         end;
  1400.       end;
  1401.       if (s[1 + position] <> ')') then
  1402.         exit(-1);
  1403.     end
  1404.     else if (s[1 + position] = '[') then
  1405.     begin
  1406.       inc(position);
  1407.       temp.Clear;
  1408.       position := TUtils.readRawUntil(temp, s, position, ']');
  1409.       if (position < start) then
  1410.         exit(-1);
  1411.       if temp.length > 0 then
  1412.         id := temp.ToString()
  1413.       else
  1414.         id := name;
  1415.       if linkRefs.find(LowerCase(id), i) then
  1416.       begin
  1417.         lr := TLinkRef(linkRefs.Objects[i]);
  1418.         link := lr.link;
  1419.         hasLink := true;
  1420.         comment := lr.title;
  1421.       end
  1422.     end
  1423.     else
  1424.     begin
  1425.       if linkRefs.find(LowerCase(name), i) then
  1426.       begin
  1427.         lr := TLinkRef(linkRefs.Objects[i]);
  1428.         isAbbrev := lr.isAbbrev;
  1429.         link := lr.link;
  1430.         hasLink := true;
  1431.         comment := lr.title;
  1432.         position := oldPos;
  1433.       end
  1434.       else
  1435.         exit(-1);
  1436.     end;
  1437.     if (not hasLink) then
  1438.       exit(-1);
  1439.  
  1440.     if (token = mtLINK) then
  1441.     begin
  1442.       if (isAbbrev) and (comment <> '') then
  1443.       begin
  1444.         if (not FuseExtensions) then
  1445.           exit(-1);
  1446.         out_.append('<abbr title:="');
  1447.         TUtils.appendValue(out_, comment, 0, Length(comment));
  1448.         out_.append('">');
  1449.         recursiveEmitLine(out_, name, 0, mtNONE);
  1450.         out_.append('</abbr>');
  1451.       end
  1452.       else
  1453.       begin
  1454.         FConfig.decorator.openLink(out_);
  1455.         out_.append(' href="');
  1456.         TUtils.appendValue(out_, link, 0, Length(link));
  1457.         out_.append('"');
  1458.         if (comment <> '') then
  1459.         begin
  1460.           out_.append(' title="');
  1461.           TUtils.appendValue(out_, comment, 0, Length(comment));
  1462.           out_.append('"');
  1463.         end;
  1464.         out_.append('>');
  1465.         recursiveEmitLine(out_, name, 0, mtNONE);
  1466.         FConfig.decorator.closeLink(out_);
  1467.       end
  1468.     end
  1469.     else
  1470.     begin
  1471.       FConfig.decorator.openImage(out_);
  1472.       out_.append(' src="');
  1473.       TUtils.appendValue(out_, link, 0, Length(link));
  1474.       out_.append('" alt="');
  1475.       TUtils.appendValue(out_, name, 0, Length(name));
  1476.       out_.append('"');
  1477.       if (comment <> '') then
  1478.       begin
  1479.         out_.append(' title="');
  1480.         TUtils.appendValue(out_, comment, 0, Length(comment));
  1481.         out_.append('"');
  1482.       end;
  1483.       FConfig.decorator.closeImage(out_);
  1484.     end;
  1485.     result := position;
  1486.   finally
  1487.     temp.Free;
  1488.   end;
  1489. end;
  1490.  
  1491. function TEmitter.checkHTML(out_: TStringBuilder; s: String; start: integer): integer;
  1492. var
  1493.   temp: TStringBuilder;
  1494.   position: integer;
  1495.   link: String;
  1496. begin
  1497.   temp := TStringBuilder.Create();
  1498.   try
  1499.     // Check for auto links
  1500.     temp.Clear;
  1501.     position := TUtils.readUntil(temp, s, start + 1, [':', ' ', '>', #10]);
  1502.     if (position <> -1) and (s[1 + position] = ':') and (THTML.isLinkPrefix(temp.ToString())) then
  1503.     begin
  1504.       position := TUtils.readUntil(temp, s, position, ['>']);
  1505.       if (position <> -1) then
  1506.       begin
  1507.         link := temp.ToString();
  1508.         FConfig.decorator.openLink(out_);
  1509.         out_.append(' href="');
  1510.         TUtils.appendValue(out_, link, 0, Length(link));
  1511.         out_.append('">');
  1512.         TUtils.appendValue(out_, link, 0, Length(link));
  1513.         FConfig.decorator.closeLink(out_);
  1514.         exit(position);
  1515.       end;
  1516.     end;
  1517.  
  1518.     // Check for mailto auto link
  1519.     temp.Clear;
  1520.     position := TUtils.readUntil(temp, s, start + 1, ['@', ' ', '>', #10]);
  1521.     if (position <> -1) and (s[1 + position] = '@') then
  1522.     begin
  1523.       position := TUtils.readUntil(temp, s, position, '>');
  1524.       if (position <> -1) then
  1525.       begin
  1526.         link := temp.ToString();
  1527.         FConfig.decorator.openLink(out_);
  1528.         out_.append(' href="');
  1529.         TUtils.appendMailto(out_, 'mailto:', 0, 7);
  1530.         TUtils.appendMailto(out_, link, 0, Length(link));
  1531.         out_.append('">');
  1532.         TUtils.appendMailto(out_, link, 0, Length(link));
  1533.         FConfig.decorator.closeLink(out_);
  1534.         exit(position);
  1535.       end;
  1536.     end;
  1537.  
  1538.     // Check for inline html
  1539.     if (start + 2 < Length(s)) then
  1540.     begin
  1541.       temp.Clear;
  1542.       exit(TUtils.readXML(out_, s, start, FConfig.safeMode));
  1543.     end;
  1544.  
  1545.     result := -1;
  1546.   finally
  1547.     temp.Free;
  1548.   end;
  1549. end;
  1550.  
  1551. function isLetterOrDigit(c : char) : boolean;
  1552. begin
  1553.   {$IFDEF FPC}
  1554.   result := c in ['A'..'Z', 'a'..'z', '0'..'9'];
  1555.   {$ELSE}
  1556.   result := c.isLetterOrDigit();
  1557.   {$ENDIF}
  1558. end;
  1559.  
  1560. function isDigit(c : char) : boolean;
  1561. begin
  1562.   {$IFDEF FPC}
  1563.   result := c in ['0'..'9'];
  1564.   {$ELSE}
  1565.   result := c.isDigit();
  1566.   {$ENDIF}
  1567. end;
  1568.  
  1569. function isWhitespace(c : char) : boolean;
  1570. begin
  1571.   {$IFDEF FPC}
  1572.   result := c in [' ', #9, #10, #13];
  1573.   {$ELSE}
  1574.   result := c.isWhitespace();
  1575.   {$ENDIF}
  1576. end;
  1577.  
  1578. class function TEmitter.checkEntity(out_: TStringBuilder; s: String; start: integer): integer;
  1579. var
  1580.   position, i: integer;
  1581.   c: char;
  1582. begin
  1583.   position := TUtils.readUntil(out_, s, start, ';');
  1584.   if (position < 0) or (out_.length < 3) then
  1585.     exit(-1);
  1586.   if (out_[1] = '#') then
  1587.   begin
  1588.     if (out_[2] = 'x') or (out_[2] = 'X') then
  1589.     begin
  1590.       if (out_.length < 4) then
  1591.         exit(-1);
  1592.       for i := 3 to out_.length do
  1593.       begin
  1594.         c := out_[i];
  1595.         if ((c < '0') or (c > '9')) and (((c < 'a') or (c > 'f')) and ((c < 'A') or (c > 'F'))) then
  1596.           exit(-1);
  1597.       end;
  1598.     end
  1599.     else
  1600.     begin
  1601.       for i := 2 to out_.length do
  1602.       begin
  1603.         c := out_[i];
  1604.         if (c < '0') or (c > '9') then
  1605.           exit(-1);
  1606.       end;
  1607.     end;
  1608.     out_.append(';');
  1609.   end
  1610.   else
  1611.   begin
  1612.     for i := 1 to out_.length - 1 do
  1613.     begin
  1614.       c := out_[i]; // zero based
  1615.       if (not isLetterOrDigit(c)) then
  1616.         exit(-1);
  1617.     end;
  1618.     out_.append(';');
  1619.     if THTML.isEntity(out_.ToString()) then
  1620.       exit(position)
  1621.     else
  1622.       exit(-1);
  1623.   end;
  1624.  
  1625.   result := position;
  1626. end;
  1627.  
  1628. function TEmitter.recursiveEmitLine(out_: TStringBuilder; s: String; start: integer; token: TMarkToken): integer;
  1629. var
  1630.   position, a, b: integer;
  1631.   temp: TStringBuilder;
  1632.   mt: TMarkToken;
  1633. begin
  1634.   position := start;
  1635.   temp := TStringBuilder.Create();
  1636.   try
  1637.     while (position < Length(s)) do
  1638.     begin
  1639.       mt := getToken(s, position);
  1640.       if (token <> mtNONE) and ((mt = token) or ((token = mtEM_STAR) and (mt = mtSTRONG_STAR)) or ((token = mtEM_UNDERSCORE) and (mt = mtSTRONG_UNDERSCORE))) then
  1641.         exit(position);
  1642.  
  1643.       case mt of
  1644.         mtIMAGE, mtLINK:
  1645.           begin
  1646.             temp.Clear;
  1647.             b := checkLink(temp, s, position, mt);
  1648.             if (b > 0) then
  1649.             begin
  1650.               out_.append(temp);
  1651.               position := b;
  1652.             end
  1653.             else
  1654.               out_.append(s[1 + position]);
  1655.           end;
  1656.         mtEM_STAR, mtEM_UNDERSCORE:
  1657.           begin
  1658.             temp.Clear;
  1659.             b := recursiveEmitLine(temp, s, position + 1, mt);
  1660.             if (b > 0) then
  1661.             begin
  1662.               FConfig.decorator.openEmphasis(out_);
  1663.               out_.append(temp);
  1664.               FConfig.decorator.closeEmphasis(out_);
  1665.               position := b;
  1666.             end
  1667.             else
  1668.               out_.append(s[1 + position]);
  1669.           end;
  1670.         mtSTRONG_STAR, mtSTRONG_UNDERSCORE:
  1671.           begin
  1672.             temp.Clear;
  1673.             b := recursiveEmitLine(temp, s, position + 2, mt);
  1674.             if (b > 0) then
  1675.             begin
  1676.               FConfig.decorator.openStrong(out_);
  1677.               out_.append(temp);
  1678.               FConfig.decorator.closeStrong(out_);
  1679.               position := b + 1;
  1680.             end
  1681.             else
  1682.               out_.append(s[1 + position]);
  1683.           end;
  1684.         mtSUPER:
  1685.           begin
  1686.             temp.Clear;
  1687.             b := recursiveEmitLine(temp, s, position + 1, mt);
  1688.             if (b > 0) then
  1689.             begin
  1690.               FConfig.decorator.openSuper(out_);
  1691.               out_.append(temp);
  1692.               FConfig.decorator.closeSuper(out_);
  1693.               position := b;
  1694.             end
  1695.             else
  1696.               out_.append(s[1 + position]);
  1697.           end;
  1698.         mtCODE_SINGLE, mtCODE_DOUBLE:
  1699.           begin
  1700.             if mt = mtCODE_DOUBLE then
  1701.               a := position + 2
  1702.             else
  1703.               a := position + 1;
  1704.             b := findToken(s, a, mt);
  1705.             if (b > 0) then
  1706.             begin
  1707.               if mt = mtCODE_DOUBLE then
  1708.                 position := b + 1
  1709.               else
  1710.                 position := b + 0;
  1711.               while (a < b) and (s[1 + a] = ' ') do
  1712.                 inc(a);
  1713.               if (a < b) then
  1714.               begin
  1715.                 while (s[1 + b - 1] = ' ') do
  1716.                   dec(b);
  1717.               end;
  1718.               FConfig.decorator.openCodeSpan(out_);
  1719.               TUtils.appendCode(out_, s, a, b);
  1720.               FConfig.decorator.closeCodeSpan(out_);
  1721.             end
  1722.             else
  1723.               out_.append(s[1 + position]);
  1724.           end;
  1725.         mtHTML:
  1726.           begin
  1727.             temp.Clear;
  1728.             b := checkHTML(temp, s, position);
  1729.             if (b > 0) then
  1730.             begin
  1731.               out_.append(temp);
  1732.               position := b;
  1733.             end
  1734.             else
  1735.               out_.append('&lt;');
  1736.           end;
  1737.         mtENTITY:
  1738.           begin
  1739.             temp.Clear;
  1740.             b := checkEntity(temp, s, position);
  1741.             if (b > 0) then
  1742.             begin
  1743.               out_.append(temp);
  1744.               position := b;
  1745.             end
  1746.             else
  1747.               out_.append('&amp;');
  1748.           end;
  1749.         mtX_LINK_OPEN:
  1750.           begin
  1751.             temp.Clear;
  1752.             b := recursiveEmitLine(temp, s, position + 2, mtX_LINK_CLOSE);
  1753.             if (b > 0) and (FConfig.specialLinkEmitter <> nil) then
  1754.             begin
  1755.               FConfig.specialLinkEmitter.emitSpan(out_, temp.ToString());
  1756.               position := b + 1;
  1757.             end
  1758.             else
  1759.               out_.append(s[1 + position]);
  1760.           end;
  1761.         mtX_COPY:
  1762.           begin
  1763.             out_.append('&copy;');
  1764.             inc(position, 2);
  1765.           end;
  1766.         mtX_REG:
  1767.           begin
  1768.             out_.append('&reg;');
  1769.             inc(position, 2);
  1770.           end;
  1771.         mtX_TRADE:
  1772.           begin
  1773.             out_.append('&trade;');
  1774.             inc(position, 3);
  1775.           end;
  1776.         mtX_NDASH:
  1777.           begin
  1778.             out_.append('&ndash;');
  1779.             inc(position);
  1780.           end;
  1781.         mtX_MDASH:
  1782.           begin
  1783.             out_.append('&mdash;');
  1784.             inc(position, 2);
  1785.           end;
  1786.         mtX_HELLIP:
  1787.           begin
  1788.             out_.append('&hellip;');
  1789.             inc(position, 2);
  1790.           end;
  1791.         mtX_LAQUO:
  1792.           begin
  1793.             out_.append('&laquo;');
  1794.             inc(position);
  1795.           end;
  1796.         mtX_RAQUO:
  1797.           begin
  1798.             out_.append('&raquo;');
  1799.             inc(position);
  1800.           end;
  1801.         mtX_RDQUO:
  1802.           out_.append('&rdquo;');
  1803.         mtX_LDQUO:
  1804.           out_.append('&ldquo;');
  1805.         mtESCAPE:
  1806.           begin
  1807.             inc(position);
  1808.             out_.append(s[1 + position]);
  1809.           end;
  1810.         // $FALL-THROUGH$
  1811.       else
  1812.         out_.append(s[1 + position]);
  1813.       end;
  1814.       inc(position);
  1815.     end;
  1816.     result := -1;
  1817.   finally
  1818.     temp.Free;
  1819.   end;
  1820. end;
  1821.  
  1822. class function TEmitter.whitespaceToSpace(c: char): char;
  1823. begin
  1824.   if isWhitespace(c) then
  1825.     result := ' '
  1826.   else
  1827.     result := c;
  1828. end;
  1829.  
  1830. function TEmitter.getToken(s: String; position: integer): TMarkToken;
  1831. var
  1832.   c0, c, c1, c2, c3: char;
  1833. begin
  1834.  
  1835.   result := mtNONE;
  1836.   if (position > 0) then
  1837.     c0 := whitespaceToSpace(s[1 + position - 1])
  1838.   else
  1839.     c0 := ' ';
  1840.   c := whitespaceToSpace(s[1 + position]);
  1841.   if (position + 1 < Length(s)) then
  1842.     c1 := whitespaceToSpace(s[1 + position + 1])
  1843.   else
  1844.     c1 := ' ';
  1845.   if (position + 2 < Length(s)) then
  1846.     c2 := whitespaceToSpace(s[1 + position + 2])
  1847.   else
  1848.     c2 := ' ';
  1849.   if (position + 3 < Length(s)) then
  1850.     c3 := whitespaceToSpace(s[1 + position + 3])
  1851.   else
  1852.     c3 := ' ';
  1853.  
  1854.   case (c) of
  1855.     '*':
  1856.       if (c1 = '*') then
  1857.       begin
  1858.         if (c0 <> ' ') or (c2 <> ' ') then
  1859.           exit(mtSTRONG_STAR)
  1860.         else
  1861.           exit(mtEM_STAR);
  1862.       end
  1863.       else if (c0 <> ' ') or (c1 <> ' ') then
  1864.         exit(mtEM_STAR)
  1865.       else
  1866.         exit(mtNONE);
  1867.     '_':
  1868.       if (c1 = '_') then
  1869.       begin
  1870.         if (c0 <> ' ') or (c2 <> ' ') then
  1871.           exit(mtSTRONG_UNDERSCORE)
  1872.         else
  1873.           exit(mtEM_UNDERSCORE);
  1874.       end
  1875.       else if (FuseExtensions) then
  1876.       begin
  1877.         if (isLetterOrDigit(c0)) and (c0 <> '_') and (isLetterOrDigit(c1)) then
  1878.           exit(mtNONE)
  1879.         else
  1880.           exit(mtEM_UNDERSCORE);
  1881.       end
  1882.       else if (c0 <> ' ') or (c1 <> ' ') then
  1883.         exit(mtEM_UNDERSCORE)
  1884.       else
  1885.         exit(mtNONE);
  1886.     '!':
  1887.       if (c1 = '[') then
  1888.         exit(mtIMAGE)
  1889.       else
  1890.         exit(mtNONE);
  1891.     '[':
  1892.       if (FuseExtensions) and (c1 = '[') then
  1893.         exit(mtX_LINK_OPEN)
  1894.       else
  1895.         exit(mtLINK);
  1896.     ']':
  1897.       if (FuseExtensions) and (c1 = ']') then
  1898.         exit(mtX_LINK_CLOSE)
  1899.       else
  1900.         exit(mtNONE);
  1901.     '`':
  1902.       if (c1 = '`') then
  1903.         exit(mtCODE_DOUBLE)
  1904.       else
  1905.         exit(mtCODE_SINGLE);
  1906.     '\':
  1907.       if CharInSet(c1, ['\', '[', ']', '(', ')', '{', '}', '#', '"', '''', '.', '>', '<', '*', '+', '-', '_', '!', '`', '~', '^']) then
  1908.         exit(mtESCAPE)
  1909.       else
  1910.         exit(mtNONE);
  1911.     '<':
  1912.       if (FuseExtensions) and (c1 = '<') then
  1913.         exit(mtX_LAQUO)
  1914.       else
  1915.         exit(mtHTML);
  1916.     '&':
  1917.       exit(mtENTITY);
  1918.   else
  1919.     if (FuseExtensions) then
  1920.       case (c) of
  1921.         '-':
  1922.           if (c1 = '-') and (c2 = '-') then
  1923.             exit(mtX_MDASH)
  1924.           else
  1925.             exit(mtX_NDASH);
  1926.         '^':
  1927.           if (c0 = '^') or (c1 = '^') then
  1928.             exit(mtNONE)
  1929.           else
  1930.             exit(mtSUPER);
  1931.         '>':
  1932.           if (c1 = '>') then
  1933.             exit(mtX_RAQUO);
  1934.         '.':
  1935.           if (c1 = '.') and (c2 = '.') then
  1936.             exit(mtX_HELLIP);
  1937.         '(':
  1938.           begin
  1939.             if (c1 = 'C') and (c2 = ')') then
  1940.               exit(mtX_COPY);
  1941.             if (c1 = 'R') and (c2 = ')') then
  1942.               exit(mtX_REG);
  1943.             if (c1 = 'T') and (c2 = 'M') and (c3 = ')') then
  1944.               exit(mtX_TRADE);
  1945.           end;
  1946.         '"':
  1947.           begin
  1948.             if (not isLetterOrDigit(c0)) and (c1 <> ' ') then
  1949.               exit(mtX_LDQUO);
  1950.             if (c0 <> ' ') and (not isLetterOrDigit(c1)) then
  1951.               exit(mtX_RDQUO);
  1952.             exit(mtNONE);
  1953.           end;
  1954.       end;
  1955.   end;
  1956. end;
  1957.  
  1958. procedure TEmitter.emitMarkedLines(out_: TStringBuilder; lines: TLine);
  1959. var
  1960.   s: TStringBuilder;
  1961.   line: TLine;
  1962. begin
  1963.   s := TStringBuilder.Create();
  1964.   try
  1965.     line := lines;
  1966.     while (line <> nil) do
  1967.     begin
  1968.       if (not line.isEmpty) then
  1969.       begin
  1970. //        s.append(line.value.substring(line.leading, line.value.length - line.trailing)); PSTfix
  1971.         s.Append( Copy(line.value, line.leading + 1, Length(line.value) - line.trailing));
  1972.         if (line.trailing >= 2) then
  1973.           s.append('<br />');
  1974.       end;
  1975.       if (line.next <> nil) then
  1976.         s.append(#10);
  1977.       line := line.next;
  1978.     end;
  1979.     recursiveEmitLine(out_, s.ToString(), 0, mtNONE);
  1980.   finally
  1981.     s.Free;
  1982.   end;
  1983. end;
  1984.  
  1985. procedure TEmitter.emitRawLines(out_: TStringBuilder; lines: TLine);
  1986. var
  1987.   s: String;
  1988.   line: TLine;
  1989.   temp: TStringBuilder;
  1990.   position, t: integer;
  1991. begin
  1992.   line := lines;
  1993.   if (FConfig.safeMode) then
  1994.   begin
  1995.     temp := TStringBuilder.Create();
  1996.     try
  1997.       while (line <> nil) do
  1998.       begin
  1999.         if (not line.isEmpty) then
  2000.           temp.append(line.value);
  2001.         temp.append(#10);
  2002.         line := line.next;
  2003.       end;
  2004.       s := temp.ToString();
  2005.       position := 0;
  2006.       while position < length(s) do
  2007.       begin
  2008.         if (s[1 + position] = '<') then
  2009.         begin
  2010.           temp.Clear;
  2011.           t := TUtils.readXML(temp, s, position, FConfig.safeMode);
  2012.           if (t <> -1) then
  2013.           begin
  2014.             out_.append(temp);
  2015.             position := t;
  2016.           end
  2017.           else
  2018.             out_.append(s[1 + position]);
  2019.         end
  2020.         else
  2021.           out_.append(s[1 + position]);
  2022.         inc(position);
  2023.       end
  2024.     finally
  2025.       temp.Free;
  2026.     end;
  2027.   end
  2028.   else
  2029.   begin
  2030.     while (line <> nil) do
  2031.     begin
  2032.       if (not line.isEmpty) then
  2033.         out_.append(line.value);
  2034.       out_.append(#10);
  2035.       line := line.next;
  2036.     end;
  2037.   end;
  2038. end;
  2039.  
  2040. procedure TEmitter.emitCodeLines(out_: TStringBuilder; lines: TLine; meta: String; removeIndent: boolean);
  2041. var
  2042.   line: TLine;
  2043.   list: TStringList;
  2044.   i, sp: integer;
  2045.   c: char;
  2046. begin
  2047.   line := lines;
  2048.   if (FConfig.codeBlockEmitter <> nil) then
  2049.   begin
  2050.     list := TStringList.Create;
  2051.     try
  2052.       while (line <> nil) do
  2053.       begin
  2054.         if (line.isEmpty) then
  2055.           list.add('')
  2056.         else if removeIndent then
  2057. //          list.add(line.value.substring(4)) P{STfix
  2058.           list.Add( Copy(line.value, 5))
  2059.         else
  2060.           list.add(line.value);
  2061.         line := line.next;
  2062.       end;
  2063.       FConfig.codeBlockEmitter.emitBlock(out_, list, meta);
  2064.     finally
  2065.       list.Free
  2066.     end
  2067.   end
  2068.   else
  2069.   begin
  2070.     while (line <> nil) do
  2071.     begin
  2072.       if (not line.isEmpty) then
  2073.       begin
  2074.         if removeIndent then
  2075.           sp := 4
  2076.         else
  2077.           sp := 0;
  2078.         for i := sp to Length(line.value) - 1 do
  2079.         begin
  2080.           c := line.value[1 + i];
  2081.           case c of
  2082.             '&':
  2083.               out_.append('&amp;');
  2084.             '<':
  2085.               out_.append('&lt;');
  2086.             '>':
  2087.               out_.append('&gt;');
  2088.           else
  2089.             out_.append(c);
  2090.           end;
  2091.         end;
  2092.       end;
  2093.       out_.append(#10);
  2094.       line := line.next;
  2095.     end;
  2096.   end;
  2097. end;
  2098.  
  2099. { TReader }
  2100.  
  2101. constructor TReader.Create(source: String);
  2102. begin
  2103.   inherited Create;
  2104.   FValue := source;
  2105.   FCursor := 0;
  2106. end;
  2107.  
  2108. function TReader.read: char;
  2109. begin
  2110.   inc(FCursor);
  2111.   if FCursor > Length(FValue) then
  2112.     result := #0
  2113.   else
  2114.     result := FValue[FCursor];
  2115. end;
  2116.  
  2117. { TUtils }
  2118.  
  2119. class function TUtils.skipSpaces(s: String; start: integer): integer;
  2120. var
  2121.   position: integer;
  2122. begin
  2123.   position := start;
  2124.   while (position < Length(s)) and ((s[1 + position] = ' ') or (s[1 + position] = #10)) do
  2125.     inc(position);
  2126.   if position < Length(s) then
  2127.     result := position
  2128.   else
  2129.     result := -1;
  2130. end;
  2131.  
  2132. class function TUtils.escape(out_: TStringBuilder; ch: char; position: integer): integer;
  2133. begin
  2134.   if CharInSet(ch, ['\', '[', ']', '(', ')', '{', '}', '#', '"', '''', '.', '>', '<', '*', '+', '-', '_', '!', '`', '^']) then
  2135.   begin
  2136.     out_.append(ch);
  2137.     result := position + 1;
  2138.   end
  2139.   else
  2140.   begin
  2141.     out_.append('\');
  2142.     result := position;
  2143.   end;
  2144. end;
  2145.  
  2146. class function TUtils.readUntil(out_: TStringBuilder; s: String; start: integer; cend: TSysCharSet): integer;
  2147. var
  2148.   position: integer;
  2149.   ch: char;
  2150. begin
  2151.   position := start;
  2152.   while (position < Length(s)) do
  2153.   begin
  2154.     ch := s[1 + position];
  2155.     if (ch = '\') and (position + 1 < Length(s)) then
  2156.       position := escape(out_, s[1 + position + 1], position)
  2157.     else
  2158.     begin
  2159.       if CharInSet(ch, cend) then
  2160.         break
  2161.       else
  2162.         out_.append(ch);
  2163.     end;
  2164.     inc(position);
  2165.   end;
  2166.   if position = Length(s) then
  2167.     result := -1
  2168.   else
  2169.     result := position;
  2170. end;
  2171.  
  2172. class function TUtils.readUntil(out_: TStringBuilder; s: String; start: integer; cend: char): integer;
  2173. var
  2174.   position: integer;
  2175.   ch: char;
  2176. begin
  2177.   position := start;
  2178.   while (position < Length(s)) do
  2179.   begin
  2180.     ch := s[1 + position];
  2181.     if (ch = '\') and (position + 1 < Length(s)) then
  2182.       position := escape(out_, s[1 + position + 1], position)
  2183.     else
  2184.     begin
  2185.       if (ch = cend) then
  2186.         break;
  2187.       out_.append(ch);
  2188.     end;
  2189.     inc(position);
  2190.   end;
  2191.   if position = Length(s) then
  2192.     result := -1
  2193.   else
  2194.     result := position;
  2195. end;
  2196.  
  2197. class function TUtils.readMdLink(out_: TStringBuilder; s: String; start: integer): integer;
  2198. var
  2199.   position, counter: integer;
  2200.   ch: char;
  2201.   endReached: boolean;
  2202. begin
  2203.   position := start;
  2204.   counter := 1;
  2205.   while (position < Length(s)) do
  2206.   begin
  2207.     ch := s[1 + position];
  2208.     if (ch = '\') and (position + 1 < Length(s)) then
  2209.       position := escape(out_, s[1 + position + 1], position)
  2210.     else
  2211.     begin
  2212.       endReached := false;
  2213.       case ch of
  2214.         '(':
  2215.           inc(counter);
  2216.         ' ':
  2217.           if (counter = 1) then
  2218.             endReached := true;
  2219.         ')':
  2220.           begin
  2221.             dec(counter);
  2222.             if (counter = 0) then
  2223.               endReached := true;
  2224.           end;
  2225.       end;
  2226.       if (endReached) then
  2227.         break;
  2228.       out_.append(ch);
  2229.     end;
  2230.     inc(position);
  2231.   end;
  2232.   if position = Length(s) then
  2233.     result := -1
  2234.   else
  2235.     result := position;
  2236. end;
  2237.  
  2238. class function TUtils.readMdLinkId(out_: TStringBuilder; s: String; start: integer): integer;
  2239. var
  2240.   position, counter: integer;
  2241.   ch: char;
  2242.   endReached: boolean;
  2243. begin
  2244.   position := start;
  2245.   counter := 1;
  2246.   while (position < Length(s)) do
  2247.   begin
  2248.     ch := s[1 + position];
  2249.     endReached := false;
  2250.     case ch of
  2251.       #10:
  2252.         out_.append(' ');
  2253.       '[':
  2254.         begin
  2255.           inc(counter);
  2256.           out_.append(ch);
  2257.         end;
  2258.       ']':
  2259.         begin
  2260.           dec(counter);
  2261.           if (counter = 0) then
  2262.             endReached := true
  2263.           else
  2264.             out_.append(ch);
  2265.         end;
  2266.     else
  2267.       out_.append(ch);
  2268.     end;
  2269.     if (endReached) then
  2270.       break;
  2271.     inc(position);
  2272.   end;
  2273.   if position = Length(s) then
  2274.     result := -1
  2275.   else
  2276.     result := position;
  2277. end;
  2278.  
  2279. class function TUtils.readRawUntil(out_: TStringBuilder; s: String; start: integer; cend: TSysCharSet): integer;
  2280. var
  2281.   position: integer;
  2282.   ch: char;
  2283. begin
  2284.   position := start;
  2285.   while (position < Length(s)) do
  2286.   begin
  2287.     ch := s[1 + position];
  2288.     if CharInSet(ch, cend) then
  2289.       break;
  2290.     out_.append(ch);
  2291.     inc(position);
  2292.   end;
  2293.   if position = Length(s) then
  2294.     result := -1
  2295.   else
  2296.     result := position;
  2297. end;
  2298.  
  2299. class function TUtils.readRawUntil(out_: TStringBuilder; s: String; start: integer; cend: char): integer;
  2300. var
  2301.   position: integer;
  2302.   ch: char;
  2303. begin
  2304.   position := start;
  2305.   while (position < Length(s)) do
  2306.   begin
  2307.     ch := s[1 + position];
  2308.     if (ch = cend) then
  2309.       break;
  2310.     out_.append(ch);
  2311.     inc(position);
  2312.   end;
  2313.   if position = Length(s) then
  2314.     result := -1
  2315.   else
  2316.     result := position;
  2317. end;
  2318.  
  2319. class function TUtils.readXMLUntil(out_: TStringBuilder; s: String; start: integer; cend: TSysCharSet): integer;
  2320. var
  2321.   position : integer;
  2322.   ch, stringChar: char;
  2323.   inString: boolean;
  2324. begin
  2325.   position := start;
  2326.   inString := false;
  2327.   stringChar := #0;
  2328.   while (position < Length(s)) do
  2329.   begin
  2330.     ch := s[1 + position];
  2331.     if (inString) then
  2332.     begin
  2333.       if (ch = '\') then
  2334.       begin
  2335.         out_.append(ch);
  2336.         inc(position);
  2337.         if (position < Length(s)) then
  2338.         begin
  2339.           out_.append(ch);
  2340.           inc(position);
  2341.         end;
  2342.         continue;
  2343.       end;
  2344.       if (ch = stringChar) then
  2345.       begin
  2346.         inString := false;
  2347.         out_.append(ch);
  2348.         inc(position);
  2349.         continue;
  2350.       end;
  2351.     end;
  2352.     if CharInSet(ch, ['"', '''']) then
  2353.     begin
  2354.       inString := true;
  2355.       stringChar := ch;
  2356.     end;
  2357.     if (not inString) then
  2358.     begin
  2359.       if CharInSet(ch, cend) then
  2360.         break;
  2361.     end;
  2362.     out_.append(ch);
  2363.     inc(position);
  2364.   end;
  2365.   if position = Length(s) then
  2366.     result := -1
  2367.   else
  2368.     result := position;
  2369. end;
  2370.  
  2371. class procedure TUtils.appendCode(out_: TStringBuilder; s: String; start: integer; e: integer);
  2372. var
  2373.   i: integer;
  2374.   c: char;
  2375. begin
  2376.   for i := start to e - 1 do
  2377.   begin
  2378.     c := s[1 + i];
  2379.     case c of
  2380.       '&':
  2381.         out_.append('&amp;');
  2382.       '<':
  2383.         out_.append('&lt;');
  2384.       '>':
  2385.         out_.append('&gt;');
  2386.     else
  2387.       out_.append(c);
  2388.     end;
  2389.   end;
  2390. end;
  2391.  
  2392. class procedure TUtils.appendValue(out_: TStringBuilder; s: String; start: integer; e: integer);
  2393. var
  2394.   i: integer;
  2395.   c: char;
  2396. begin
  2397.   for i := start to e - 1 do
  2398.   begin
  2399.     c := s[1 + i];
  2400.     case c of
  2401.       '&':
  2402.         out_.append('&amp;');
  2403.       '<':
  2404.         out_.append('&lt;');
  2405.       '>':
  2406.         out_.append('&gt;');
  2407.       '"':
  2408.         out_.append('&quot;');
  2409.       '''':
  2410.         out_.append('&apos;');
  2411.     else
  2412.       out_.append(c);
  2413.     end;
  2414.   end;
  2415. end;
  2416.  
  2417. class procedure TUtils.appendDecEntity(out_: TStringBuilder; value: char);
  2418. begin
  2419.   out_.append('&#');
  2420.   out_.append(IntToStr(ord(value)));
  2421.   out_.append(';');
  2422. end;
  2423.  
  2424. class procedure TUtils.appendHexEntity(out_: TStringBuilder; value: char);
  2425. begin
  2426.   out_.append('&#');
  2427.   out_.append(IntToHex(ord(value), 2));
  2428.   out_.append(';');
  2429. end;
  2430.  
  2431. class procedure TUtils.appendMailto(out_: TStringBuilder; s: String; start: integer; e: integer);
  2432. var
  2433.   i: integer;
  2434.   c: char;
  2435. begin
  2436.   for i := start to e - 1 do
  2437.   begin
  2438.     c := s[1 + i];
  2439.     if CharInSet(c, ['&', '<', '>', '"', '''']) then
  2440.       appendHexEntity(out_, c)
  2441.     else
  2442.       out_.append(c);
  2443.   end;
  2444. end;
  2445.  
  2446. class procedure TUtils.getXMLTag(out_: TStringBuilder; bin: TStringBuilder);
  2447. var
  2448.   position: integer;
  2449. begin
  2450.   position := 1;
  2451.   if (bin[1] = '/') then
  2452.     inc(position);
  2453.   while (isLetterOrDigit(bin[position])) do
  2454.   begin
  2455.     out_.append(bin[position]);
  2456.     inc(position)
  2457.   end;
  2458. end;
  2459.  
  2460. class procedure TUtils.getXMLTag(out_: TStringBuilder; s: String);
  2461. var
  2462.   position: integer;
  2463. begin
  2464.   if (1 + 1 > s.length) then
  2465.     exit;
  2466.  
  2467.   position := 1;
  2468.   if (s[1 + 1] = '/') then
  2469.     inc(position);
  2470.   while (isLetterOrDigit(s[1 + position])) do
  2471.   begin
  2472.     out_.append(s[1 + position]);
  2473.     inc(position)
  2474.   end;
  2475. end;
  2476.  
  2477. class function TUtils.readXML(out_: TStringBuilder; s: String; start: integer; safeMode: boolean): integer;
  2478. var
  2479.   position: integer;
  2480.   isCloseTag: boolean;
  2481.   temp: TStringBuilder;
  2482.   tag: String;
  2483. begin
  2484.   if (1 + start + 1) > s.length then
  2485.     exit(start);
  2486.  
  2487.   if (s[1 + start + 1] = '/') then
  2488.   begin
  2489.     isCloseTag := true;
  2490.     position := start + 2;
  2491.   end
  2492.   else if (s[1 + start + 1] = '!') then
  2493.   begin
  2494.     out_.append('<!');
  2495.     exit(start + 1);
  2496.   end
  2497.   else
  2498.   begin
  2499.     isCloseTag := false;
  2500.     position := start + 1;
  2501.   end;
  2502.  
  2503.   if (safeMode) then
  2504.   begin
  2505.     temp := TStringBuilder.Create();
  2506.     try
  2507.       position := readXMLUntil(temp, s, position, [' ', '/', '>']);
  2508.       if (position = -1) then
  2509.         exit(-1);
  2510. //      tag := temp.ToString().trim().ToLower; PSTFix
  2511.         tag := LowerCase( Trim( temp.ToString));
  2512.       if (THTML.isUnsafeHtmlElement(tag)) then
  2513.         out_.append('&lt;')
  2514.       else
  2515.         out_.append('<');
  2516.       if (isCloseTag) then
  2517.         out_.append('/');
  2518.       out_.append(temp);
  2519.     finally
  2520.       temp.Free;
  2521.     end;
  2522.   end
  2523.   else
  2524.   begin
  2525.     out_.append('<');
  2526.     if (isCloseTag) then
  2527.       out_.append('/');
  2528.     position := readXMLUntil(out_, s, position, [' ', '/', '>']);
  2529.   end;
  2530.   if (position = -1) then
  2531.     exit(-1);
  2532.   position := readXMLUntil(out_, s, position, ['/', '>']);
  2533.   if (position = -1) then
  2534.     exit(-1);
  2535.  
  2536.   if (s[1 + position] = '/') then
  2537.   begin
  2538.     out_.append(' /');
  2539.     position := readXMLUntil(out_, s, position + 1, ['>']);
  2540.     if (position = -1) then
  2541.       exit(-1);
  2542.   end;
  2543.  
  2544.   if (s[1 + position] = '>') then
  2545.   begin
  2546.     out_.append('>');
  2547.     exit(position);
  2548.   end;
  2549.   result := -1;
  2550. end;
  2551.  
  2552. class procedure TUtils.codeEncode(out_: TStringBuilder; value: String; offset: integer);
  2553. var
  2554.   i: integer;
  2555.   c: char;
  2556. begin
  2557.   for i := offset to Length(value) - 1 do
  2558.   begin
  2559.     c := value[1 + i];
  2560.     case c of
  2561.       '&':
  2562.         out_.append('&amp;');
  2563.       '<':
  2564.         out_.append('&lt;');
  2565.       '>':
  2566.         out_.append('&gt;');
  2567.     else
  2568.       out_.append(c);
  2569.     end;
  2570.   end;
  2571. end;
  2572.  
  2573. class function TUtils.getMetaFromFence(fenceLine: String): String;
  2574. var
  2575.   i: integer;
  2576.   c: char;
  2577. begin
  2578.   for i := 0 to Length(fenceLine) - 1 do
  2579.   begin
  2580.     c := fenceLine[1 + i];
  2581.     if (not isWhitespace(c)) and (c <> '`') and (c <> '~') then
  2582. //      exit(fenceLine.substring(i).trim()); PSTfix
  2583.       Exit(  Trim( Copy(fenceLine, i+1)));
  2584.   end;
  2585.   result := '';
  2586. end;
  2587.  
  2588. { THTML }
  2589.  
  2590. class function THTML.isHtmlBlockElement(s: String): boolean;
  2591. var
  2592.   ht: THTMLElement;
  2593. begin
  2594.   ht := THTMLElement(StringToEnum(TypeInfo(THTMLElement), 'he' + s, ord(heNONE)));
  2595.   result := ht in BLOCK_ELEMENTS;
  2596. end;
  2597.  
  2598. class function THTML.isLinkPrefix(s: String): boolean;
  2599. begin
  2600.   result := StringsContains(LINK_PREFIXES, s);
  2601. end;
  2602.  
  2603. class function THTML.isEntity(s: String): boolean;
  2604. begin
  2605.   result := StringsContains(ENTITY_NAMES, s);
  2606. end;
  2607.  
  2608. class function THTML.isUnsafeHtmlElement(s: String): boolean;
  2609. var
  2610.   ht: THTMLElement;
  2611. begin
  2612.   ht := THTMLElement(StringToEnum(TypeInfo(THTMLElement), s, ord(heNONE)));
  2613.   result := ht in UNSAFE_ELEMENTS;
  2614. end;
  2615.  
  2616. { TLine }
  2617.  
  2618. procedure TLine.Init();
  2619. begin
  2620.   FLeading := 0;
  2621.   while (leading < Length(value)) and (value[1 + leading] = ' ') do
  2622.     inc(FLeading);
  2623.  
  2624.   if (leading = Length(value)) then
  2625.     setEmpty()
  2626.   else
  2627.   begin
  2628.     isEmpty := false;
  2629.     trailing := 0;
  2630.     while (value[1 + Length(value) - trailing - 1] = ' ') do
  2631.       inc(FTrailing);
  2632.   end;
  2633. end;
  2634.  
  2635. procedure TLine.InitLeading();
  2636. begin
  2637.   FLeading := 0;
  2638.   while (leading < Length(value)) and (value[1 + leading] = ' ') do
  2639.     inc(FLeading);
  2640.   if (leading = Length(value)) then
  2641.     setEmpty();
  2642. end;
  2643.  
  2644. // TODO use Util#skipSpaces
  2645. function TLine.skipSpaces(): boolean;
  2646. begin
  2647.   while (position < Length(value)) and (value[1 + position] = ' ') do
  2648.     inc(FPosition);
  2649.   result := position < Length(value);
  2650. end;
  2651.  
  2652. // TODO use Util#readUntil
  2653. function TLine.readUntil(chend: TSysCharSet): String;
  2654. var
  2655.   sb: TStringBuilder;
  2656.   pos: integer;
  2657.   ch, c: char;
  2658. begin
  2659.   sb := TStringBuilder.Create();
  2660.   try
  2661.     pos := self.position;
  2662.     while (pos < Length(value)) do
  2663.     begin
  2664.       ch := value[1 + pos];
  2665.       if (ch = '\') and (pos + 1 < Length(value)) then
  2666.       begin
  2667.         c := value[1 + pos + 1];
  2668.         if CharInSet(c, ['\', '[', ']', '(', ')', '{', '}', '#', '"', '''', '.', '>', '*', '+', '-', '_', '!', '`', '~']) then
  2669.         begin
  2670.           sb.append(c);
  2671.           inc(pos);
  2672.         end
  2673.         else
  2674.         begin
  2675.           sb.append(ch);
  2676.           break;
  2677.         end;
  2678.       end
  2679.       else if CharInSet(ch, chend) then
  2680.         break
  2681.       else
  2682.         sb.append(ch);
  2683.       inc(pos);
  2684.     end;
  2685.  
  2686.     if (pos < Length(value)) then
  2687.       ch := value[1 + pos]
  2688.     else
  2689.       ch := #10;
  2690.     if CharInSet(ch, chend) then
  2691.     begin
  2692.       self.position := pos;
  2693.       result := sb.ToString();
  2694.     end
  2695.     else
  2696.       result := '';
  2697.   finally
  2698.     sb.Free;
  2699.   end;
  2700. end;
  2701.  
  2702. procedure TLine.setEmpty();
  2703. begin
  2704.   value := '';
  2705.   leading := 0;
  2706.   trailing := 0;
  2707.   isEmpty := true;
  2708.   if (previous <> nil) then
  2709.     previous.nextEmpty := true;
  2710.   if (next <> nil) then
  2711.     next.prevEmpty := true;
  2712. end;
  2713.  
  2714. function TLine.countChars(ch: char): integer;
  2715. var
  2716.   count, i: integer;
  2717.   c: char;
  2718. begin
  2719.   count := 0;
  2720.   for i := 0 to Length(value) - 1 do
  2721.   begin
  2722.     c := value[1 + i];
  2723.     if (c = ' ') then
  2724.       continue;
  2725.     if (c = ch) then
  2726.     begin
  2727.       inc(count);
  2728.       continue;
  2729.     end;
  2730.     count := 0;
  2731.     break;
  2732.   end;
  2733.   result := count;
  2734. end;
  2735.  
  2736. function TLine.countCharsStart(ch: char; allowSpaces: boolean): integer;
  2737. var
  2738.   count, i: integer;
  2739.   c: char;
  2740. begin
  2741.   count := 0;
  2742.   for i := 0 to Length(value) - 1 do
  2743.   begin
  2744.     c := value[1 + i];
  2745.     if (c = ' ') and (allowSpaces) then
  2746.     begin
  2747.       continue;
  2748.     end;
  2749.     if (c = ch) then
  2750.       inc(count)
  2751.     else
  2752.       break;
  2753.   end;
  2754.   result := count;
  2755. end;
  2756.  
  2757. function TLine.getLineType(configuration: TConfiguration): TLineType;
  2758. var
  2759.   i: integer;
  2760. begin
  2761.   if (isEmpty) then
  2762.     exit(ltEMPTY);
  2763.  
  2764.   if (leading > 3) then
  2765.     exit(ltCODE);
  2766.  
  2767.   if (value[1 + leading] = '#') then
  2768.     exit(ltHEADLINE);
  2769.  
  2770.   if (value[1 + leading] = '>') then
  2771.     exit(ltBQUOTE);
  2772.  
  2773.   if (configuration.forceExtendedProfile) then
  2774.   begin
  2775.     if (Length(value) - leading - trailing > 2) then
  2776.     begin
  2777.       if (value[1 + leading] = '`') and (countCharsStart('`', configuration.allowSpacesInFencedDelimiters) >= 3) then
  2778.         exit(ltFENCED_CODE);
  2779.       if (value[1 + leading] = '~') and (countCharsStart('~', configuration.allowSpacesInFencedDelimiters) >= 3) then
  2780.         exit(ltFENCED_CODE);
  2781.     end;
  2782.   end;
  2783.  
  2784.   if (Length(value) - leading - trailing > 2) and ((value[1 + leading] = '*') or (value[1 + leading] = '-') or (value[1 + leading] = '_')) then
  2785.   begin
  2786.     if (countChars(value[1 + leading]) >= 3) then
  2787.       exit(ltHR);
  2788.   end;
  2789.  
  2790.   if (Length(value) - leading >= 2) and (value[1 + leading + 1] = ' ') then
  2791.   begin
  2792.     if CharInSet(value[1 + leading], ['*', '-', '+']) then
  2793.       exit(ltULIST);
  2794.   end;
  2795.  
  2796.   if (Length(value) - leading >= 3) and (isDigit(value[1 + leading])) then
  2797.   begin
  2798.     i := leading + 1;
  2799.     while (i < Length(value)) and (isDigit(value[1 + i])) do
  2800.       inc(i);
  2801.     if (i + 1 < Length(value)) and (value[1 + i] = '.') and (value[1 + i + 1] = ' ') then
  2802.       exit(ltOLIST);
  2803.   end;
  2804.  
  2805.   if (value[1 + leading] = '<') then
  2806.   begin
  2807.     if (checkHTML()) then
  2808.       exit(ltXML);
  2809.   end;
  2810.  
  2811.   if (next <> nil) and (not next.isEmpty) then
  2812.   begin
  2813.     if ((next.value[1 + 0] = '-')) and ((next.countChars('-') > 0)) then
  2814.       exit(ltHEADLINE2);
  2815.     if ((next.value[1 + 0] = '=')) and ((next.countChars('=') > 0)) then
  2816.       exit(ltHEADLINE1);
  2817.   end;
  2818.  
  2819.   exit(ltOTHER);
  2820. end;
  2821.  
  2822. function TLine.readXMLComment(firstLine: TLine; start: integer): integer;
  2823. var
  2824.   line: TLine;
  2825.   pos: integer;
  2826. begin
  2827.   line := firstLine;
  2828.   if (start + 3 < Length(line.value)) then
  2829.   begin
  2830.     if (line.value[1 + 2] = '-') and (line.value[1 + 3] = '-') then
  2831.     begin
  2832.       pos := start + 4;
  2833.       while (line <> nil) do
  2834.       begin
  2835.         while (pos < Length(line.value)) and (line.value[1 + pos] <> '-') do
  2836.           inc(pos);
  2837.         if (pos = Length(line.value)) then
  2838.         begin
  2839.           line := line.next;
  2840.           pos := 0;
  2841.         end
  2842.         else
  2843.         begin
  2844.           if (pos + 2 < Length(line.value)) then
  2845.           begin
  2846.             if (line.value[1 + pos + 1] = '-') and (line.value[1 + pos + 2] = '>') then
  2847.             begin
  2848.               xmlEndLine := line;
  2849.               exit(pos + 3);
  2850.             end;
  2851.           end;
  2852.           inc(pos);
  2853.         end;
  2854.       end;
  2855.     end;
  2856.   end;
  2857.   exit(-1);
  2858. end;
  2859.  
  2860. // FIXME ... hack
  2861. function TLine.stripID(): String;
  2862. var
  2863.   p, start: integer;
  2864.   found: boolean;
  2865.   id: String;
  2866. begin
  2867.   if (isEmpty or (value[1 + Length(value) - trailing - 1] <> '}')) then
  2868.     exit('');
  2869.  
  2870.   p := leading;
  2871.   found := false;
  2872.   while (p < Length(value)) and (not found) do
  2873.   begin
  2874.     case value[1 + p] of
  2875.       '\':
  2876.         begin
  2877.           if (p + 1 < Length(value)) then
  2878.           begin
  2879.             if (value[1 + p + 1]) = '{' then
  2880.             begin
  2881.               inc(p);
  2882.               break;
  2883.             end;
  2884.           end;
  2885.           inc(p);
  2886.           break;
  2887.         end;
  2888.       '{':
  2889.         begin
  2890.           found := true;
  2891.           break;
  2892.         end
  2893.     else
  2894.       begin
  2895.         inc(p);
  2896.         break;
  2897.       end;
  2898.     end;
  2899.   end;
  2900.  
  2901.   if (found) then
  2902.   begin
  2903.     if (p + 1 < Length(value)) and (value[1 + p + 1] = '#') then
  2904.     begin
  2905.       start := p + 2;
  2906.       p := start;
  2907.       found := false;
  2908.       while (p < Length(value)) and (not found) do
  2909.       begin
  2910.         case (value[1 + p]) of
  2911.           '\':
  2912.             begin
  2913.               if (p + 1 < Length(value)) then
  2914.               begin
  2915.                 if (value[1 + p + 1]) = '}' then
  2916.                   inc(p);
  2917.               end;
  2918.               inc(p);
  2919.             end;
  2920.           '}':
  2921.             begin
  2922.               found := true;
  2923.             end;
  2924.         else
  2925.           begin
  2926.             inc(p);
  2927.           end;
  2928.         end;
  2929.  
  2930.         if (found) then
  2931.         begin
  2932. //          id := value.substring(start, p).trim(); PSTfix
  2933.           id := Trim( Copy(value, start + 1, p));
  2934.           if (leading <> 0) then
  2935.           begin
  2936. //            value := value.substring(0, leading) + value.substring(leading, start - 2).trim(); PSTfix
  2937.             value := Copy(value, 1, leading) + Trim( Copy( value, leading + 1, start -2));
  2938.           end
  2939.           else
  2940.           begin
  2941. //            value := value.substring(leading, start - 2).trim();  PSTFix
  2942.             value := Trim( Copy(value, leading +1, start -2));
  2943.           end;
  2944.           trailing := 0;
  2945.           if (Length(id) > 0) then
  2946.             exit(id)
  2947.           else
  2948.             exit('');
  2949.         end;
  2950.       end;
  2951.     end;
  2952.   end;
  2953.   exit('');
  2954. end;
  2955.  
  2956. function TLine.checkHTML: boolean;
  2957. var
  2958.   tags: TStringList;
  2959.   temp: TStringBuilder;
  2960.   element, tag: String;
  2961.   line: TLine;
  2962.   newPos: integer;
  2963. begin
  2964.   result := false;
  2965.   tags := TStringList.Create();
  2966.   temp := TStringBuilder.Create();
  2967.   try
  2968.     position := leading;
  2969.     if (value.length >= 1 + leading + 1) and (value[1 + leading + 1] = '!') then
  2970.     begin
  2971.       if (readXMLComment(self, leading) > 0) then
  2972.       begin
  2973.         exit(true);
  2974.       end;
  2975.     end;
  2976.     position := TUtils.readXML(temp, value, leading, false);
  2977.     if (position > -1) then
  2978.     begin
  2979.       element := temp.ToString();
  2980.       temp.Clear;
  2981.       TUtils.getXMLTag(temp, element);
  2982.       tag := LowerCase(temp.ToString());
  2983.       if (not THTML.isHtmlBlockElement(tag)) then
  2984.         exit(false);
  2985. //      if (tag.equals('hr') or element.endsWith('/>')) then PSTFix
  2986.       if (tag = 'hr') or AnsiEndsText('/>', element) then
  2987.  
  2988.       begin
  2989.         xmlEndLine := self;
  2990.         exit(true);
  2991.       end;
  2992.       tags.add(tag);
  2993.  
  2994.       line := self;
  2995.       while (line <> nil) do
  2996.       begin
  2997.         while (position < Length(line.value)) and ((line.value[1 + position] <> '<') or (position = Length(line.value)-1)) do
  2998.           inc(FPosition);
  2999.         if (position >= Length(line.value)) then
  3000.         begin
  3001.           line := line.next;
  3002.           position := 0;
  3003.         end
  3004.         else
  3005.         begin
  3006.           temp.Clear;
  3007.           newPos := TUtils.readXML(temp, line.value, position, false);
  3008.           if (newPos > 0) then
  3009.           begin
  3010.             element := temp.ToString();
  3011.             temp.Clear;
  3012.             TUtils.getXMLTag(temp, element);
  3013.             tag := LowerCase(temp.ToString());
  3014.             if (THTML.isHtmlBlockElement(tag)) and (tag <> 'hr') and (not AnsiEndsText('/>', element)) then
  3015.             begin
  3016.               if (element[1 + 1] = '/') then
  3017.               begin
  3018.                 if (tags[tags.Count - 1] <> tag) then
  3019.                   exit(false);
  3020.                 tags.Delete(tags.count - 1);
  3021.               end
  3022.               else
  3023.                 tags.add(tag);
  3024.             end;
  3025.             if (tags.count = 0) then
  3026.             begin
  3027.               xmlEndLine := line;
  3028.               break;
  3029.             end;
  3030.             position := newPos;
  3031.           end
  3032.           else
  3033.           begin
  3034.             inc(FPosition);
  3035.           end;
  3036.         end;
  3037.       end;
  3038.       result := tags.count = 0;
  3039.     end;
  3040.   finally
  3041.     temp.Free;
  3042.     tags.Free;
  3043.   end;
  3044. end;
  3045.  
  3046. { TLinkRef }
  3047.  
  3048. constructor TLinkRef.Create(link, title: String; isAbbrev: boolean);
  3049. begin
  3050.   inherited Create;
  3051.   FLink := link;
  3052.   FTitle := title;
  3053.   FIsAbbrev := isAbbrev;
  3054. end;
  3055.  
  3056. { TBlock }
  3057.  
  3058. constructor TBlock.Create;
  3059. begin
  3060.   inherited;
  3061. end;
  3062.  
  3063. destructor TBlock.Destroy;
  3064. begin
  3065.   FLines.Free;
  3066.   FBlocks.Free;
  3067.   FNext.free;
  3068.   inherited;
  3069. end;
  3070.  
  3071. procedure TBlock.AppendLine(line: TLine);
  3072. begin
  3073.   if (self.lineTail = nil) then
  3074.   begin
  3075.     self.FLines := line;
  3076.     self.FLineTail := line;
  3077.   end
  3078.   else
  3079.   begin
  3080.     self.lineTail.nextEmpty := line.isEmpty;
  3081.     line.prevEmpty := self.lineTail.isEmpty;
  3082.     line.previous := self.lineTail;
  3083.     self.lineTail.next := line;
  3084.     self.FLineTail := line;
  3085.   end;
  3086.  
  3087. end;
  3088.  
  3089. procedure TBlock.expandListParagraphs;
  3090. var
  3091.   outer: TBlock;
  3092.   inner: TBlock;
  3093.   hasParagraph: boolean;
  3094. begin
  3095.   if (self.type_ <> btORDERED_LIST) and (self.type_ <> btUNORDERED_LIST) then
  3096.     exit;
  3097.  
  3098.   outer := self.blocks;
  3099.   hasParagraph := false;
  3100.   while (outer <> nil) and (not hasParagraph) do
  3101.   begin
  3102.     if (outer.type_ = btLIST_ITEM) then
  3103.     begin
  3104.       inner := outer.blocks;
  3105.       while (inner <> nil) and (not hasParagraph) do
  3106.       begin
  3107.         if (inner.type_ = btPARAGRAPH) then
  3108.         begin
  3109.           hasParagraph := true;
  3110.         end;
  3111.         inner := inner.next;
  3112.       end;
  3113.     end;
  3114.     outer := outer.next;
  3115.   end;
  3116.  
  3117.   if (hasParagraph) then
  3118.   begin
  3119.     outer := self.blocks;
  3120.     while (outer <> nil) do
  3121.     begin
  3122.       if (outer.type_ = btLIST_ITEM) then
  3123.       begin
  3124.         inner := outer.blocks;
  3125.         while (inner <> nil) do
  3126.         begin
  3127.           if (inner.type_ = btNONE) then
  3128.           begin
  3129.             inner.type_ := btPARAGRAPH;
  3130.           end;
  3131.           inner := inner.next;
  3132.         end;
  3133.       end;
  3134.       outer := outer.next;
  3135.     end;
  3136.   end;
  3137. end;
  3138.  
  3139. function TBlock.hasLines: boolean;
  3140. begin
  3141.   result := lines <> nil;
  3142. end;
  3143.  
  3144. procedure TBlock.removeLine(line: TLine);
  3145. begin
  3146.   if (line.previous = nil) then
  3147.   begin
  3148.     self.FLines := line.next;
  3149.   end
  3150.   else
  3151.   begin
  3152.     line.previous.next := line.next;
  3153.   end;
  3154.  
  3155.   if (line.next = nil) then
  3156.   begin
  3157.     self.FLineTail := line.previous;
  3158.   end
  3159.   else
  3160.   begin
  3161.     line.next.previous := line.previous;
  3162.   end;
  3163.   line.previous := nil;
  3164.  
  3165.   line.next := nil;
  3166.   line.free;
  3167. end;
  3168.  
  3169. procedure TBlock.removeBlockQuotePrefix;
  3170. var
  3171.   line: TLine;
  3172.   rem: integer;
  3173. begin
  3174.   line := self.lines;
  3175.   while (line <> nil) do
  3176.   begin
  3177.     if (not line.isEmpty) then
  3178.     begin
  3179.       if (line.value[1 + line.leading] = '>') then
  3180.       begin
  3181.         rem := line.leading + 1;
  3182.         if (line.leading + 1 < Length(line.value)) and (line.value[1 + line.leading + 1] = ' ') then
  3183.         begin
  3184.           inc(rem);
  3185.         end;
  3186.         line.value := Copy(line.value, rem+1);
  3187.         line.InitLeading();
  3188.       end;
  3189.     end;
  3190.     line := line.next;
  3191.   end;
  3192. end;
  3193.  
  3194. function TBlock.removeLeadingEmptyLines: boolean;
  3195. var
  3196.   wasEmpty: boolean;
  3197.   line: TLine;
  3198. begin
  3199.   wasEmpty := false;
  3200.   line := self.lines;
  3201.   while (line <> nil) and (line.isEmpty) do
  3202.   begin
  3203.     self.removeLine(line);
  3204.     line := self.lines;
  3205.     wasEmpty := true;
  3206.   end;
  3207.   result := wasEmpty;
  3208.  
  3209. end;
  3210.  
  3211. procedure TBlock.removeTrailingEmptyLines;
  3212. var
  3213.   line: TLine;
  3214. begin
  3215.   line := self.lineTail;
  3216.   while (line <> nil) and (line.isEmpty) do
  3217.   begin
  3218.     self.removeLine(line);
  3219.     line := self.lineTail;
  3220.   end;
  3221. end;
  3222.  
  3223. procedure TBlock.removeListIndent(config: TConfiguration);
  3224. var
  3225.   line: TLine;
  3226. begin
  3227.   line := self.lines;
  3228.   while (line <> nil) do
  3229.   begin
  3230.     if (not line.isEmpty) then
  3231.     begin
  3232.       case (line.getLineType(config)) of
  3233.         ltULIST:
  3234. //          line.value := line.value.substring(line.leading + 2); PSTfix
  3235.           line.value := Copy(line.value, line.leading +3);
  3236.         ltOLIST:
  3237. //          line.value := line.value.substring(line.value.indexOf('.') + 2); pstfix
  3238.         line.value := Copy(line.value, pos('.', line.value) + 2);
  3239.       else
  3240. //        line.value := line.value.substring(Math.min(line.leading, 4)); pstfix
  3241.         line.value := Copy(line.value, Math.Min(line.leading + 1, 5));
  3242.       end;
  3243.       line.InitLeading();
  3244.     end;
  3245.     line := line.next;
  3246.   end;
  3247.  
  3248. end;
  3249.  
  3250. procedure TBlock.removeSurroundingEmptyLines;
  3251. begin
  3252.   if (self.lines <> nil) then
  3253.   begin
  3254.     self.removeTrailingEmptyLines();
  3255.     self.removeLeadingEmptyLines();
  3256.   end;
  3257.  
  3258. end;
  3259.  
  3260. function TBlock.split(line: TLine): TBlock;
  3261. var
  3262.   block: TBlock;
  3263. begin
  3264.   block := TBlock.Create();
  3265.   block.FLines := self.lines;
  3266.   block.FLineTail := line;
  3267.   self.FLines := line.next;
  3268.   line.next := nil;
  3269.   if (self.lines = nil) then
  3270.   begin
  3271.     self.FLineTail := nil;
  3272.   end
  3273.   else
  3274.   begin
  3275.     self.lines.previous := nil;
  3276.   end;
  3277.  
  3278.   if (self.blocks = nil) then
  3279.   begin
  3280.     self.FBlocks := block;
  3281.     self.FBlockTail := block;
  3282.   end
  3283.   else
  3284.   begin
  3285.     self.blockTail.next := block;
  3286.     self.FBlockTail := block;
  3287.   end;
  3288.   result := block;
  3289. end;
  3290.  
  3291. procedure TBlock.transfromHeadline;
  3292. var
  3293.   level, start, end_: integer;
  3294.   line: TLine;
  3295. begin
  3296.   if (self.hlDepth > 0) then
  3297.   begin
  3298.     exit;
  3299.   end;
  3300.   level := 0;
  3301.   line := self.lines;
  3302.   if (line.isEmpty) then
  3303.   begin
  3304.     exit;
  3305.   end;
  3306.   start := line.leading;
  3307.   while (start < Length(line.value)) and (line.value[1 + start] = '#') do
  3308.   begin
  3309.     inc(level);
  3310.     inc(start);
  3311.   end;
  3312.   while (start < Length(line.value)) and (line.value[1 + start] = ' ') do
  3313.   begin
  3314.     inc(start);
  3315.   end;
  3316.   if (start >= Length(line.value)) then
  3317.   begin
  3318.     line.setEmpty();
  3319.   end
  3320.   else
  3321.   begin
  3322.     end_ := Length(line.value) - line.trailing - 1;
  3323.     while (line.value[1 + end_] = '#') do
  3324.     begin
  3325.       dec(end_);
  3326.     end;
  3327.     while (line.value[1 + end_] = ' ') do
  3328.     begin
  3329.       dec(end_);
  3330.     end;
  3331.     line.value := Copy(line.value, start+1, end_-start+1);
  3332.     line.leading := 0;
  3333.     line.trailing := 0;
  3334.   end;
  3335.   self.hlDepth := Math.min(level, 6);
  3336.  
  3337. end;
  3338.  
  3339. {$IFDEF FPC}
  3340. { TStringBuilder }
  3341.  
  3342. constructor TStringBuilder.Create;
  3343. begin
  3344.   Inherited;
  3345.   FBufferSize := BUFFER_INCREMENT_SIZE;
  3346. end;
  3347.  
  3348. procedure TStringBuilder.Append(value: TStringBuilder);
  3349. begin
  3350.   append(value.ToString);
  3351. end;
  3352.  
  3353. procedure TStringBuilder.Append(value: integer);
  3354. begin
  3355.   append(inttostr(value));
  3356. end;
  3357.  
  3358. procedure TStringBuilder.Append(value: String);
  3359. begin
  3360.   If (value <> '') Then
  3361.   Begin
  3362.     If FLength + System.Length(value) > System.Length(FContent) Then
  3363.       SetLength(FContent, System.Length(FContent) + Math.Max(FBufferSize, System.Length(value)));
  3364.  
  3365.     Move(value[1], FContent[FLength + 1], System.Length(value) * SizeOf(Char));
  3366.  
  3367.     Inc(FLength, System.Length(value));
  3368.   End;
  3369. end;
  3370.  
  3371. procedure TStringBuilder.Clear;
  3372. begin
  3373.   FContent := '';
  3374.   FLength := 0;
  3375. end;
  3376.  
  3377. function TStringBuilder.GetChar(index: integer): char;
  3378. begin
  3379.   if (index < 0) or (index >= Length) then
  3380.     raise EMarkdownProcessor.Create('Out of bounds');
  3381.   result := FContent[index+1];
  3382. end;
  3383.  
  3384.  
  3385. function TStringBuilder.toString: String;
  3386. begin
  3387.   Result := Copy(FContent, 1, FLength);
  3388. end;
  3389.  
  3390. {$ENDIF}
  3391.  
  3392. end.
  3393.