[Example] Document structure as a tree

Demos, code samples. Only questions related to the existing topics are allowed here.
Post Reply
Sergey Tkachenko
Site Admin
Posts: 17559
Joined: Sat Aug 27, 2005 10:28 am
Contact:

[Example] Document structure as a tree

Post by Sergey Tkachenko »

Update: the code in the message is for TRichView 17.2 and older. For TRichView 17.3 and newer, see viewtopic.php?f=3&t=3627&p=34717#p34717

The code below shows a document structure in TreeView.

The first level of this structure is "Document".

The second level: paragraphs (in TRichView, paragraphs are not represented as objects; items simply have "new paragraph" flags; but for the convenience of displaying a structure, paragraphs are shown as a structure level). For paragraphs, the following properties are shown:
- Outline level (if positive, it is named 'Heading level N' instead of 'Paragraph')
- ParaNo (index of paragraph style)
- page break (if this paragraph has "page break before" flag)
- text flow properties (clear left/right/both, if specified)

The third level: items. For text items, their text is shown. For non-text items, class of their items is shown. Index of text style/item type (StyleNo) is shown.
If the item is a hyperlink, or it has a non-empty tag, this information is shown.

If some item starts a line break inside a paragraph, "line break" is added before this item.

The fourth level: checkpoint (if the item has it). A name is displayed for the checkpoint.

Special cases:
1) Tables. For tables, the fourth level is rows, the fifth level is cells. Each cell is a document with its own structure levels.
2) Footnotes and endnotes. For notes, the fourth level is "Note text". This is a document with its own structure levels.
3) List markers. Information about list marker is shown in the fourth level.

Code: Select all

procedure ShowStructure(RVData: TCustomRVData; Node: TTreeNode); 
var i, StyleNo: Integer; 
    ParaNode, ItemNode: TTreeNode; 

    function GetExtraBreakProps(i: Integer): String; 
    begin 
      if RVData.PageBreaksBeforeItems[i] then begin 
        Result := '; page break'; 
        exit; 
      end;
      if RVData.ClearLeft[i] then 
        if RVData.ClearRight[i] then 
          Result := '; clear both' 
        else 
          Result := '; clear left' 
      else 
        if RVData.ClearRight[i] then 
          Result := '; clear right' 
        else 
          Result := ''; 
    end; 

    function GetExtraItemString(i: Integer): String; 
    begin 
      Result := ''; 
      if RVData.GetItem(i).GetBoolValueEx(rvbpJump, RVData.GetRVStyle) then 
        Result := '; hyperlink';
      if RVData.GetItemTag(i)<>'' then 
          Result := Result+ '; tag: "'+RVData.GetItemTag(i)+'"';
    end; 

    procedure AddCheckpoint(i: Integer); 
    var CPTag: TRVTag; 
        CPName: String; 
        CPRE: Boolean; 
    begin 
      if RVData.GetItemCheckpoint(i)<>nil then begin 
        RVData.GetCheckpointInfo(RVData.GetItemCheckpoint(i), CPTag, CPName, CPRE); 
        Node.Owner.AddChild(ItemNode, Format('Checkpoint "%s"', 
          [CPName])); 
      end; 
    end;

    procedure AddTableInfo(i: Integer); 
    var r, c: Integer; 
      table: TRVTableItemInfo; 
      RowNode, CellNode: TTreeNode; 
    begin 
      table := TRVTableItemInfo(RVData.GetItem(i)); 
      for r := 0 to table.RowCount-1 do begin 
        RowNode := Node.Owner.AddChild(ItemNode, Format('Row %d', [r])); 
        for c := 0 to table.ColCount-1 do begin 
          if table.Cells[r,c]<>nil then begin 
            CellNode := Node.Owner.AddChild(RowNode, Format('Cell %d', [c])); 
            ShowStructure(table.Cells[r,c].GetRVData, CellNode); 
          end; 
        end; 
      end 
    end;

    procedure AddListMarkerInfo(i: Integer); 
    var ListNo, Level, StartFrom: Integer; 
       UseStartFrom: Boolean; 
       s: String; 
    begin 
      RVData.GetListMarkerInfo(i, ListNo, Level, StartFrom, UseStartFrom); 
      s := Format('ListNo=%d, Level=%d',[ListNo, Level]); 
      if UseStartFrom then 
        s := s+Format(', start from %d', [StartFrom]); 
      Node.Owner.AddChild(ItemNode, s);
    end;

    function GetParaCaption(i: Integer): String;
    var Level: Integer;
    begin
      Level := RVData.GetRVStyle.ParaStyles[RVData.GetItemPara(i)].OutlineLevel;
      if Level<=0 then
        Result := 'Paragraph'
      else
        Result := Format('Heading level %d', [Level]);
    end;

