Home General
New Blog Posts: Merging Reports - Part 1 and Part 2

Hash value of a report

edited October 2013 in General
Hi,

Probably a weird question to ask, but I was wondering if anyone has any
suggestions on how I could go about checking / verifying when a report
is printed to see if it matches exactly to a previous one that has been
printed?

Is there a way to perform a hash tag on the report contents as such, or
what would be the best method to compare one report to another to see if
they are the same.

I'm mainly just wanting to verify text fields, so I'm happy if it only
checks text - but am equally as happy if the process also checks images
/ lines shapes / etc.

Thanks & regards

Adam

Comments

  • edited October 2013
    Hi Adam,

    When a report is printed, a TppPage object is created for each page.
    Each TppPage object is essentially a list of DrawCommand objects
    containing instructions how to render each report component.

    If you have an archive of a previously printed report, you can easily
    print that report with the archive reader's Publisher.CachePages
    property set to True to obtain a list of the Page objects generated.
    Then with the new report's CachePages set to true, you would do the same
    thing with your newly generated report. From there you can access each
    list of page objects (Publisher.Pages[]) and compare them as you like.

    I suggest taking a look at the TppPage class located in the ppDevice.pas
    file to become familiar with its properties.


    Best Regards,

    Nico Cizik
    Digital Metaphors
    http://www.digital-metaphors.com
  • edited October 2013
    Hi Nico,



    Thanks for the suggestion. However, I'm not wanting to save the entire
    report (that would require a large bit of storage for the amount of
    reports that will be generated in the end )

    Rather, I would like to generate a hash tag from the report when
    printing, and compare it to a previous one to see if they match.

    I was wondering if it was possible to do something such as printing the
    report to a stream, and then hashing that stream?

    I've tried (using your suggestion) to generate a hash using the cache
    with the following code, but I get an access violation when calling
    GetPageFromCache.

    I was just wondering if you might have any suggestions?

    (The report is set to twopass:)

    Thanks & Regards

    Adam.

    -----------------------

    procedure TForm1.Button1Click(Sender: TObject);
    begin
    ppreport1.print;
    end;

    function idbytestohex(b1 : TidBytes) : Widestring;
    var
    i : integer;
    s : string;
    begin
    s := '';
    for i := 0 to length(b1) do
    s := s + bytetohex(b1[i]);
    result := s;
    end;

    Function HashReport(Report: TppReport) : TIdBytes;
    var
    i : integer;
    j : integer;
    ms : TMemoryStream;
    Hash : TIDHash;
    LongHash : WideString;
    b1 : TidBytes;
    EndHash : Tidbytes;

    begin
    ms := tmemorystream.create;

    LongHash := '';

    // First go through each page and generate a has for it, adding all the
    hashes together
    for i := 0 to Report.CacheManager.CachePageCount - 1 do
    for j := 0 to Report.Publisher.GetPageFromCache(i).DrawCommandCount - 1 do
    begin

    Report.Publisher.GetPageFromCache(i).DrawCommands[j].AsMetaFile.SaveToStream(MS);
    b1 := hash.hashstream(ms);
    LongHash := LongHash + idbytestohex(b1);
    end;

    Endhash := hash.HashString(LongHash);
    result := endhash;
    end;

    procedure TForm1.ppReport1StartSecondPass(Sender: TObject);
    var
    hash : TIdBytes;
    begin
    hash := HashReport(Tppreport(sender));
    showmessage(idbytestohex(hash));

    end;
  • edited October 2013
    Hi Adam,

    The solution I presented was a general one. It is not a built-in
    feature of ReportBuilder.

    The basic idea is to extract the page objects from each report and
    compare the two. How you access the page objects and what you do with
    them is completely up to you.

    Some Notes:

    1. In your code, it is not possible to access the page cache after the
    first pass of a report.

    2. Take a look at the following example and article. There is code that
    prints a report to a generic device to cache the pages for later use.

    http://www.digital-metaphors.com/rbWiki/Delphi_Code/Layouts/How_To...Create_a_Spread_Sheet_Style_Report

    3. It is also possible to access the page objects as they are generated
    by implementing the Device.OnPageReceive event.

    Best Regards,

    Nico Cizik
    Digital Metaphors
    http://www.digital-metaphors.com
  • edited October 2013
    Hi Nico,

    Thanks for your reply. It looks as though this might be a bit more
    difficult than I first thought.

    I'll check out the example and see what I can find.

    Thanks & Regards

    Adam
  • edited October 2013
    Hi Nico,

    I've been trying to use the example that you've provided on your link to
    implement into my code, but I'm still experiencing problems.

    The problem I face is that I am still unable to access the cached pages.

    lPublisher.GetPageFromCache(i) is returning nil.


    Here is the modified code that I am now using. I was wondering if you
    could please advise where I've gone wrong?

    Thanks & Regards

    Adam.

    ---------------

    function idbytestohex(b1 : TidBytes) : Widestring;
    var
    i : integer;
    s : string;
    begin
    s := '';
    for i := 0 to length(b1) do
    s := s + bytetohex(b1[i]);
    result := s;
    end;


    procedure TForm1.Button1Click(Sender: TObject);
    var
    lDevice: TppDevice;
    lPages : TList;
    i, j : integer;
    lpublisher : TppPublisher;
    lpage : tppPage;
    lclonepage : TppPage;
    ms : TMemoryStream;
    Hash : TIDHash;
    LongHash : WideString;
    b1 : TidBytes;
    EndHash : Tidbytes;
    begin

    lDevice := TppDevice.Create(Self);
    lDevice.PageSetting := psLastPage;
    lDevice.Publisher := ppReport1.Publisher;

    ppReport1.CachePages := True;
    ppReport1.PrintToDevices;

    lPages := TList.Create;
    lPublisher := ppReport1.Publisher;

    MS := TMemoryStream.create;

    // work through all pages
    for i := 0 to lPublisher.PageCount-1 do
    for j := 0 to lPublisher.Pages[i].DrawCommandCount - 1 do
    begin

    {******FAILS HERE. GetPageFromCache is Nil******}
    lPublisher.GetPageFromCache(i).DrawCommands[j].AsMetaFile.SaveToStream(MS);
    b1 := hash.hashstream(ms);
    LongHash := LongHash + idbytestohex(b1);
    end;

    lDevice.Free;


    Endhash := hash.HashString(LongHash);

    showmessage(idbytestohex(endhash));
    end;
  • edited October 2013
    Hi Adam,

    1. GetPageFromCache expects a page number as its parameter. You are
    starting with page 0 which does not exist. If you take a closer look at
    the example, it uses Publisher.Pages[] property to access a 0-based page
    list.

    2. Not all drawcommands implement the AsMetaFile routine. You are going
    to want to check if this returns nil or you may run into more issues.

    Best Regards,

    Nico Cizik
    Digital Metaphors
    http://www.digital-metaphors.com
This discussion has been closed.