Lazarus

Programming => Packages and Libraries => FPvectorial => Topic started by: JD on April 16, 2019, 12:06:36 am

Title: [SOLVED] Adding columns to a Word document table (FPVectorial)
Post by: JD on April 16, 2019, 12:06:36 am
Hi there everyone,

I am using the FPVectorial library to create a Word/ODF document that contains a table with 2 columns. According to the example on this page http://wiki.lazarus.freepascal.org/fpvectorial_-_Text_Document_Support#Simple_Table in the Wiki, a table is created with ONE column by default and to add another column, it is necessary to use AddColWidths.

However, AddColWidths is no longer available!! It looks like the code has been refactored and AddColWidths has been removed.

My code is as shown below:

Code: Pascal  [Select]
  1.     AttitudesTable                 := Page.AddTable;
  2.     AttitudesTable.PreferredWidth   := Dimension(100, dimPercent);
  3.     AttitudesTable.ColWidthsUnits   := dimMillimeter;
  4.     AttitudesTable.AddColWidths(50);      <------ This does not work any more!!!
  5.  

How can I create a second column in my Attitudes table?

Thanks a lot for your kind assistance.

Cheers,

JD
Title: Re: Adding columns to a Word document table (FPVectorial)
Post by: JD on April 16, 2019, 01:43:39 pm
Any ideas? Anyone?  :D :D
Title: Re: Adding columns to a Word document table (FPVectorial)
Post by: JD on April 16, 2019, 02:57:52 pm
Alternatively, can one add 2 cells horizontally side by side in the same row?

JD
Title: Re: Adding columns to a Word document table (FPVectorial)
Post by: Mike.Cornflake on April 16, 2019, 06:29:40 pm
I'm away from code right now, but what happens when you run the example code?

Also - I note you have typed AddColWidths.  The call is (or should be) AddColWidth (ie singluar)
Code: Pascal  [Select]
  1. Table.AddColWidth(50);

UPDATE:
Ug.  Looking at the pas file online (https://svn.freepascal.org/svn/lazarus/trunk/components/fpvectorial/fpvectorial.pas), I concur that call is no longer present.  No idea who removed it or why :-(   It'll be more than a few days before I get a chance to look at this though.

UPDATE2:
It's been more than 4 years since I wrote that code.  Looking through docxvectorialwriter.pas.Procedure ProcessTable(ATable: TvTable);  ColWidths is optional.  It's sufficient to add Cells to a row to determine column count.
Reading the comments on the wiki - looks like ColWidths is ONLY essential if you're merging cells AND writing to ODT.

All I can suggest is you try, and in a few days I'll try and understand if a change has been made and why.
Title: Re: Adding columns to a Word document table (FPVectorial)
Post by: JD on April 16, 2019, 07:46:45 pm
@Mike.Cornflake

Thank you very much for your reply. I was getting desperate.  :D

I looked at the code for TvTable. It is defined as follows:

Code: Pascal  [Select]
  1.   (*
  2.       Note on the grid used for the table
  3.  
  4.       For the table shown below, three ColWidths must be defined.
  5.  
  6.       First row should only have 2 cells. First cell spans 2 columns.
  7.       Second row should only have 2 cells. Second cell spans 2 columns.
  8.       Third row should have 3 cells.  Each cell only spans 1 column (default)
  9.  
  10.    X,Y +-----+------+---------+
  11.        |            |         |
  12.        +-----+----------------+
  13.        |     |                |
  14.        +-----+------+---------+
  15.        |     |      |         |
  16.        +-----+------+---------+
  17.  
  18.        The table draws at X,Y and downwards
  19.   *)
  20.  
  21.   // TvTable.Style should be a Table Style, not a Paragraph Style
  22.   // and is optional.
  23.   TvTable = class(TvEntityWithStyle)
  24.   private
  25.     Rows: TFPList;
  26.     ColWidthsInMM: array of Double;   // calculated during Render
  27.     TableWidth, TableHeight: Double;  // in mm; calculated during Render
  28.     procedure CalculateColWidths(constref ARenderInfo: TvRenderInfo);
  29.     procedure CalculateRowHeights(constref ARenderInfo: TvRenderInfo);
  30.   public
  31.     ColWidths: array of Double;       // Can be left empty for simple tables
  32.                                       // MUST be fully defined for merging cells
  33.     ColWidthsUnits : TvUnits;         // Cannot mix ColWidth Units.
  34.     Borders : TvTableBorders;         // Defaults: single/black/inside and out
  35.     PreferredWidth : TvDimension;     // Optional. Units mm.
  36.     SpacingBetweenCells: Double;      // Units mm. Gap between Cells.
  37.     CellSpacingLeft, CellSpacingRight, CellSpacingTop,
  38.       CellSpacingBottom: Double;      // space around each side of cells, in mm
  39.     BackgroundColor : TFPColor;       // Optional.
  40.  
  41.     constructor create(APage : TvPage); override;
  42.     destructor destroy; override;
  43.  
  44.     function AddRow: TvTableRow;
  45.     function GetRowCount : Integer;
  46.     function GetRow(AIndex: Integer) : TvTableRow;
  47.     //
  48.     function GetColCount(): Integer;
  49.     //
  50.     procedure Render(var ARenderInfo: TvRenderInfo; ADoDraw: Boolean = True); override;
  51.     function GenerateDebugTree(ADestRoutine: TvDebugAddItemProc; APageItem: Pointer): Pointer; override;
  52.   end;
  53.  

There does not seem to be any way to add several columns to a table.
By the way, I must congratulate the authors of fpvectorial because it produces perfect Microsoft Word documents.
I was pleasantly surprised. I just need to be able to create multi-columned tables in those Word documents.

Cheers,

JD
Title: Re: Adding columns to a Word document table (FPVectorial)
Post by: Mike.Cornflake on April 16, 2019, 08:44:52 pm
> There does not seem to be any way to add several columns to a table.

Add multiple cells to a row.   Go on, try it :-)   As I said before, looking at docxwriter, this should be sufficient.
Title: Re: Adding columns to a Word document table (FPVectorial)
Post by: JD on April 17, 2019, 11:30:57 am
> There does not seem to be any way to add several columns to a table.