begin
  ParaNode := nil;
  for i := 0 to RVData.ItemCount-1 do begin
    if RVData.IsParaStart(i) then
      ParaNode := Node.Owner.AddChild(Node, Format('%s, ParaNo=%d%s',
        [GetParaCaption(i), RVData.GetItemPara(i), GetExtraBreakProps(i)]))
    else if RVData.IsFromNewLine(i) then
      Node.Owner.AddChild(ParaNode, Format('Line break%s',
        [GetExtraBreakProps(i)]));
    StyleNo := RVData.GetItemStyle(i);
    if StyleNo>=0 then
      ItemNode := Node.Owner.AddChild(ParaNode, Format('Text "%s", StyleNo=%d%s',
        [RVData.GetItemText(i), StyleNo, GetExtraItemString(i)]))
    else begin
      ItemNode := Node.Owner.AddChild(ParaNode, Format('%s [StyleNo=%d]%s',
        [RVData.GetItem(i).ClassName, StyleNo, GetExtraItemString(i)]));
      if RVData.GetItem(i) is TRVTableItemInfo then
        AddTableInfo(i)
      else if RVData.GetItem(i) is TCustomRVNoteItemInfo then
        ShowStructure(TCustomRVNoteItemInfo(RVData.GetItem(i)).Document,
          Node.Owner.AddChild(ItemNode, 'Note text'))
      else if RVData.GetItem(i) is TRVMarkerItemInfo then
        AddListMarkerInfo(i);
    end; 
    AddCheckpoint(i); 
  end; 
end;
How to use:

Code: Select all

  TreeView1.Items.BeginUpdate;
  TreeView1.Items.Clear;
  TreeView1.Items.AddChildFirst(nil, 'Document');
  ShowStructure(RichViewEdit1.RVData, TreeView1.Items[0]);
  TreeView1.Items.EndUpdate;
Updates:
2011-Mar-31: displaying paragraph outline level
2011-Oct-2: for compatibility with TRichView 13.4
Last edited by Sergey Tkachenko on Sun Oct 02, 2011 7:57 pm, edited 2 times in total.
jonjon
Posts: 467
Joined: Sat Aug 27, 2005 4:19 pm

Post by jonjon »

Some additions to highlight the correct parts:

- In ShowStructure, instead of AddChild use AddChildObject. Example:

Code: Select all

// Node.Owner.AddChild(ItemNode, s);
Node.Owner.AddChildObject(ItemNode, s, Pointer(i));
- On mouse over the TreeView:

Code: Select all

procedure TForm1.TreeView1MouseMove(Sender: TObject; Shift: TShiftState; X,
  Y: Integer);
var
  OverItem: TTreeNode;
  RVItemNb: Integer;
begin
  OverItem := TreeView1.GetNodeAt(X, Y);
  if (assigned(OverItem)) then
  begin
    RVItemNb := Integer(OverItem.Data);
    StatusBar1.SimpleText := 'Current Item #' + IntToStr(RVItemNb);
    Self.SelectRVItem(RVItemNb);
  end;
end;
- On mouse over the TRichViewEdit:

Code: Select all

procedure TForm1.RichViewEdit1MouseMove(Sender: TObject; Shift: TShiftState; X,
  Y: Integer);
var
  RVData: TCustomRVFormattedData;
  ItemNo, OffsetInItem: integer;
  I: Integer;
begin
  if RichViewEdit1.GetItemAt(X, Y, RVData, ItemNo, OffsetInItem, True) then
  begin
    Self.SelectRVItem(ItemNo);
    GetNodeByData(TreeView1, ItemNo, True);
  end;
end;
- Helper functions:

Code: Select all

function TForm1.GetNodeByData(ATree : TTreeView; AData: Integer; AVisible: Boolean): TTreeNode;
var
  Node: TTreeNode;
begin
  Result := nil;
  if ATree.Items.Count = 0 then Exit;
  Node := ATree.Items[0];
  while Node <> nil do
  begin
    if Integer(Node.Data) = AData then
    begin
      Result := Node;
      if AVisible then
      begin
        Result.MakeVisible;
        Result.Selected := True;
        ATree.SetFocus;
      end;
      Break;
    end;
    Node := Node.GetNext;
  end;
end;

procedure TForm1.SelectRVItem(anItem: integer);
var
  Offs1, Offs2: integer;
begin
  Offs1 := RichViewEdit1.RVData.GetOffsBeforeItem(anItem);
  Offs2 := RichViewEdit1.RVData.GetOffsAfterItem(anItem);
  RichViewEdit1.RVData.SetSelectionBounds(anItem, Offs1, anItem, Offs2);
  RichViewEdit1.RVData.Invalidate;
end;
allanj42
Posts: 10
Joined: Mon Mar 02, 2009 11:56 pm
Location: Canada

C++ Version?

Post by allanj42 »

Is there a C++ version of the document tree utility, preferably with the interactive enhancements?
Sergey Tkachenko
Site Admin
Posts: 17559
Joined: Sat Aug 27, 2005 10:28 am
Contact:

Post by Sergey Tkachenko »

The simplest way is creating a new pas-unit, copying this procedure to this unit, and including this pas-unit in C++project.
HPP-file will be generated on compiling.
Petko
Posts: 174
Joined: Tue Sep 06, 2005 12:42 pm

Post by Petko »

Sergey, does this utility show paragraph outline levels?
Sergey Tkachenko
Site Admin
Posts: 17559
Joined: Sat Aug 27, 2005 10:28 am
Contact:

