(* Name: VisualKeyboardExportHTML Copyright: Copyright (C) SIL International. Documentation: Description: Create Date: 20 Jun 2006 Modified Date: 8 Jun 2012 Authors: mcdurdin Related Files: Dependencies: Bugs: Todo: Notes: History: 20 Jun 2006 - mcdurdin - Initial version 23 Aug 2006 - mcdurdin - Initial refactor for new visual keyboard 04 Dec 2006 - mcdurdin - Support new XML+XSLT OSK export 22 Jan 2007 - mcdurdin - Fix XSLT not loading for loading include files 19 Nov 2007 - mcdurdin - I1157 - const string parameters 29 Sep 2008 - mcdurdin - I1658 - Add support for export graphics options 16 Jan 2009 - mcdurdin - Widestring filenames 04 Jun 2009 - mcdurdin - I2003 - UTF8Encode replacement 03 May 2011 - mcdurdin - I2890 - Record diagnostic data when encountering registry errors 18 May 2012 - mcdurdin - I3306 - V9.0 - Remove TntControls + Win9x support 08 Jun 2012 - mcdurdin - I3337 - V9.0 - Review of input/output for Unicode *) unit VisualKeyboardExportHTML; // I3306 interface uses System.UITypes, Windows, VisualKeyboard, Classes, SysUtils, Graphics, VisualKeyboardParameters; type TVisualKeyboardExportHTML = class(TVisualKeyboardExport) private FFolders, FGraphical: Boolean; public constructor Create(AKbd: TVisualKeyboard; AFolders: Boolean = True; AGraphical: Boolean = True); reintroduce; procedure ExportToFile(FileName: WideString); override; end; implementation uses ActiveX, ComObj, Controls, DebugPaths, Dialogs, ErrorControlledRegistry, KeymanPaths, RegistryKeys, Unicode, utildir, VersionInfo, VisualKeyboardExportXML, MSXML2_TLB, xmldoc, xmlintf; { TVisualKeyboardExportHTML } function GetOSKXSLPath: string; var keyman_root: string; begin Result := ExtractFilePath(ParamStr(0)) + 'xml\osk\'; if not DirectoryExists(Result) then begin if TKeymanPaths.RunningFromSource(keyman_root) then begin Result := keyman_root + 'windows\src\engine\xml\osk\'; end else begin with TRegistryErrorControlled.Create do // I2890 try RootKey := HKEY_LOCAL_MACHINE; if OpenKeyReadOnly(SRegKey_KeymanEngine_LM) and ValueExists(SRegValue_RootPath) then Result := IncludeTrailingPathDelimiter(ReadString(SRegValue_RootPath)) + 'xml\osk\'; finally Free; end; if not DirectoryExists(Result) then Result := ''; end; end; Result := GetDebugPath('Debug_OSKXSLPath', Result, True); end; const { GUID's from MSXML2_TLB.pas } //CLASS_DOMDocument26: TGUID = '{F5078F1B-C551-11D3-89B9-0000F81FE221}'; CLASS_DOMDocument30: TGUID = '{F5078F32-C551-11D3-89B9-0000F81FE221}'; CLASS_DOMDocument40: TGUID = '{88D969C0-F192-11D4-A65F-0040963251E5}'; function TryObjectCreate(const GuidList: array of TGuid): IUnknown; var I: Integer; Status: HResult; begin Status := S_OK; for I := Low(GuidList) to High(GuidList) do begin Status := CoCreateInstance(GuidList[I], nil, CLSCTX_INPROC_SERVER or CLSCTX_LOCAL_SERVER, IDispatch, Result); if Status = S_OK then Exit; end; OleCheck(Status); end; function CreateDOMDocument: IXMLDOMDocument2; begin Result := TryObjectCreate([CLASS_DOMDocument40, CLASS_DOMDocument30]) as IXMLDOMDocument2; if not Assigned(Result) then raise Exception.Create('MS XML DOM 3.0 not installed'); end; constructor TVisualKeyboardExportHTML.Create(AKbd: TVisualKeyboard; AFolders, AGraphical: Boolean); begin inherited Create(AKbd); FFolders := AFolders; FGraphical := AGraphical; end; procedure TVisualKeyboardExportHTML.ExportToFile(FileName: WideString); var doc: IXMLDomDocument2; xsldoc: IXMLDOMDocument2; xslproc: IXSLProcessor; xslt: IXSLTemplate; nodes: IXMLDOMNodeList; FOutput: WideString; //ss: TStringStream; f: TSearchRec; s: string; files: TStringList; FSubdir: WideString; stemp: WideString; i: Integer; begin { Structure for HTML keyboard: file + subdirectory with images; always output as UTF-8? } stemp := ChangeFileExt(FileName, '') + '.xml'; try with TVisualKeyboardExportXML.Create(FKbd) do try ExportToFile(stemp); finally Free; end; files := TStringList.Create; try doc := CreateDOMDocument; try doc.async := False; doc.validateOnParse := False; if not doc.load(stemp) then begin if doc.parseError <> nil then begin ShowMessage('Could not load XML: '+doc.parseError.reason); Exit; end; end; xsldoc := ComsFreeThreadedDOMDocument.Create; try xsldoc.async := False; xsldoc.resolveExternals := True; xsldoc.validateOnParse := False; if not xsldoc.load(GetOSKXSLPath + 'osk.xsl') then begin if xsldoc.parseError <> nil then begin ShowMessage('Could not load transform '+GetOSKXSLPath + 'osk.xsl: '+xsldoc.parseError.reason); Exit; end; end; xsldoc.setProperty('SelectionNamespaces', 'xmlns:oskexportdetails=''http://www.tavultesoft.com/xml/oskexportdetails'''); xsldoc.setProperty('SelectionLanguage', 'XPath'); nodes := xsldoc.documentElement.selectNodes('//oskexportdetails:includefile'); for i := 0 to nodes.length - 1 do files.Add(nodes.item[i].text); xslt := ComsXSLTemplate.Create; xslt.stylesheet := xsldoc; xslproc := xslt.createProcessor; xslproc.input := doc; xslproc.addParameter('graphical', FGraphical, ''); xslproc.addParameter('folders', FFolders, ''); xslproc.transform; FOutput := xslproc.output; // doc.transformNode(xsldoc); finally xsldoc := nil; end; finally doc := nil; end; with TStringList.Create do try Text := FOutput; SaveToFile(FileName, TEncoding.UTF8); // I3337 finally Free; end; if FFolders then begin FSubdir := ChangeFileExt(FileName, '')+'_files'; if DirectoryExists(FSubdir) and not DirectoryEmpty(FSubdir) then begin if MessageDlg('The subdirectory "'+FSubdir+'" already exists. Images for the HTML file will be placed in this '+ 'subdirectory. If you continue, any files currently in the directory will be deleted.'#13#10#13#10+ 'Continue exporting and delete all existing files in the subdirectory?', mtConfirmation, mbOkCancel, 0) = mrCancel then Exit; if not EmptyDirectory(FSubdir) then if MessageDlg('The subdirectory "'+FSubdir+'" was not able to be emptied. Continue exporting anyway?', mtConfirmation, mbOkCancel, 0) = mrCancel then Exit; end; CreateDir(FSubdir); end else FSubdir := ExtractFileDir(FileName); s := GetOSKXSLPath; if FGraphical then for i := 0 to files.Count - 1 do CopyFile(PChar(s+files[i]), PChar(FSubDir+'\'+files[i]), True); finally files.Free; end; s := ChangeFileExt(stemp, '')+'_xml_files\'; if FGraphical then if FindFirst(s + '*', 0, f) = 0 then begin repeat CopyFile(PChar(s+f.Name), PChar(FSubDir+'\'+f.Name), True); until FindNext(f) <> 0; FindClose(f); end; RecursiveDelete(ExcludeTrailingPathDelimiter(s)); finally DeleteFile(stemp); end; end; end.