Add multiple cells to a row.   Go on, try it :-)   As I said before, looking at docxwriter, this should be sufficient.

I tried it but the resulting Word document is corrupted and cannot be opened. I added a second Cell := Row.AddCell; a line Cell2 := Row.AddCell; a fluent programming style with Cell := Row.AddCell.Row.AddCell; It all created unreadable Word documents

This is what I'm trying to do. I am trying to create a work schedule document with 2 columns: Days & Hours Worked.

The code I'm using is as follows (of course in the real world, the hours will be input manually):

Code: Pascal  [Select]
  1. procedure TForm1.Button1Click(Sender: TObject);
  2. var
  3.   Document: TvVectorialDocument;
  4.   Page: TvTextPageSequence;
  5.   BoldTextStyle: TvStyle;
  6.   HeaderParagraph, Paragraph: TvParagraph;
  7.   DaysTable: TvTable;
  8.   Row: TvTableRow;
  9.   Cell, Cell2: TvTableCell;
  10.   intRow, intTotalRows: integer;
  11.   DaysArray, HoursWorkedArray: array of string;
  12. begin
  13.   //
  14.   DaysArray := ['DAYS', 'Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
  15.   HoursWorkedArray := ['HOURS WORKED', '4', '7', '8', '8', '4', '8', '0'];
  16.  
  17.   // Create the document
  18.   Document := TvVectorialDocument.Create;
  19.  
  20.   //
  21.   try
  22.      //  Adds the defaut Paragraph Styles
  23.     //    StyleTextBody, StyleHeading1,
  24.     //    StyleHeading2 & StyleHeading3
  25.     Document.AddStandardTextDocumentStyles(vfUnknown);
  26.  
  27.     // Add our own Style
  28.     BoldTextStyle             := Document.AddStyle();
  29.     BoldTextStyle.Kind        := vskTextSpan;
  30.     BoldTextStyle.Name        := 'Bold';
  31.     BoldTextStyle.Font.Bold   := True;
  32.     BoldTextStyle.SetElements := BoldTextStyle.SetElements + [spbfFontBold];
  33.  
  34.     // Add a page and a paragraph
  35.     Page                      := Document.AddTextPageSequence;
  36.     //
  37.     HeaderParagraph           := Page.AddParagraph;
  38.     HeaderParagraph.Style     := Document.StyleHeading1Centralized;
  39.     HeaderParagraph.AddText('WORK SCHEDULE');
  40.     HeaderParagraph           := Page.AddParagraph;
  41.  
  42.     // Add the Days table
  43.     DaysTable                  := Page.AddTable;
  44.     DaysTable.PreferredWidth   := Dimension(100, dimPercent);
  45.     DaysTable.ColWidthsUnits   := dimMillimeter;
  46.  
  47.     // Get the number of rows to add to the table
  48.     intTotalRows := Length(DaysArray);
  49.     //
  50.     for intRow := 0 to Pred(intTotalRows) do
  51.     begin
  52.       Row                            := DaysTable.AddRow;
  53.       if intRow = 0 then
  54.       begin
  55.         Row.BackgroundColor := RGBToFPColor(192, 192, 192); // Grey Shading
  56.         Row.Header := True; // Tell the table this is a Header Row
  57.       end;
  58.       Cell                           := Row.AddCell;
  59.       Paragraph                      := Cell.AddParagraph;
  60.       Paragraph.Style                := Document.StyleTextBody;
  61.       Paragraph.AddText(DaysArray[intRow]).Style := BoldTextStyle;
  62.     end;
  63.  
  64.     try
  65.       // Save the document
  66.       Document.WriteToFile('Work schedule.docx', vfDOCX);
  67.     except
  68.       raise;
  69.     end;
  70.   finally
  71.     Document.Free;
  72.   end;
  73. end;
  74.  

The resulting Microsoft Word document created from the code above is in the attachment. This is where I am as of today. Any help will be greatly appreciated.

Thanks,

JD
Title: Re: Adding columns to a Word document table (FPVectorial)
Post by: Mike.Cornflake on April 17, 2019, 12:01:14 pm
Yup - as you say - it's broken.  I've confirmed the full write test I wrote no longer works.

Here's the original function

Code: Pascal  [Select]
  1. function TvTable.AddColWidth(AValue: Double): Integer;
  2. begin
  3.   SetLength(ColWidths, Length(ColWidths) + 1);
  4.   ColWidths[High(ColWidths)] := AValue;
  5. end;

ColWidths is an array of double, and it's in TvTable.Public.   So you can implement your own AddColWidth and see if that produces a working docx :-(

Sorry about this :-(

Mike

UPDATE:

In order to get fpvtextwritetest2 to run, I added the following helper routine.

Code: Pascal  [Select]
  1.     Procedure AddColWidth(ATable: TvTable; AValue: Double);
  2.     begin
  3.       SetLength(ATable.ColWidths, Length(ATable.ColWidths) + 1);
  4.       ATable.ColWidths[High(ATable.ColWidths)] := AValue;
  5.     end;    

I then changed all calls from
Code: Pascal  [Select]
  1.     CurTable.AddColWidth( 15);
  2.     CurTable.AddColWidth(20);
  3.     CurTable.AddColWidth(20);
  4.     CurTable.AddColWidth(20);
  5.     CurTable.AddColWidth(79.5); // For Word (and possibly odt), this only has to be close.

to

Code: Pascal  [Select]
  1.     AddColWidth(CurTable, 15);
  2.     AddColWidth(CurTable, 20);
  3.     AddColWidth(CurTable, 20);
  4.     AddColWidth(CurTable, 20);
  5.     AddColWidth(CurTable, 79.5); // For Word (and possibly odt), this only has to be close.

Result Word Document was well formed and worked.

When I'm back on land I'll submit a patch and get the original code restored.   Either that or get the author of revision 55389 to update the wiki.  My preference is original code restored.


Title: Re: Adding columns to a Word document table (FPVectorial)
Post by: JD on April 17, 2019, 12:38:00 pm
@Mike.Cornflake

No need to be sorry.

I added a class helper to my code as follows:

Code: Pascal  [Select]
  1. type
  2.   { TvTableHelper }
  3.   TvTableHelper = class helper for TvTable
  4.   public
  5.     function AddColWidth(AValue: Double): Integer;
  6.   end;
  7.  
  8. { TvTableHelper }
  9.  
  10. function TvTableHelper.AddColWidth(AValue: Double): Integer;
  11. begin
  12.   SetLength(ColWidths, Length(ColWidths) + 1);
  13.   ColWidths[High(ColWidths)] := AValue;
  14. end;
  15.  
  16.  

My new document creation code now becomes:

Code: Pascal  [Select]
  1. procedure TForm1.Button1Click(Sender: TObject);
  2. var
  3.   Document: TvVectorialDocument;
  4.   Page: TvTextPageSequence;
  5.   BoldTextStyle: TvStyle;
  6.   HeaderParagraph, Paragraph: TvParagraph;
  7.   DaysTable: TvTable;
  8.   Row: TvTableRow;
  9.   Cell, Cell2: TvTableCell;
  10.   intRow, intTotalRows: integer;
  11.   DaysArray, HoursWorkedArray: array of string;
  12. begin
  13.   //
  14.   DaysArray := ['DAYS', 'Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
  15.   HoursWorkedArray := ['HOURS WORKED', '4', '7', '8', '8', '4', '8', '0'];
  16.  
  17.   // Create the document
  18.   Document := TvVectorialDocument.Create;
  19.  
  20.   //
  21.   try
  22.      //  Adds the defaut Paragraph Styles
  23.     //    StyleTextBody, StyleHeading1,
  24.     //    StyleHeading2 & StyleHeading3
  25.     Document.AddStandardTextDocumentStyles(vfUnknown);
  26.  
  27.     // Add our own Style
  28.     BoldTextStyle             := Document.AddStyle();
  29.     BoldTextStyle.Kind        := vskTextSpan;
  30.     BoldTextStyle.Name        := 'Bold';
  31.     BoldTextStyle.Font.Bold   := True;
  32.     BoldTextStyle.SetElements := BoldTextStyle.SetElements + [spbfFontBold];
  33.  
  34.     // Add a page and a paragraph
  35.     Page                      := Document.AddTextPageSequence;
  36.     //
  37.     HeaderParagraph           := Page.AddParagraph;
  38.     HeaderParagraph.Style     := Document.StyleHeading1Centralized;
  39.     HeaderParagraph.AddText('WORK SCHEDULE');
  40.     HeaderParagraph           := Page.AddParagraph;
  41.  
  42.     // Add the Days table
  43.     DaysTable                  := Page.AddTable;
  44.     DaysTable.PreferredWidth   := Dimension(100, dimPercent);
  45.     DaysTable.ColWidthsUnits   := dimMillimeter;
  46.     // Add 2 columns
  47.     DaysTable.AddColWidth(50);
  48.     DaysTable.AddColWidth(50);
  49.  
  50.     // Get the number of rows to add to the table
  51.     intTotalRows := Length(DaysArray);
  52.     //
  53.     for intRow := 0 to Pred(intTotalRows) do
  54.     begin
  55.       Row                            := DaysTable.AddRow;
  56.       if intRow = 0 then
  57.       begin
  58.         Row.BackgroundColor := RGBToFPColor(192, 192, 192); // Grey Shading
  59.         Row.Header := True; // Tell the table this is a Header Row
  60.       end;
  61.       Cell                           := Row.AddCell;
  62.       Paragraph                      := Cell.AddParagraph;
  63.       Paragraph.Style                := Document.StyleTextBody;
  64.       Paragraph.AddText(DaysArray[intRow]).Style := BoldTextStyle;
  65.     end;
  66.  
  67.     try
  68.       // Save the document
  69.       Document.WriteToFile('Work schedule.docx', vfDOCX);
  70.     except
  71.       raise;
  72.     end;
  73.   finally
  74.     Document.Free;
  75.   end;
  76. end;
  77.  

But it stll creates a table with one column!! What is it that I am missing?

Thanks a lot for your assistance,

JD
Title: Re: Adding columns to a Word document table (FPVectorial)
Post by: JD on April 17, 2019, 12:39:51 pm
I didn't see the updates you added to your last post. I'll try them now.....
Title: Re: Adding columns to a Word document table (FPVectorial)
Post by: JD on April 17, 2019, 12:56:03 pm
It worked!!!! Thanks a lot Mike.Cornflake.

I'll post the final solution later today to help those who might be facing a similar problem.

JD
Title: Re: Adding columns to a Word document table (FPVectorial)
Post by: Mike.Cornflake on April 17, 2019, 12:59:59 pm
I've never used Class Helpers.  Can't see why your code didn't work, but glad we finally got there :-)   

Many thanks for your patience with this issue.
Title: Re: Adding columns to a Word document table (FPVectorial)
Post by: JD on April 17, 2019, 01:03:28 pm
I've never used Class Helpers.  Can't see why your code didn't work, but glad we finally got there :-)   

Many thanks for your patience with this issue.

The code with the class helper and your own code all work perfectly now.  :D

Class helpers extend the functionality of existing classes without the need of modifying the classes themselves. I use them when I am not the author of a class and I either don't have the source code for the class OR I don't want to modify it so that I don't break anything.

JD
Title: Re: Adding columns to a Word document table (FPVectorial)
Post by: JD on April 17, 2019, 04:20:20 pm
Here is the sample published project in case anyone has a similar problem.

JD
Title: Re: Adding columns to a Word document table (FPVectorial)
Post by: qindj on October 24, 2019, 03:10:23 pm
Here is the sample published project in case anyone has a similar problem.

JD

Hi, JD

thanks for your example, it's very helpful.

I have another question, do you know why we should add a space after a single char , otherwise it will become empty?

As i tested, every single char such as 'A', 'B', or '1', '2'... will be empty in saved docx or odt file
Title: Re: [SOLVED] Adding columns to a Word document table (FPVectorial)
Post by: Mike.Cornflake on October 24, 2019, 09:05:58 pm
G'day,

Heh.  I've only been offshore twice this year, and both times bugs in my code have been found.

Apologies @JD, when I got back onshore I completely forgot to follow up and get the trunk code fixed.  And I see CellSpacing has now failed


Quote
As i tested, every single char such as 'A', 'B', or '1', '2'... will be empty in saved docx or odt file

@qindj
Right - I've confirmed that indeed there is a bug.  I'll get to it, but in the meantime you're going to need to keep appending those spaces. 

I'm away from IDE at the moment (Yes, offshore again working 12 hours shifts).

The following are my quick notes to assist with chasing this when I have time.   I'm planned to be back on shore Nov 4, so I should reply sometime in the weekend following that.  If you haven't heard, ping me (pm or here) and remind me.

The issue is likely in docxvectorialwriter.pas,

Line 703.  For some reason AddTextRun doesn't look like it's being run (in the xml of the resulting word document, not only is the there no text, but there's no w:r tag written either, and that's done in AddtextRun).  Oh, or AddTextRun  is being passed an empty string.  Ah, that's makes more sense.  If it's being passed with an empty string, then the following code needs to be tightened to ONLY strip CR or LF. 

Line 696:
Code: Pascal  [Select]
  1.         // Strip out the trailing line break
  2.         // added by TStringList.Text
  3.         If DefaultTextLineBreakStyle = tlbsCRLF Then
  4.           sTemp := Copy(sTemp, 1, Length(sTemp) - 2)
  5.         Else
  6.           sTemp := Copy(sTemp, 1, Length(sTemp) - 1);  

If my suspicion is right about that code being overzealous and stripping two chars when there is only one to be stripped, then I'd expect missing characters all over the place.  Replacement code for the above should be TrimChars(<string>, [CR, LF]).  There will be one floating around somewhere.  Not sure, but have to resume work now :-(

Cheers

Mike

UPDATE:   OK, I've broken out an IDE.  No, it wasn't the above code.

Issue is actually in AddTextRun.  I'm parsing over the passed string - looking for internal LF, CR, TAB, so I can turn large collections of text in to actual word paragraphs.

Except, I start my looking at the wrong index :-(

Quick fix:
Line 554 of docxvectorialwriter.pas needs to be changed to:
Code: Pascal  [Select]
  1.           If i >= iStart Then    
(it was previously i > iStart, so worked for strings with length > 1)

Better fix:
Mike remembers this time and supplies an actual patch, fixing both this and @JD's issue.


Title: Re: [SOLVED] Adding columns to a Word document table (FPVectorial)
Post by: qindj on October 25, 2019, 08:05:56 am
@Mike

Thank you for your time & explaination, I use the fix you suggested and It works now (Single char without space)