Helpers.pas 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722
  1. unit Helpers;
  2. {$I ImagingOptions.inc}
  3. interface
  4. uses
  5. SysUtils, Classes, ImagingUtility, DemoUtils, DOM, XMLRead, XMLWrite;
  6. const
  7. sXTOCRoot = 'toc';
  8. sXTOCList = 'itemlist';
  9. sXTOCItem = 'item';
  10. sXName = 'name';
  11. sXURL = 'url';
  12. sXTOCFile = 'toc';
  13. sXTitle = 'title';
  14. sXRootFile = 'root';
  15. sXOutputFile = 'output';
  16. sXXslDir = 'xsldir';
  17. sXRefDir = 'refdir';
  18. SXProducer = 'producer';
  19. sXLinkTargetURL = 'url';
  20. sXLinkTargetAnchor = 'anchor';
  21. sXRef = 'ref';
  22. sXAnchorDelim = '#';
  23. sXTemplate = '<?xml version="1.0" encoding="utf-8"?>' + sLineBreak +
  24. '<?xml-stylesheet type="text/xsl" href="%s/doc2html.xsl"?>' + sLineBreak +
  25. '<doc>' + sLineBreak +
  26. ' <title>Template</title>' + sLineBreak +
  27. ' <chapter>' + sLineBreak +
  28. ' <title>Template</title>' + sLineBreak +
  29. ' <par>This file was autogenerated by VampyreDoc</par>' + sLineBreak +
  30. ' </chapter>' + sLineBreak +
  31. '</doc>';
  32. sURLDelim = '/';
  33. sXSLLink = 'link';
  34. sXSLHref = 'href';
  35. sXSLSrc = 'src';
  36. sCSSExt = 'css';
  37. sCSSLink = 'url';
  38. sCSSLinkBStart = '(';
  39. sCSSLinkBEnd = ')';
  40. type
  41. { Base class for objects based on elements of XML files.}
  42. TXMLItem = class(TObject)
  43. private
  44. FElement: TDOMElement;
  45. procedure ParseElement; virtual; abstract;
  46. function FindElement(const NodeName: string; Parent: TDOMElement;
  47. var Elem: TDOMElement): Boolean;
  48. public
  49. constructor Create(AElement: TDOMElement);
  50. property Element: TDOMElement read FElement;
  51. end;
  52. TDocProject = class;
  53. { This class represents table of contents with items stored in tree.
  54. Use your projects toc xml file as input to constructor.}
  55. TContentItem = class(TXMLItem)
  56. private
  57. FChildren: TList;
  58. FURL: string;
  59. FName: string;
  60. FProject: TDocProject;
  61. function GetChild(Index: Integer): TContentItem;
  62. function GetChildCount: LongInt;
  63. procedure ParseElement; override;
  64. public
  65. constructor Create(AElement: TDOMElement; AProject: TDocProject);
  66. destructor Destroy; override;
  67. property Child[Index: LongInt]: TContentItem read GetChild; default;
  68. property ChildCount: LongInt read GetChildCount;
  69. property URL: string read FURL;
  70. property Name: string read FName;
  71. end;
  72. { This class represents whole VampyreDoc project loaded from
  73. .vdocproj xml file. It contains table of contents loaded
  74. from toc xml file.}
  75. TDocProject = class(TXMLItem)
  76. private
  77. FProjectFile: string;
  78. FTitle: string;
  79. FContentsFile: string;
  80. FOutputFile: string;
  81. FRootFile: string;
  82. FXslDir: string;
  83. FRefDir: string;
  84. FContents: TContentItem;
  85. FFiles: TStrings;
  86. procedure ParseElement; override;
  87. public
  88. constructor Create(AElement: TDOMElement; const AProjectFile: string);
  89. destructor Destroy; override;
  90. property ProjectFile: string read FProjectFile;
  91. property Title: string read FTitle;
  92. property ContentsFile: string read FContentsFile;
  93. property OutputFile: string read FOutputFile;
  94. property RootFile: string read FRootFile;
  95. property XslDir: string read FXslDir;
  96. property RefDir: string read FRefDir;
  97. property Contents: TContentItem read FContents;
  98. end;
  99. { Base class for output documentation producers. They take
  100. project and output directory and transform xml based VampyreDoc
  101. files to another formats like HTML or CHM.}
  102. TDocProducer = class(TObject)
  103. protected
  104. FName: string;
  105. public
  106. procedure Process(Project: TDocProject; const OutDir: string); virtual; abstract;
  107. property Name: string read FName;
  108. end;
  109. TCustomConverter = function(const S, Context: string): string;
  110. TLinker = class(TObject)
  111. private
  112. FDoc: TXMLDocument;
  113. FResultPath: string;
  114. FProject: TDocProject;
  115. FFileName: string;
  116. FIntendedOutput: string;
  117. FExternalRefs: TStringList;
  118. function ResolveExternalLink(const Link: string; const BasePath: string = ''): string;
  119. procedure CheckElement(Elem: TDOMElement); virtual; abstract;
  120. function ConvertLink(const Link, Context: string): string; virtual; abstract;
  121. public
  122. constructor Create;
  123. destructor Destroy; override;
  124. function CheckDocument(const FileName: string; Project: TDocProject): Boolean; virtual;
  125. procedure SaveResult(const FileName: string);
  126. procedure DeleteResult;
  127. property Doc: TXMLDocument read FDoc;
  128. property References: TStringList read FExternalRefs;
  129. property IntendedOutput: string read FIntendedOutput write FIntendedOutput;
  130. end;
  131. { This class is used by doc producers to convert all links to external
  132. files in VampyreDoc xml files to format compatible with producer's
  133. output format.}
  134. TLinkChecker = class(TLinker)
  135. private
  136. FStripDir: Boolean;
  137. FExtension: string;
  138. FNewPathDelim: string;
  139. FDestDir: string;
  140. FCustomConverter: TCustomConverter;
  141. FRefFiles: TStringList;
  142. procedure CheckElement(Elem: TDOMElement); override;
  143. function ConvertLink(const Link, Context: string): string; override;
  144. procedure ResolveReferenceLink(Elem: TDOMElement);
  145. public
  146. function CheckDocument(const FileName: string; Project: TDocProject): Boolean; override;
  147. property StripDir: Boolean read FStripDir write FStripDir;
  148. property Extension: string read FExtension write FExtension;
  149. property NewPathDelim: string read FNewPathDelim write FNewPathDelim;
  150. property DestDir: string read FDestDir write FDestDir;
  151. property CustomConverter: TCustomConverter read FCustomConverter write
  152. FCustomConverter;
  153. end;
  154. { This class checks XSL stylesheet used to transform documents and
  155. if there are any links (href and src) to existing files these
  156. files are copied to IntendedOutput's directory.
  157. If link refers to CSS stylesheet, this sheet is parsed and
  158. all references to external files (like background-image: url())
  159. are processed - files are copied to output dir and urls are updated
  160. (Note: your original CSS styles are not changed! Only styles
  161. copied to output dir are parsed).)}
  162. TXSLLinker = class(TLinker)
  163. private
  164. procedure CheckElement(Elem: TDOMElement); override;
  165. function ConvertLink(const Link, Context: string): string; override;
  166. procedure ParseCSSFile(const DestCSS, SourceCSS: string);
  167. public
  168. end;
  169. { Runs given command line and waits until process ends.
  170. Returns True if process was successfuly executed.}
  171. function RunCmdLine(const CmdLine: string): Boolean;
  172. { Transforms InDoc XML document to OutDoc using StyleSheet XSL file.
  173. You must have Instant Saxon installed and in OS's search path
  174. in order to work in Windows.}
  175. procedure TransformDoc(const InDoc, OutDoc, StyleSheet: string);
  176. { Returns list filled with filenames of all files contained in project.}
  177. procedure ProjectToStrings(Project: TDocProject; List: TStrings);
  178. { Creates files refered by project's toc file but which do not exist yet.}
  179. procedure GenerateTOCTemplates(Project: TDocProject);
  180. { Outputs message. Uses Write if compiled as console app or nothing if else.}
  181. procedure Msg(const S: string);
  182. implementation
  183. {$IFDEF MSWINDOWS}
  184. uses
  185. Windows;
  186. {$ENDIF}
  187. function RunCmdLine(const CmdLine: string): Boolean;
  188. {$IFDEF MSWINDOWS}
  189. var
  190. StartupInfo: TStartupInfo;
  191. ProcessInfo: TProcessInformation;
  192. begin
  193. Result := False;
  194. FillChar(StartupInfo, SizeOf(StartupInfo), 0);
  195. with StartupInfo do
  196. begin
  197. cb := SizeOf(StartupInfo);
  198. dwFlags := STARTF_USESHOWWINDOW;
  199. wShowWindow := SW_SHOW;
  200. end;
  201. if CreateProcess(nil, PChar(CmdLine), nil, nil,
  202. False, NORMAL_PRIORITY_CLASS, nil, nil, StartupInfo, ProcessInfo) then
  203. begin
  204. repeat
  205. until WaitForSingleObject(ProcessInfo.hProcess, 1) = WAIT_OBJECT_0;
  206. CloseHandle(ProcessInfo.hProcess);
  207. CloseHandle(ProcessInfo.hThread);
  208. Result := True;
  209. end
  210. else
  211. Msg('Cannot run command line: ' + CmdLine);
  212. end;
  213. {$ELSE}
  214. begin
  215. Msg('RunCmdLine is not implemented for this platform yet.');
  216. end;
  217. {$ENDIF}
  218. procedure TransformDoc(const InDoc, OutDoc, StyleSheet: string);
  219. var
  220. CmdLine: string;
  221. begin
  222. CmdLine := Format('saxon-transform -novw -s "%s" -o "%s" "%s"', [InDoc, OutDoc, StyleSheet]);
  223. if not RunCmdLine(CmdLine) then
  224. raise Exception.Create('SAXON cannot be executed.');
  225. end;
  226. procedure ProjectToStrings(Project: TDocProject; List: TStrings);
  227. procedure AddItem(Item: TContentItem);
  228. var
  229. I: LongInt;
  230. begin
  231. List.Add(Item.URL);
  232. for I := 0 to Item.ChildCount - 1 do
  233. AddItem(Item[I]);
  234. end;
  235. begin
  236. List.Clear;
  237. AddItem(Project.FContents);
  238. List.Add(Project.FContentsFile);
  239. end;
  240. procedure GenerateTOCTemplates(Project: TDocProject);
  241. var
  242. I: LongInt;
  243. Stream: TFileStream;
  244. S, XslPath: string;
  245. begin
  246. for I := 0 to Project.FFiles.Count - 1 do
  247. if not FileExists(Project.FFiles[I]) then
  248. try
  249. ForceDirectories(ExtractFileDir(Project.FFiles[I]));
  250. Stream := TFileStream.Create(Project.FFiles[I], fmCreate);
  251. XslPath := SwapPathDelims(
  252. ExtractRelativePath(Project.FFiles[I], Project.FXslDir), sURLDelim);
  253. S := Format(sXTemplate, [XslPath]);
  254. Stream.Write(S[1], Length(S));
  255. Stream.Free;
  256. except
  257. end;
  258. end;
  259. procedure Msg(const S: string);
  260. begin
  261. if System.IsConsole then
  262. WriteLn(S);
  263. end;
  264. { TXMLItem }
  265. constructor TXMLItem.Create(AElement: TDOMElement);
  266. begin
  267. inherited Create;
  268. FElement := AElement;
  269. ParseElement;
  270. end;
  271. function TXMLItem.FindElement(const NodeName: string; Parent: TDOMElement;
  272. var Elem: TDOMElement): Boolean;
  273. var
  274. I: LongInt;
  275. begin
  276. Elem := TDOMElement(Parent.FindNode(NodeName));
  277. Result := Elem <> nil;
  278. if (not Result) and (Parent.ChildNodes.Count > 0) then
  279. for I := 0 to Parent.ChildNodes.Count - 1 do
  280. begin
  281. Result := FindElement(NodeName, TDOMElement(Parent.ChildNodes.Item[I]), Elem);
  282. if Result then
  283. Break;
  284. end;
  285. end;
  286. { TContentItem }
  287. constructor TContentItem.Create(AElement: TDOMElement; AProject: TDocProject);
  288. begin
  289. FProject := AProject;
  290. inherited Create(AElement);
  291. end;
  292. destructor TContentItem.Destroy;
  293. var
  294. I: LongInt;
  295. begin
  296. for I := 0 to FChildren.Count - 1 do
  297. TContentItem(FChildren[I]).Free;
  298. FChildren.Free;
  299. inherited Destroy;
  300. end;
  301. function TContentItem.GetChild(Index: LongInt): TContentItem;
  302. begin
  303. Result := TContentItem(FChildren[Index]);
  304. end;
  305. function TContentItem.GetChildCount: LongInt;
  306. begin
  307. Result := FChildren.Count;
  308. end;
  309. procedure TContentItem.ParseElement;
  310. var
  311. I: LongInt;
  312. Elem: TDOMElement;
  313. begin
  314. FChildren := TList.Create;
  315. // if given element is root of toc file we find first valid
  316. // toc item element
  317. if SameText(FElement.NodeName, sXTOCRoot) then
  318. if not FindElement(sXTOCItem, FElement, Elem) then
  319. Exit
  320. else
  321. FElement := Elem;
  322. // we assign attributes of toc item element to properties
  323. FName := FElement.GetAttribute(sXName);
  324. FURL := SwapPathDelims(FElement.GetAttribute(sXURL));
  325. FURL := ExpandFileTo(FURL, ExtractFileDir(FProject.ProjectFile));
  326. if not FileExists(FURL) then
  327. WriteLn('Warning: TOC item "' + ExtractFileName(FURL) + '" not found.');
  328. // if toc item contains toc itemlist and this list has some items
  329. // we add these items to child list of this content item
  330. if (FElement.FirstChild <> nil) and
  331. (FElement.FirstChild.ChildNodes.Count > 0) and
  332. (FElement.NodeType = ELEMENT_NODE) then
  333. for I := 0 to FElement.FirstChild.GetChildNodes.Count - 1 do
  334. if Element.FirstChild.ChildNodes.Item[I].NodeType = ELEMENT_NODE then
  335. begin
  336. FChildren.Add(TContentItem.Create(
  337. TDOMElement(FElement.FirstChild.ChildNodes.Item[I]), FProject));
  338. end;
  339. end;
  340. { TDocProject }
  341. constructor TDocProject.Create(AElement: TDOMElement; const AProjectFile: string);
  342. begin
  343. FProjectFile := AProjectFile;
  344. inherited Create(AElement);
  345. end;
  346. destructor TDocProject.Destroy;
  347. begin
  348. FFiles.Free;
  349. FContents.Free;
  350. inherited Destroy;
  351. end;
  352. procedure TDocProject.ParseElement;
  353. var
  354. ContDoc: TXMLDocument;
  355. begin
  356. FTitle := FElement.FindNode(sXTitle).FirstChild.NodeValue;
  357. FContentsFile := SwapPathDelims(FElement.FindNode(sXTOCFile).FirstChild.NodeValue);
  358. FOutputFile := SwapPathDelims(FElement.FindNode(sXOutputFile).FirstChild.NodeValue);
  359. FRootFile := SwapPathDelims(FElement.FindNode(sXRootFile).FirstChild.NodeValue);
  360. FXslDir := SwapPathDelims(FElement.FindNode(sXXslDir).FirstChild.NodeValue);
  361. FRefDir := SwapPathDelims(FElement.FindNode(sXRefDir).FirstChild.NodeValue);
  362. FContentsFile := ExpandFileTo(FContentsFile, ExtractFileDir(FProjectFile));
  363. FXslDir := ExpandFileTo(FXslDir, ExtractFileDir(FProjectFile));
  364. FRefDir := ExpandFileTo(FRefDir, ExtractFileDir(FProjectFile));
  365. XMLRead.ReadXMLFile(ContDoc, FContentsFile);
  366. FContents := TContentItem.Create(ContDoc.DocumentElement, Self);
  367. ContDoc.Free;
  368. FFiles := TStringList.Create;
  369. ProjectToStrings(Self, FFiles);
  370. end;
  371. { TLinker }
  372. constructor TLinker.Create;
  373. begin
  374. inherited Create;
  375. FExternalRefs := TStringList.Create;
  376. FExternalRefs.Sorted := True;
  377. end;
  378. destructor TLinker.Destroy;
  379. begin
  380. FExternalRefs.Free;
  381. FDoc.Free;
  382. inherited Destroy;
  383. end;
  384. function TLinker.CheckDocument(const FileName: string;
  385. Project: TDocProject): Boolean;
  386. begin
  387. Result := False;
  388. FreeAndNil(FDoc);
  389. FProject := Project;
  390. FFileName := FileName;
  391. FExternalRefs.Clear;
  392. if FileExists(FFileName) then
  393. try
  394. XMLRead.ReadXMLFile(FDoc, FFileName);
  395. FDoc.Encoding := 'utf-8';
  396. CheckElement(FDoc.DocumentElement);
  397. Result := True;
  398. except
  399. end;
  400. end;
  401. procedure TLinker.SaveResult(const FileName: string);
  402. begin
  403. if fDoc <> nil then
  404. begin
  405. FResultPath := FileName;
  406. XMLWrite.WriteXMLFile(FDoc, FResultPath);
  407. end;
  408. end;
  409. procedure TLinker.DeleteResult;
  410. begin
  411. FreeAndNil(FDoc);
  412. SysUtils.DeleteFile(FResultPath);
  413. end;
  414. function TLinker.ResolveExternalLink(const Link: string; const BasePath: string): string;
  415. var
  416. FullPath, Base: string;
  417. begin
  418. Base := BasePath;
  419. if Base = '' then
  420. Base := FFileName;
  421. Result := Link;
  422. FullPath := SwapPathDelims(Result);
  423. FullPath := ExpandFileTo(FullPath, ExtractFileDir(Base));
  424. // if this link targets existing external file we copy this
  425. // file to directory where intended transformed doc will be output
  426. // and change link's target to only file name with no dir
  427. if FileExists(FullPath) then
  428. begin
  429. CopyFile(PChar(FullPath), PChar(ExtractFileDir(FIntendedOutput) +
  430. PathDelim + ExtractFileName(FullPath)), False);
  431. Result := ExtractFileName(FullPath);
  432. FExternalRefs.Add(Result);
  433. end;
  434. end;
  435. { TLinkChecker }
  436. function TLinkChecker.CheckDocument(const FileName: string;
  437. Project: TDocProject): Boolean;
  438. begin
  439. FRefFiles := TStringList.Create;
  440. BuildFileList(Project.FRefDir + PathDelim + '*', faAnyFile, FRefFiles, [flFullNames, flRecursive]);
  441. Result := inherited CheckDocument(FileName, Project);
  442. FRefFiles.Free;
  443. end;
  444. procedure TLinkChecker.ResolveReferenceLink(Elem: TDOMElement);
  445. var
  446. I: LongInt;
  447. Name, Ref, Link: string;
  448. Resolved: Boolean;
  449. begin
  450. Resolved := False;
  451. Ref := LowerCase(Elem.FirstChild.NodeValue);
  452. if Pos('.pas', Ref) > 1 then
  453. Ref := StringReplace(Ref, '.pas', '_pas', [rfIgnoreCase]);
  454. for I := 0 to FRefFiles.Count - 1 do
  455. begin
  456. Name := LowerCase(ExtractFileName(FRefFiles[I]));
  457. if Pos(Ref, Name) = 1 then
  458. begin
  459. Link := ExtractRelativePath(FIntendedOutput, FRefFiles[I]);
  460. Link := SwapPathDelims(Link, FNewPathDelim);
  461. Elem.SetAttribute(sXURL, Link);
  462. Resolved := True;
  463. Break;
  464. end;
  465. end;
  466. if not Resolved then
  467. WriteLn('Warning: Reference to "' + ExtractFileName(FFileName) + '->' +
  468. Elem.FirstChild.NodeValue + '" not resolved.');
  469. end;
  470. procedure TLinkChecker.CheckElement(Elem: TDOMElement);
  471. var
  472. I: LongInt;
  473. Link: string;
  474. procedure ConvertAttrib(const Name: string);
  475. var
  476. Anchor: string;
  477. begin
  478. Link := Elem.GetAttribute(Name);
  479. if Link <> '' then
  480. begin
  481. // we must handle links to anchors
  482. I := Pos(sXAnchorDelim, Link);
  483. if I > 0 then
  484. begin
  485. Anchor := Copy(Link, I, MaxInt);
  486. Delete(Link, I, MaxInt);
  487. end;
  488. Link := ConvertLink(Link, Name);
  489. if I > 0 then
  490. Link := Link + Anchor;
  491. Elem.SetAttribute(Name, Link);
  492. end;
  493. end;
  494. begin
  495. if Elem.NodeType = ELEMENT_NODE then
  496. begin
  497. // convert links in url, anchor and ref attributes of
  498. // tags like link, listlink, ...
  499. ConvertAttrib(sXLinkTargetURL);
  500. ConvertAttrib(sXLinkTargetAnchor);
  501. // this is only for mail project file and
  502. // converts root and contents file names (needed by xsl stylesheet
  503. // when transforming project file)
  504. if (SameText(Elem.NodeName, sXTOCFile) or
  505. SameText(Elem.NodeName, sXRef) or
  506. SameText(Elem.NodeName, sXRootFile)) and
  507. ((Elem.FirstChild <> nil) and (Elem.FirstChild.NodeValue <> '')) then
  508. begin
  509. Link := Elem.FirstChild.NodeValue;
  510. if not SameText(Elem.NodeName, sXRef) then
  511. Elem.FirstChild.NodeValue := ConvertLink(Link, Elem.NodeName)
  512. else
  513. ResolveReferenceLink(Elem);
  514. end;
  515. end;
  516. for I := 0 to Elem.ChildNodes.Count - 1 do
  517. CheckElement(TDOMElement(Elem.ChildNodes.Item[I]));
  518. end;
  519. function TLinkChecker.ConvertLink(const Link, Context: string): string;
  520. function FileIsInTOC(const Link: string): Boolean;
  521. var
  522. I: LongInt;
  523. S: string;
  524. begin
  525. Result := False;
  526. S := ExtractFileName(SwapPathDelims(Link));
  527. for I := 0 to FProject.FFiles.Count - 1 do
  528. if SameText(ExtractFileName(FProject.FFiles[I]), S) then
  529. begin
  530. Result := True;
  531. Break;
  532. end;
  533. end;
  534. begin
  535. Result := Link;
  536. if Result <> '' then
  537. begin
  538. if FileIsInTOC(Result) then
  539. begin
  540. // for all files in toc (original XML documents)
  541. // we can strip original directory, add new directory,
  542. // change file extension and swap path delimiters
  543. Result := SwapPathDelims(Result);
  544. if FStripDir then
  545. Result := ExtractFileName(Result);
  546. if FDestDir <> '' then
  547. Result := FDestDir + FNewPathDelim + Result;
  548. Result := ChangeFileExt(Result, '.' + FExtension);
  549. Result := SwapPathDelims(Result, FNewPathDelim);
  550. end
  551. else
  552. Result := ResolveExternalLink(Result);
  553. end;
  554. if Assigned(FCustomConverter) then
  555. Result := FCustomConverter(Result, Context);
  556. end;
  557. { TXSLLinker }
  558. procedure TXSLLinker.CheckElement(Elem: TDOMElement);
  559. var
  560. I: LongInt;
  561. Link: string;
  562. procedure ConvertAttrib(const Name: string);
  563. begin
  564. Link := Elem.GetAttribute(Name);
  565. if Link <> '' then
  566. begin
  567. Link := ConvertLink(Link, Name);
  568. Elem.SetAttribute(Name, Link);
  569. end;
  570. end;
  571. begin
  572. if Elem.NodeType = ELEMENT_NODE then
  573. begin
  574. ConvertAttrib(sXSLHref);
  575. ConvertAttrib(sXSLSrc);
  576. end;
  577. for I := 0 to Elem.ChildNodes.Count - 1 do
  578. CheckElement(TDOMElement(Elem.ChildNodes.Item[I]));
  579. end;
  580. function TXSLLinker.ConvertLink(const Link, Context: string): string;
  581. begin
  582. Result := ResolveExternalLink(Link);
  583. if SameText(GetFileExt(Result), sCSSExt) then
  584. ParseCSSFile(Result, Link);
  585. end;
  586. procedure TXSLLinker.ParseCSSFile(const DestCSS, SourceCSS: string);
  587. var
  588. FullPath, FullSourcePath, TheCSS, WriteCSS, Path: string;
  589. Stream: TFileStream;
  590. PStart, PEnd, P: PChar;
  591. Len: LongInt;
  592. I: LongInt;
  593. begin
  594. // first we get path to the destination CSS file which was copied
  595. // do IntendedOutput's dir by previously called ResolveExternalLink
  596. FullPath := SwapPathDelims(DestCSS);
  597. FullPath := ExtractFileDir(FIntendedOutput) + PathDelim +
  598. ExtractFileName(FullPath);
  599. // now we get path to the source CSS file
  600. FullSourcePath := SwapPathDelims(SourceCSS);
  601. FullSourcePath := ExpandFileTo(FullSourcePath, ExtractFileDir(FFileName));
  602. // if both files exist we process them
  603. if FileExists(FullPath) and FileExists(FullSourcePath) then
  604. begin
  605. // we read CSS file to memory string
  606. Stream := TFileStream.Create(FullPath, fmOpenRead);
  607. SetString(TheCSS, nil, Stream.Size);
  608. Stream.Read(TheCSS[1], Length(TheCSS));
  609. Stream.Free;
  610. WriteCSS := TheCSS;
  611. // now we are looking for references to external files in CSS
  612. // (some-property: url(someurl);)
  613. PStart := StrPos(PChar(TheCSS), sCSSLink);
  614. while PStart <> nil do
  615. begin
  616. PStart := StrPos(PStart, sCSSLinkBStart);
  617. PEnd := StrPos(PStart, sCSSLinkBEnd);
  618. if PEnd = nil then
  619. Break;
  620. Inc(PStart, Length(sCSSLinkBStart));
  621. Len := LongInt(PEnd) - LongInt(PStart);
  622. GetMem(P, Len + 1);
  623. StrLCopy(P, PStart, Len);
  624. Path := StrPas(P);
  625. for I := 1 to Length(Path) do
  626. if (Path[I] = '''') or (Path[I] = '"') then
  627. Path[I] := ' ';
  628. Path := Trim(Path);
  629. // now we have parameter of CSS's url() function in Path
  630. // we must resolve this external link but we must
  631. // use path to the source CSS in resolving (we are handling
  632. // paths relative to source CSS)
  633. Path := ResolveExternalLink(Path, FullSourcePath);
  634. // replace old path with new path in dest CSS
  635. WriteCSS := StringReplace(WriteCSS, StrPas(P), Path, [rfReplaceAll]);
  636. FreeMem(P);
  637. PStart := StrPos(PEnd, sCSSLink);
  638. end;
  639. // write dest CSS to the file
  640. Stream := TFileStream.Create(FullPath, fmCreate);
  641. Stream.Write(WriteCSS[1], Length(WriteCSS));
  642. Stream.Free;
  643. end;
  644. end;
  645. end.