Formatter.pas
上传用户:yjb1804
上传日期:2021-01-30
资源大小:3105k
文件大小:14k
源码类别:

Email服务器

开发平台:

Delphi

  1. unit Formatter;
  2. interface
  3. uses
  4.   DomCore;
  5. const
  6.   SHOW_ALL                    = $FFFFFFFF;
  7.   SHOW_ELEMENT                = $00000001;
  8.   SHOW_ATTRIBUTE              = $00000002;
  9.   SHOW_TEXT                   = $00000004;
  10.   SHOW_CDATA_SECTION          = $00000008;
  11.   SHOW_ENTITY_REFERENCE       = $00000010;
  12.   SHOW_ENTITY                 = $00000020;
  13.   SHOW_PROCESSING_INSTRUCTION = $00000040;
  14.   SHOW_COMMENT                = $00000080;
  15.   SHOW_DOCUMENT               = $00000100;
  16.   SHOW_DOCUMENT_TYPE          = $00000200;
  17.   SHOW_DOCUMENT_FRAGMENT      = $00000400;
  18.   SHOW_NOTATION               = $00000800;
  19. type
  20.   TStringBuilder = class
  21.   private
  22.     FCapacity: Integer;
  23.     FLength: Integer;
  24.     FValue: TDomString;
  25.   public
  26.     constructor Create(ACapacity: Integer);
  27.     function EndWithWhiteSpace: Boolean;
  28.     function TailMatch(const Tail: TDomString): Boolean;
  29.     function ToString: TDomString;
  30.     procedure AppendText(const TextStr: TDomString);
  31.     property Length: Integer read FLength;
  32.   end;
  33.   TBaseFormatter = class
  34.   private
  35.     procedure ProcessNode(Node: TNode);
  36.   protected
  37.     FDocument: TDocument;
  38.     FStringBuilder: TStringBuilder;
  39.     FDepth: Integer;
  40.     FWhatToShow: Integer;
  41.     FExpandEntities: Boolean;
  42.     FPreserveWhiteSpace: Boolean;
  43.     FInAttributes: Boolean;
  44.     procedure AppendNewLine;
  45.     procedure AppendParagraph;
  46.     procedure AppendText(const TextStr: TDomString); virtual;
  47.     procedure ProcessAttribute(Attr: TAttr); virtual;
  48.     procedure ProcessAttributes(Element: TElement); virtual;
  49.     procedure ProcessCDataSection(CDataSection: TCDataSection); virtual;
  50.     procedure ProcessComment(Comment: TComment); virtual;
  51.     procedure ProcessDocumentElement; virtual;
  52.     procedure ProcessElement(Element: TElement); virtual;
  53.     procedure ProcessEntityReference(EntityReference: TEntityReference); virtual;
  54. //    procedure ProcessNotation(Notation: TNotation); virtual;
  55.     procedure ProcessProcessingInstruction(ProcessingInstruction: TProcessingInstruction); virtual;
  56.     procedure ProcessTextNode(TextNode: TTextNode); virtual;
  57.   public
  58.     constructor Create;
  59.     function getText(document: TDocument): TDomString;
  60.   end;
  61.   THtmlFormatter = class(TBaseFormatter)
  62.   private
  63.     FIndent: Integer;
  64.     function OnlyTextContent(Element: TElement): Boolean;
  65.   protected
  66.     procedure ProcessAttribute(Attr: TAttr); override;
  67.     procedure ProcessComment(Comment: TComment); override;
  68.     procedure ProcessElement(Element: TElement); override;
  69.     procedure ProcessTextNode(TextNode: TTextNode); override;
  70.   public
  71.     constructor Create;
  72.     property Indent: Integer read FIndent write FIndent;
  73.   end;
  74.   TTextFormatter = class(TBaseFormatter)
  75.   protected
  76.     FInsideAnchor: Boolean;
  77.     function GetAnchorText(Node: TElement): TDomString; virtual;
  78.     function GetImageText(Node: TElement): TDomString; virtual;
  79.     procedure AppendText(const TextStr: TDomString); override;
  80.     procedure ProcessElement(Element: TElement); override;
  81.     procedure ProcessEntityReference(EntityReference: TEntityReference); override;
  82.     procedure ProcessTextNode(TextNode: TTextNode); override;
  83.   public
  84.     constructor Create;
  85.   end;
  86. implementation
  87. uses
  88.   SysUtils, Entities, HtmlTags;
  89. const
  90.   CRLF: TDomString = #13#10;
  91.   PARAGRAPH_SEPARATOR: TDomString = #13#10#13#10;
  92.   ViewAsBlockTags: THtmlTagSet = [
  93.     ADDRESS_TAG, BLOCKQUOTE_TAG, CAPTION_TAG, CENTER_TAG, DD_TAG, DIV_TAG,
  94.     DL_TAG, DT_TAG, FIELDSET_TAG, FORM_TAG, FRAME_TAG, H1_TAG, H2_TAG, H3_TAG,
  95.     H4_TAG, H5_TAG, H6_TAG, HR_TAG, IFRAME_TAG, LI_TAG, NOFRAMES_TAG, NOSCRIPT_TAG,
  96.     OL_TAG, P_TAG, PRE_TAG, TABLE_TAG, TD_TAG, TH_TAG, TITLE_TAG, UL_TAG
  97.   ];
  98. function IsWhiteSpace(W: WideChar): Boolean;
  99. begin
  100.   Result := Ord(W) in WhiteSpace
  101. end;
  102. function normalizeWhiteSpace(const TextStr: TDomString): TDomString;
  103. var
  104.   I, J, Count: Integer;
  105. begin
  106.   SetLength(Result, Length(TextStr));
  107.   J := 0;
  108.   Count := 0;
  109.   for I := 1 to Length(TextStr) do
  110.   begin
  111.     if IsWhiteSpace(TextStr[I]) then
  112.     begin
  113.       Inc(Count);
  114.       Continue
  115.     end;
  116.     if Count <> 0 then
  117.     begin
  118.       Count := 0;
  119.       Inc(J);
  120.       Result[J] := ' '
  121.     end;
  122.     Inc(J);
  123.     Result[J] := TextStr[I]
  124.   end;
  125.   if Count <> 0 then
  126.   begin
  127.     Inc(J);
  128.     Result[J] := ' '
  129.   end;
  130.   SetLength(Result, J)
  131. end;
  132. function Spaces(Count: Integer): TDomString;
  133. var
  134.   I: Integer;
  135. begin
  136.   SetLength(Result, Count);
  137.   for I := 1 to Count do
  138.     Result[I] := ' '
  139. end;
  140. function TrimLeftSpaces(const S: TDomString): TDomString;
  141. var
  142.   I: Integer;
  143. begin
  144.   I := 1;
  145.   while (I <= Length(S)) and (Ord(S[I]) = SP) do
  146.     Inc(I);
  147.   Result := Copy(S, I, Length(S) - I + 1)
  148. end;
  149. constructor TStringBuilder.Create(ACapacity: Integer);
  150. begin
  151.   inherited Create;
  152.   FCapacity := ACapacity;
  153.   SetLength(FValue, FCapacity)
  154. end;
  155. function TStringBuilder.EndWithWhiteSpace: Boolean;
  156. begin
  157.   Result := IsWhiteSpace(FValue[FLength])
  158. end;
  159. function TStringBuilder.TailMatch(const Tail: TDomString): Boolean;
  160. var
  161.   TailLen, I: Integer;
  162. begin
  163.   Result := false;
  164.   TailLen := System.Length(Tail);
  165.   if TailLen > FLength then
  166.     Exit;
  167.   for I := 1 to TailLen do
  168.     if FValue[FLength - TailLen + I] <> Tail[I] then
  169.       Exit;
  170.   Result := true
  171. end;
  172. function TStringBuilder.ToString: WideString;
  173. begin
  174.   SetLength(FValue, FLength);
  175.   Result := FValue
  176. end;
  177. procedure TStringBuilder.AppendText(const TextStr: TDomString);
  178. var
  179.   TextLen, I: Integer;
  180. begin
  181.   if (FLength + System.Length(TextStr)) > FCapacity then
  182.   begin
  183.     FCapacity := 2 * FCapacity;
  184.     SetLength(FValue, FCapacity)
  185.   end;
  186.   TextLen := System.Length(TextStr);
  187.   for I := 1 to TextLen do
  188.     FValue[FLength + I] := TextStr[I];
  189.   Inc(FLength, TextLen)
  190. end;
  191. constructor TBaseFormatter.Create;
  192. begin
  193.   inherited Create;
  194.   FWhatToShow := Integer(SHOW_ALL)
  195. end;
  196.                                     
  197. procedure TBaseFormatter.ProcessNode(Node: TNode);
  198. begin
  199.   case Node.nodeType of
  200.     ELEMENT_NODE:                ProcessElement(Node as TElement);
  201.     TEXT_NODE:                   if (FWhatToShow and SHOW_TEXT) <> 0 then ProcessTextNode(Node as TTextNode);
  202.     CDATA_SECTION_NODE:          if (FWhatToShow and SHOW_CDATA_SECTION) <> 0 then ProcessCDataSection(Node as TCDataSection);
  203.     ENTITY_REFERENCE_NODE:       if (FWhatToShow and SHOW_ENTITY_REFERENCE) <> 0 then ProcessEntityReference(Node as TEntityReference);
  204.     PROCESSING_INSTRUCTION_NODE: if (FWhatToShow and SHOW_PROCESSING_INSTRUCTION) <> 0 then ProcessProcessingInstruction(Node as TProcessingInstruction);
  205.     COMMENT_NODE:                if (FWhatToShow and SHOW_COMMENT) <> 0 then ProcessComment(Node as TComment);
  206. //    NOTATION_NODE:               if (FWhatToShow and SHOW_NOTATION) <> 0 then ProcessNotation(Node as Notation)
  207.   end
  208. end;
  209.                                     
  210. procedure TBaseFormatter.AppendNewLine;
  211. begin                                 
  212.   if FStringBuilder.Length > 0 then
  213.   begin
  214.     if not FStringBuilder.TailMatch(CRLF) then
  215.       FStringBuilder.AppendText(CRLF)
  216.   end
  217. end;
  218. procedure TBaseFormatter.AppendParagraph;
  219. begin
  220.   if FStringBuilder.Length > 0 then
  221.   begin
  222.     if not FStringBuilder.TailMatch(CRLF) then
  223.       FStringBuilder.AppendText(PARAGRAPH_SEPARATOR)
  224.     else
  225.     if not FStringBuilder.TailMatch(PARAGRAPH_SEPARATOR) then
  226.       FStringBuilder.AppendText(CRLF)
  227.   end
  228. end;
  229. procedure TBaseFormatter.AppendText(const TextStr: TDomString);
  230. begin
  231.   FStringBuilder.AppendText(TextStr)
  232. end;
  233. procedure TBaseFormatter.ProcessAttribute(Attr: TAttr);
  234. var
  235.   I: Integer;
  236. begin
  237.   for I := 0 to Attr.childNodes.length - 1 do
  238.     ProcessNode(Attr.childNodes.item(I))
  239. end;
  240. procedure TBaseFormatter.ProcessAttributes(Element: TElement);
  241. var
  242.   I: Integer;
  243. begin
  244.   if (FWhatToShow and SHOW_ATTRIBUTE) <> 0 then
  245.   begin
  246.     FInAttributes := true;
  247.     for I := 0 to Element.attributes.length - 1 do
  248.       ProcessAttribute(Element.attributes.item(I) as TAttr);
  249.     FInAttributes := false
  250.   end
  251. end;
  252. procedure TBaseFormatter.ProcessCDataSection(CDataSection: TCDataSection);
  253. begin
  254.   // TODO
  255. end;
  256. procedure TBaseFormatter.ProcessComment(Comment: TComment);
  257. begin
  258.   AppendText('<!--');
  259.   AppendText(Comment.data);
  260.   AppendText('-->')
  261. end;
  262. procedure TBaseFormatter.ProcessDocumentElement;
  263. begin
  264.   if Assigned(FDocument.documentElement) then
  265.   begin
  266.     FDepth := 0;
  267.     ProcessElement(FDocument.documentElement)
  268.   end
  269. end;
  270. procedure TBaseFormatter.ProcessElement(Element: TElement);
  271. var
  272.   I: Integer;
  273. begin
  274.   Inc(FDepth);
  275.   for I := 0 to Element.childNodes.length - 1 do
  276.     ProcessNode(Element.childNodes.item(I));
  277.   Dec(FDepth)
  278. end;
  279. procedure TBaseFormatter.ProcessEntityReference(EntityReference: TEntityReference);
  280. begin
  281.   if FExpandEntities then
  282.     AppendText(GetEntValue(EntityReference.nodeName))
  283.   else
  284.     AppendText('&' + EntityReference.nodeName + ';')
  285. end;
  286. {
  287. procedure TBaseFormatter.ProcessNotation(Notation: TNotation);
  288. begin
  289.   // TODO
  290. end;
  291. }
  292. procedure TBaseFormatter.ProcessProcessingInstruction(ProcessingInstruction: TProcessingInstruction);
  293. begin
  294.   // TODO
  295. end;
  296. procedure TBaseFormatter.ProcessTextNode(TextNode: TTextNode);
  297. begin
  298.   AppendText(TextNode.data)
  299. end;
  300. function TBaseFormatter.getText(document: TDocument): TDomString;
  301. begin
  302.   FDocument := document;
  303.   FStringBuilder := TStringBuilder.Create(65530);
  304.   try
  305.     ProcessDocumentElement;
  306.     Result := FStringBuilder.ToString
  307.   finally
  308.     FStringBuilder.Free
  309.   end
  310. end;
  311.                        
  312. constructor THtmlFormatter.Create;
  313. begin
  314.   inherited Create;
  315.   FIndent := 2
  316. end;
  317. function THtmlFormatter.OnlyTextContent(Element: TElement): Boolean;
  318. var
  319.   Node: TNode;
  320.   I: Integer;
  321. begin
  322.   Result := false;
  323.   for I := 0 to Element.childNodes.length - 1 do
  324.   begin
  325.     Node := Element.childNodes.item(I);
  326.     if not (Node.nodeType in [TEXT_NODE, ENTITY_REFERENCE_NODE]) then
  327.       Exit
  328.   end;
  329.   Result := true
  330. end;
  331. procedure THtmlFormatter.ProcessAttribute(Attr: TAttr);
  332. begin
  333.   if Attr.hasChildNodes then
  334.   begin
  335.     AppendText(' ' + Attr.name + '="');
  336.     inherited ProcessAttribute(Attr);
  337.     AppendText('"')
  338.   end
  339.   else
  340.     AppendText(' ' + Attr.name + '="' + Attr.name + '"')
  341. end;
  342.                                          
  343. procedure THtmlFormatter.ProcessComment(Comment: TComment);
  344. begin
  345.   AppendNewLine;
  346.   AppendText(Spaces(FIndent * FDepth));
  347.   inherited ProcessComment(Comment)
  348. end;
  349. procedure THtmlFormatter.ProcessElement(Element: TElement);
  350. var
  351.   HtmlTag: THtmlTag;
  352. begin
  353.   HtmlTag := HtmlTagList.GetTagByName(Element.tagName);
  354.   AppendNewLine;
  355.   AppendText(Spaces(FIndent * FDepth));
  356.   AppendText('<' + Element.tagName);
  357.   ProcessAttributes(Element);
  358.   if Element.hasChildNodes then
  359.   begin
  360.     AppendText('>');            
  361.     if HtmlTag.Number in PreserveWhiteSpaceTags then
  362.       FPreserveWhiteSpace := true;
  363.     inherited ProcessElement(Element);
  364.     FPreserveWhiteSpace := false;
  365.     if not OnlyTextContent(Element) then
  366.     begin
  367.       AppendNewLine;
  368.       AppendText(Spaces(FIndent * FDepth))
  369.     end;
  370.     AppendText('</' + Element.tagName + '>')
  371.   end
  372.   else
  373.     AppendText(' />')
  374. end;
  375. procedure THtmlFormatter.ProcessTextNode(TextNode: TTextNode);
  376. var
  377.   TextStr: TDomString;
  378. begin
  379.   if FPreserveWhiteSpace then
  380.     AppendText(TextNode.data)
  381.   else
  382.   begin
  383.     TextStr := normalizeWhiteSpace(TextNode.data);
  384.     if TextStr <> ' ' then
  385.       AppendText(TextStr)
  386.   end;
  387. end;
  388. constructor TTextFormatter.Create;
  389. begin
  390.   inherited Create;
  391.   FWhatToShow := SHOW_ELEMENT or SHOW_TEXT or SHOW_ENTITY_REFERENCE;
  392.   FExpandEntities := true 
  393. end;
  394. function TTextFormatter.GetAnchorText(Node: TElement): TDomString;
  395. var
  396.   Attr: TAttr;
  397. begin
  398.   Result := '';
  399.   if Node.hasAttribute('href') then
  400.   begin
  401.     Attr := Node.getAttributeNode('href');
  402.     Result := ' ';
  403.     if UrlSchemes.GetScheme(Attr.value) = '' then
  404.       Result := Result + 'http://';
  405.     Result := Result + Attr.value
  406.   end
  407. end;
  408. function TTextFormatter.GetImageText(Node: TElement): TDomString;
  409. begin
  410.   if Node.hasAttribute('alt') then
  411.     Result := Node.getAttributeNode('alt').value
  412.   else
  413.     Result := ''
  414. end;
  415. procedure TTextFormatter.AppendText(const TextStr: TDomString);
  416. begin
  417.   if (FStringBuilder.Length = 0) or FStringBuilder.EndWithWhiteSpace then
  418.     inherited AppendText(TrimLeftSpaces(TextStr))
  419.   else
  420.     inherited AppendText(TextStr)
  421. end;
  422. procedure TTextFormatter.ProcessElement(Element: TElement);
  423. var
  424.   HtmlTag: THtmlTag;
  425. begin
  426.   HtmlTag := HtmlTagList.GetTagByName(Element.tagName);
  427.   if HtmlTag.Number in ViewAsBlockTags then
  428.     AppendParagraph;
  429.   case HtmlTag.Number of
  430.     A_TAG:  FInsideAnchor := true;
  431.     LI_TAG: AppendText('* ')
  432.   end;
  433.   if HtmlTag.Number in PreserveWhiteSpaceTags then
  434.     FPreserveWhiteSpace := true;
  435.   inherited ProcessElement(Element);
  436.   FPreserveWhiteSpace := false;
  437.   case HtmlTag.Number of
  438.     BR_TAG:
  439.       AppendNewLine;
  440.     A_TAG:
  441.     begin
  442.       AppendText(GetAnchorText(Element));
  443.       FInsideAnchor := false
  444.     end;
  445.     IMG_TAG:
  446.     begin
  447.       if FInsideAnchor then
  448.         AppendText(GetImageText(Element))
  449.     end
  450.   end;
  451.   if HtmlTag.Number in ViewAsBlockTags then
  452.     AppendParagraph
  453. end;
  454. procedure TTextFormatter.ProcessEntityReference(EntityReference: TEntityReference);
  455. begin
  456.   if EntityReference.nodeName = 'nbsp' then
  457.     AppendText(' ')
  458.   else
  459.     inherited ProcessEntityReference(EntityReference)
  460. end;
  461. procedure TTextFormatter.ProcessTextNode(TextNode: TTextNode);
  462. begin
  463.   if FPreserveWhiteSpace then
  464.     AppendText(TextNode.data)
  465.   else
  466.     AppendText(normalizeWhiteSpace(TextNode.data))
  467. end;
  468. end.