2011. április 5., kedd

Load TWebBrowser's document content from stream or string


Problem/Question/Abstract:

I hate to face the fact that TWebBrowser does not come with Lines property and LoadFromStream/LoadFromFile method as we all familiar working with TMemo. To create temporary file and then call Navigate/Navigate2 method seems quite awful way.

Answer:

Here I want to show you how to reverse what was done by TWebBrowser.Document's IPersistStreamInit implementation in my previous article "Saving raw HTML source from TWebBrowser to disk". Beside Save() method, it also has Load() method. As you might guess from the method name, the later allows you to load the content from any object that implements IStream.

Here's how to do it:

procedure LoadDocFromStream(WB: TWebBrowser; Stream: TStream);
var
  PersistStreamInit: IPersistStreamInit;
  StreamIntf: IStream;
  StreamAdapter: TStreamAdapter;
begin
  PersistStreamInit := WB as IPersistStreamInit;
  StreamAdapter := TStreamAdapter.Create(Stream);
  StreamIntf := StreamAdapter as IStream;
  PersistStreamInit.Load(StreamIntf);
end;

After knowing which doing what from each statement above, now it is the time to reduce your typing task :)

procedure LoadDocFromStream(WB: TWebBrowser; Stream: TStream);
begin
  (WB.Document as IPersistStreamInit).Load(
    TStreamAdapter.Create(Stream, soReference));
end;

That's all the way to do it. Quite simple eh? If you have a string variable holds your HTML content, simply create TStringStream from it and pass it to second function parameter.

But wait, there's another way to work with string source! For you JavaScript guys whom play often with IE DOM should familiar with this script code:

<script language="JavaScript">
<!--
document.write("<HTML><BODY><H2>Hello World!</H2>/BODY></HTML>");
document.close();
//-->
</script>

Yes, that can be done with Delphi and TWebBrowser too. The main difference is the type of involved object. The first method is using TWebBrowser.Document's IPersistStreamInit implementation (IPersistStreamInit is IPersistStream descendant which is actually exposes Save() and Load() methods). The second is using its IHTMLDocument2 implementation method.

Here's how to do it:

procedure LoadDocFromString(WB: TWebBrowser; const HTMLString: string);
var
  v: OleVariant;
  HTMLDocument: IHTMLDocument2;
begin
  HTMLDocument := WB.Document as IHTMLDocument2;
  v := VarArrayCreate([0, 0], varVariant);
  v[0] := HTMLString;
  HTMLDocument.Write(PSafeArray(TVarData(v).VArray));
  HTMLDocument.Close;
end;

Remember to put ActiveX and MSHTML units to your uses clause (IHTMLDocument2 is declared inside the later unit). If your Delphi version does not come with it, you'll need to import "Microsoft HTML Object Library" from your Delphi IDE or use command line tool $(DELPHI)\bin\tlibimp.exe. In this case, you'll get MSHTML_TLB.pas.

There is a preliminary condition should be met though. All methods above will fail if WebBrowser does not contain valid document. You must load initial document (call it "dummy document") for the first time before using the functions above. I love to hear if anyone knows how to assign a dummy document better than my workaround below. Here's the snippet code:

if not Assigned(WebBrowser.Document) then
  LoadBlankDocAndWaitUntilDocLoaded;
LoadContentFromStringOrStream;

Finally to include the document validity checking, I rewrote all those above and make the final 'ready to use' code:

procedure LoadBlankDoc(WB: TWebBrowser);
begin
  WB.Navigate('about:blank', EmptyParam, EmptyParam, EmptyParam, EmptyParam);
  while WB.ReadyState <> READYSTATE_COMPLETE do
  begin
    Application.ProcessMessages;
    Sleep(0);
  end;
end;

procedure CheckDocReady(WB: TWebBrowser);
begin
  if not Assigned(WB.Document) then
    LoadBlankDoc(WB);
end;

procedure LoadDocFromStream(WB: TWebBrowser; Stream: TStream);
begin
  CheckDocReady(WB);
  (WB.Document as IPersistStreamInit).Load(TStreamAdapter.Create(Stream));
end;

procedure LoadDocFromString(WB: TWebBrowser; const HTMLString: string);
var
  v: OleVariant;
  HTMLDocument: IHTMLDocument2;
begin
  CheckDocReady(WB);
  HTMLDocument := WB.Document as IHTMLDocument2;
  v := VarArrayCreate([0, 0], varVariant);
  v[0] := HTMLString;
  HTMLDocument.Write(PSafeArray(TVarData(v).VArray));
  HTMLDocument.Close;
end;

I have used this method but the images dont appear to load in the html document that is displayed, I think you might need to specify the base location for the file, is there a way of doing this on the fly without have to manually code this into the html document passed to the twebbroswer??

The problem was that the browser failed to know where the document was saved so it couldnt find the images specified in the html code. The way to tell the browser where the original file resided is to insert the tag into the head section, I didnt find a way of making this possible until today using an html parser written by Przemyslaw Jankowski, which I found on http://vclcomponents.com , using this freeware unit I was able to write the following procedures/functions to accomplish the task.

function AdjustFile(HTMLDoc, SpecifiedFolder: string): string;
var
  p: THtmlParser;
  t: TTag;
begin
  p := THtmlParser.Create;
  try
    p.Text := HTMLDoc;
    p.GotoBeginning;
    while p.NextTag do
      if LowerCase(p.Tag.Name) = '/head' then
      begin
        t := TTag.Create;
        t.Name := 'BASE';
        t.Params.Add('HREF="' + SpecifiedFolder + '"');
        p.InsertTag(t);
        p.InsertText(#13#10);
        t.Free;
        Break;
      end;
    Result := p.Text;
  finally
    p.Free;
  end;
end;

procedure LoadDocFromString(WB: TWebBrowser; HTMLString, SpecifiedFolder: string);
var
  v: OleVariant;
  HTMLDocument: IHTMLDocument2;
begin
  CheckDocReady(WB);
  HTMLDocument := WB.Document as IHTMLDocument2;
  v := VarArrayCreate([0, 0], varVariant);
  v[0] := AdjustFile(HTMLString, SpecifiedFolder);
  HTMLDocument.Write(PSafeArray(TVarData(v).VArray));
  HTMLDocument.Close;
end;

SpecifiedFolder is something like:- c:\saved work\html\version 5
ofcourse the above code does not check for the existence of a base tag already or what to do if no head section exists.


Component Download: LoadWBContent.zip

Nincsenek megjegyzések:

Megjegyzés küldése