Post by Sergey Tkachenko »

I updated the code to show them.
Petko
Posts: 174
Joined: Tue Sep 06, 2005 12:42 pm

Post by Petko »

Thanks!
Sergey Tkachenko
Site Admin
Posts: 17559
Joined: Sat Aug 27, 2005 10:28 am
Contact:

Re: [Example] Document structure as a tree

Post by Sergey Tkachenko »

Code for TRichView 17.3 and newer:

Code: Select all

procedure ShowStructure(RVData: TCustomRVData; Node: TTreeNode);
var i, StyleNo: Integer;
    ParaNode, ItemNode: TTreeNode;

    function GetExtraBreakProps(i: Integer): String;
    begin
      if RVData.PageBreaksBeforeItems[i] then
      begin
        Result := '; page break';
        exit;
      end;
      if RVData.ClearLeft[i] then
        if RVData.ClearRight[i] then
          Result := '; clear both'
        else
          Result := '; clear left'
      else
        if RVData.ClearRight[i] then
          Result := '; clear right'
        else
          Result := '';
    end;

    function GetExtraItemString(i: Integer): String;
    begin
      Result := '';
      if RVData.GetItem(i).GetBoolValueEx(rvbpJump, RVData.GetRVStyle) then
        Result := '; hyperlink';
      if RVData.GetItemTag(i)<>'' then
          Result := Result+ '; tag: "'+RVData.GetItemTag(i)+'"';
    end;

    procedure AddCheckpoint(i: Integer);
    var CPTag: TRVTag;
        CPName: TRVUnicodeString;
        CPRE: Boolean;
    begin
      if RVData.GetItemCheckpoint(i)<>nil then
      begin
        RVData.GetCheckpointInfo(RVData.GetItemCheckpoint(i), CPTag, CPName, CPRE);
        Node.Owner.AddChild(ItemNode, Format('Checkpoint "%s"',
          [CPName]));
      end;
    end;

    procedure AddTableInfo(i: Integer);
    var r, c: Integer;
      table: TRVTableItemInfo;
      RowNode, CellNode: TTreeNode;
    begin
      table := TRVTableItemInfo(RVData.GetItem(i));
      for r := 0 to table.RowCount-1 do
      begin
        RowNode := Node.Owner.AddChild(ItemNode, Format('Row %d', [r]));
        for c := 0 to table.ColCount-1 do
        begin
          if table.Cells[r,c]<>nil then
          begin
            CellNode := Node.Owner.AddChild(RowNode, Format('Cell %d', [c]));
            ShowStructure(table.Cells[r,c].GetRVData, CellNode);
          end;
        end;
      end
    end;

    procedure AddListMarkerInfo(i: Integer);
    var ListNo, Level, StartFrom: Integer;
       UseStartFrom: Boolean;
       s: String;
    begin 
      RVData.GetListMarkerInfo(i, ListNo, Level, StartFrom, UseStartFrom); 
      s := Format('ListNo=%d, Level=%d',[ListNo, Level]); 
      if UseStartFrom then 
        s := s+Format(', start from %d', [StartFrom]); 
      Node.Owner.AddChild(ItemNode, s);
    end;

    function GetParaCaption(i: Integer): String;
    var Level: Integer;
    begin
      Level := RVData.GetRVStyle.ParaStyles[RVData.GetItemPara(i)].OutlineLevel;
      if Level<=0 then
        Result := 'Paragraph'
      else
        Result := Format('Heading level %d', [Level]);
    end;

begin
  ParaNode := nil;
  for i := 0 to RVData.ItemCount-1 do
  begin
    if RVData.IsParaStart(i) then
      ParaNode := Node.Owner.AddChild(Node, Format('%s, ParaNo=%d%s',
        [GetParaCaption(i), RVData.GetItemPara(i), GetExtraBreakProps(i)]))
    else if RVData.IsFromNewLine(i) then
      Node.Owner.AddChild(ParaNode, Format('Line break%s',
        [GetExtraBreakProps(i)]));
    StyleNo := RVData.GetItemStyle(i);
    if StyleNo>=0 then
      ItemNode := Node.Owner.AddChild(ParaNode, Format('Text "%s", StyleNo=%d%s',
        [RVData.GetItemText(i), StyleNo, GetExtraItemString(i)]))
    else
    begin
      ItemNode := Node.Owner.AddChild(ParaNode, Format('%s [StyleNo=%d]%s',
        [RVData.GetItem(i).ClassName, StyleNo, GetExtraItemString(i)]));
      if RVData.GetItem(i) is TRVTableItemInfo then
        AddTableInfo(i)
      else if RVData.GetItem(i) is TCustomRVNoteItemInfo then
        ShowStructure(TCustomRVNoteItemInfo(RVData.GetItem(i)).Document,
          Node.Owner.AddChild(ItemNode, 'Note text'))
      else if RVData.GetItem(i) is TRVMarkerItemInfo then
        AddListMarkerInfo(i);
    end;
    AddCheckpoint(i);
  end;
end;
Post Reply