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

Email服务器

开发平台:

Delphi

  1. {
  2.   Unit NativeXmlAppend
  3.   This unit implements a method to add XML fragments to the end of an existing
  4.   XML file that resides on disk. The file is never loaded completely into memory,
  5.   the new data will be appended at the end.
  6.   This unit requires NativeXml.
  7.   Possible exceptions (apart from the regular ones for file access):
  8.   'Reverse read past beginning of stream':
  9.     The file provided in S is not an XML file or it is an XML file with not enough
  10.     levels. The XML file should have in its last tag at least ALevel levels of
  11.     elements. Literally this exception means that the algorithm went backwards
  12.     through the complete file and arrived at the beginning, without finding a
  13.     suitable position to insert the node data.
  14.   'Level cannot be found'
  15.     This exception will be raised when the last element does not contain enough
  16.     levels, so the algorithm encounters an opening tag where it would expect a
  17.     closing tag.
  18.     Example:
  19.     We try to add a node at level 3 in this XML file
  20.     <Root>
  21.       <Level1>
  22.         <Level2>
  23.         </Level2>
  24.       </Level1>
  25.       <Level1>    <-- This last node does not have a level2, so the algorithm
  26.       </Level1>       does not know where to add the data of level 3 under level2
  27.     </Root>
  28.   See Example4 for an implementation
  29.   Copyright (c) 2003 by Nils Haeck, Simdesign
  30.   It is NOT allowed under ANY circumstances to publish or copy this code
  31.   without prior written permission of the Author!
  32.   This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF
  33.   ANY KIND, either express or implied.
  34.   Please visit http://www.simdesign.nl/xml.html for more information.
  35. }
  36. // Delphi and BCB versions
  37. // Delphi 3
  38. {$IFDEF VER110}
  39.   {$DEFINE D3UP}
  40. {$ENDIF}
  41. // Delphi 4
  42. {$IFDEF VER120}
  43.   {$DEFINE D3UP}
  44.   {$DEFINE D4UP}
  45. {$ENDIF}
  46. // BCB 4
  47. {$IFDEF VER125}
  48.   {$DEFINE D4UP}
  49. {$ENDIF}
  50. // Delphi 5
  51. {$IFDEF VER130}
  52.   {$DEFINE D3UP}
  53.   {$DEFINE D4UP}
  54.   {$DEFINE D5UP}
  55. {$ENDIF}
  56. //Delphi 6
  57. {$IFDEF VER140}
  58.   {$DEFINE D3UP}
  59.   {$DEFINE D4UP}
  60.   {$DEFINE D5UP}
  61.   {$DEFINE D6UP}
  62. {$ENDIF}
  63. //Delphi 7
  64. {$IFDEF VER150}
  65.   {$DEFINE D3UP}
  66.   {$DEFINE D4UP}
  67.   {$DEFINE D5UP}
  68.   {$DEFINE D6UP}
  69.   {$DEFINE D7UP}
  70. {$ENDIF}
  71. //Delphi 8
  72. {$IFDEF VER160}
  73.   {$DEFINE D3UP}
  74.   {$DEFINE D4UP}
  75.   {$DEFINE D5UP}
  76.   {$DEFINE D6UP}
  77.   {$DEFINE D7UP}
  78.   {$DEFINE D8UP}
  79. {$ENDIF}
  80. // Delphi 2005
  81. {$IFDEF VER170}
  82.   {$DEFINE D3UP}
  83.   {$DEFINE D4UP}
  84.   {$DEFINE D5UP}
  85.   {$DEFINE D6UP}
  86.   {$DEFINE D7UP}
  87.   {$DEFINE D8UP}
  88.   {$DEFINE D9UP}
  89. {$ENDIF}
  90. // above Delphi 2005
  91. {$IFDEF VER180}
  92.   {$DEFINE D3UP}
  93.   {$DEFINE D4UP}
  94.   {$DEFINE D5UP}
  95.   {$DEFINE D6UP}
  96.   {$DEFINE D7UP}
  97.   {$DEFINE D8UP}
  98.   {$DEFINE D9UP}
  99.   {$DEFINE D10UP}
  100. {$ENDIF}
  101. unit NativeXmlAppend;
  102. interface
  103. uses
  104.   Classes, SysUtils, Dialogs, NativeXml;
  105. // With this routine we can add a single node (TXmlNode) to an existing XML file.
  106. // The file will NOT be read in completely, the data will simply be appended at the
  107. // end. In order to do this, the file is scanned from the end until the last node
  108. // at ALevel levels deep is located.
  109. // ALevel = 0 would add the new node at the very end. This is not wise, since XML
  110. // does not allow more than one root node. Choose ALevel = 1 to add the new node
  111. // at the first level under the root (default).
  112. // <p>
  113. // TIP: If you want to start with an empty (template) XmlDocument, make sure to
  114. // set TsdXmlDocument.UseFullNodes to True before saving it. This ensures that
  115. // the append function will work correctly on the root node.
  116. // <p>
  117. // NOTE 1: This method does not work for unicode files.
  118. procedure XmlAppendToExistingFile(const AFilename: string; ANode: TXmlNode;
  119.   ALevel: integer {$IFDEF D4UP}= 1{$ENDIF});
  120. implementation
  121. type
  122.   // We need this class to get access to protected method WriteToString
  123.   THackNode = class(TXmlNode);
  124.   TTagType = record
  125.     FClose: string;
  126.     FStart: string;
  127.   end;
  128. const
  129.   // Reversed tags, note: the record fields are also in reversed order. This
  130.   // is because we read backwards
  131.   cTagCount = 4;
  132.   cTags: array[0..cTagCount - 1] of TTagType = (
  133.     // The order is important here; the items are searched for in appearing order
  134.     (FClose: '>]]'; FStart: '[ATADC[!<'), // CDATA
  135.     (FClose: '>--'; FStart: '--!<'),      // Comment
  136.     (FClose: '>?';  FStart: '?<'),        // <?{something}?>
  137.     (FClose: '>';   FStart: '<')          // Any other
  138.   );
  139. function ScanBackwards(S: TStream): char;
  140. begin
  141.   if S.Position = 0 then
  142.     raise Exception.Create('Reverse read past beginning of stream');
  143.   S.Seek(-1, soFromCurrent);
  144.   S.Read(Result, 1);
  145.   S.Seek(-1, soFromCurrent);
  146. end;
  147. function ReverseReadCloseTag(S: TStream): integer;
  148. // Try to read the type of close tag from S, in reversed order
  149. var
  150.   AIndex, i: integer;
  151.   Found: boolean;
  152.   Ch: char;
  153. begin
  154.   Result := cTagCount - 1;
  155.   AIndex := 1;
  156.   repeat
  157.     Found := False;
  158.     inc(AIndex);
  159.     Ch := ScanBackwards(S);
  160.     for i := cTagCount - 1 downto 0 do begin
  161.       if length(cTags[i].FClose) >= AIndex then
  162.         if cTags[i].FClose[AIndex] = Ch then begin
  163.           Found := True;
  164.           Result := i;
  165.           break;
  166.         end;
  167.     end;
  168.   until Found = False;
  169.   // Increase position again because we read too far
  170.   S.Seek(1, soFromCurrent);
  171. end;
  172. procedure ReverseReadFromStreamUntil(S: TStream; const ASearch: string;
  173.   var AValue: string);
  174. // Read the tag in reversed order. We are looking for the string in ASearch
  175. // (in reversed order). AValue will contain the tag when done (in correct order).
  176. var
  177.   AIndex: integer;
  178.   Ch: char;
  179. begin
  180.   AIndex := 1;
  181.   AValue := '';
  182.   while AIndex <= length(ASearch) do begin
  183.     Ch := ScanBackwards(S);
  184.     AValue := Ch + AValue;
  185.     if ASearch[AIndex] = Ch then
  186.       inc(AIndex)
  187.     else
  188.       AIndex := 1;
  189.   end;
  190.   AValue := copy(AValue, Length(ASearch) + 1, length(AValue));
  191. end;
  192. function XmlScanNodeFromEnd(S: TStream; ALevel: integer): integer;
  193. // Scan the stream S from the end and find the end of node at level ALevel
  194. var
  195.   Ch: char;
  196.   ATagIndex: integer;
  197.   AValue: string;
  198. begin
  199.   S.Seek(0, soFromEnd);
  200.   while ALevel > 0 do begin
  201.     Ch := ScanBackwards(S);
  202.     if Ch = '>' then begin
  203.       // Determine tag type from closing tag
  204.       ATagIndex := ReverseReadCloseTag(S);
  205.       // Try to find the start
  206.       ReverseReadFromStreamUntil(S, cTags[ATagIndex].FStart, AValue);
  207.       // We found the start, now decide what to do. We only decrease
  208.       // level if this is a closing tag. If it is an opening tag, we
  209.       // should raise an exception
  210.       if (ATagIndex = 3) then begin
  211.         if (Length(AValue) > 0) and (AValue[1] = '/') then
  212.           dec(ALevel)
  213.         else
  214.           raise Exception.Create('Level cannot be found');
  215.       end;
  216.     end;
  217.   end;
  218.   Result := S.Position;
  219. end;
  220. procedure StreamInsertString(S: TStream; APos: integer; Value: string);
  221. // Insert Value into stream S at position APos. The stream S (if it is a disk
  222. // file) should have write access!
  223. var
  224.   ASize: integer;
  225.   M: TMemoryStream;
  226. begin
  227.   // Nothing to do if no value
  228.   if Length(Value) = 0 then exit;
  229.   S.Position := APos;
  230.   ASize := S.Size - S.Position;
  231.   // Create intermediate memory stream that holds the new ending
  232.   M := TMemoryStream.Create;
  233.   try
  234.     // Create a copy into a memory stream that contains new insert + old last part
  235.     M.SetSize(ASize + Length(Value));
  236.     M.Write(Value[1], Length(Value));
  237.     M.CopyFrom(S, ASize);
  238.     // Now add this copy at the current position
  239.     M.Position := 0;
  240.     S.Position := APos;
  241.     S.CopyFrom(M, M.Size);
  242.   finally
  243.     M.Free;
  244.   end;
  245. end;
  246. procedure XmlAppendToExistingFile(const AFilename: string; ANode: TXmlNode;
  247.   ALevel: integer);
  248. // With this routine we can add a single node (TXmlNode) to an existing XML file.
  249. // The file will NOT be read in completely, the data will simply be appended at the
  250. // end. In order to do this, the file is scanned from the end until the last node
  251. // at ALevel levels deep is located.
  252. // ALevel = 0 would add the new node at the very end. This is not wise, since XML
  253. // does not allow more than one root node. Choose ALevel = 1 to add the new node
  254. // at the first level under the root (default).
  255. var
  256.   S: TStream;
  257.   APos: integer;
  258.   AInsert: string;
  259. begin
  260.   // Open the file with Read/Write access
  261.   S := TFileStream.Create(AFilename, fmOpenReadWrite or fmShareDenyWrite);
  262.   try
  263.     // After a successful open, we can locate the correct end of node
  264.     APos := XmlScanNodeFromEnd(S, ALevel);
  265.     // Still no exceptions, this means we found a valid position.. now insert the
  266.     // new node in here.
  267.     AInsert := THackNode(ANode).WriteToString;
  268.     // Now we happily insert the string into the opened stream at the right position
  269.     StreamInsertString(S, APos, AInsert);
  270.   finally
  271.     // We're done, close the stream, this will save the modified filestream
  272.     S.Free;
  273.   end;
  274. end;
  275. end.