IText7 nested table on auto layout column - c#

I have to create a document with tables and nested tables. When the nested table is added to a column with a fixed width (in my example this is the first column with a width of 150pt) the nested table looks as expected (first column auto, second 70pt). But If I add the same nested table to a column where the width should automatically be adapted the columns of the nested table seems to be changed to auto as well. Is there something I have missed?
nested table example
The code to generate this example looks as follow
public void Test(){
//using (var stream = new MemoryStream())
var file = string.Format(#"c:\sample{0}.pdf", Guid.NewGuid());
var rand = new Random();
using (var pdfDoc = new PdfDocument(new PdfWriter(file)))
using (var doc = new Document(pdfDoc))
{
var table = new Table(new[] { UnitValue.CreatePointValue(150), UnitValue.CreatePointValue(0), UnitValue.CreatePointValue(0), UnitValue.CreatePercentValue(25)});
//table.SetWidthPercent(100);
for (var i = 0; i < table.GetNumberOfColumns(); i ++ )
{
var column = table.GetColumnWidth(i);
table.AddHeaderCell(new Cell().SetBackgroundColor(Color.LIGHT_GRAY).SetMargin(0).Add(column.ToString()));
}
const string sampledata = "01234567";
for (var i = 0; i < table.GetNumberOfColumns() * 6; i++)
{
switch (i)
{
case 6:
table.AddCell(new Image(ImageDataFactory.Create(#"c:\demo.jpg")));
break;
case 4:
{
var subTable = CreateSubTable("subtable", rand);
table.AddCell(new Cell().SetMargin(0).SetPadding(0).Add(subTable));
break;
}
case 9:
{
var subTable = CreateSubTable("subtable", rand);
table.AddCell(new Cell().SetMargin(0).SetPadding(0).Add(subTable));
break;
}
default:
table.AddCell(sampledata.Substring(0, rand.Next(1, sampledata.Length)));
break;
}
}
doc.Add(table);
}
private static Table CreateSubTable(string sampledata, Random rand){
var subTable = new Table(new[] {UnitValue.CreatePointValue(0), UnitValue.CreatePointValue(70)});
for (var h = 0; h < subTable.GetNumberOfColumns(); h++)
{
var column = subTable.GetColumnWidth(h);
subTable.AddHeaderCell(new Cell().SetBackgroundColor(Color.LIGHT_GRAY).SetMargin(0).Add(column.ToString()));
}
for (var j = 0; j < subTable.GetNumberOfColumns() * 4; j++)
{
subTable.AddCell(sampledata.Substring(0, rand.Next(1, sampledata.Length)));
}
return subTable;
}

Yes, you've missed something.
First of all, you can check the latest itext7 snapshot version and find your code working as you expect. See the result pdf
The difference is that in 7.0.2 auto layout itext do not consider column width in max width calculations. That has been changed and that's why now your code will work as you expect.
However, in certain conditions the same nested table will be processed differently. It depends on available width (in your case for second column - column with width to be calculated automatically). So generally speaking with certain cell content that will be impossible to place nested table as it should be (0 pt, 70 pt), and itext will squeeze the second column.
The good news is that we received too many questions about auto layout algorithm and decided to write some detailed documentation. I believe that will be finished in a week. And then everybody will be happy =)

Related

Parsing a field and list of people from Excel files

I'm trying to parse Excel (.xls, .xlsx) files. The structure of files is the same except for the amount of the records.
I need to parse the industry. In this case it is "FinTech". Due to the fact that it is in one cell, I guess I have to use a regex expression such as ^Industry: (.*)$?
It has to find which row/column the list of the people starts and put it into a IEnumerable<Person>. It could use the following regex expressions.
Number always consists of 6 digits. ^[0-9]{6}$
Name consists of at least two words where each one of them starts with a capital letter. ^([a-zA-Z]+\s?\b){2,}$
A test .xlsx file can be found here https://docs.google.com/spreadsheets/d/15SR04cHXgGLWe0cuOOuuB5vUZigebh96/edit?usp=sharing&ouid=112418126731411268789&rtpof=true&sd=true.
List of people
Normal condition
Industry: FinTech
# Number Name
1 226250 Zain Griffiths
2 226256 Michael Houghton
3 226259 Hugo Willis Johnson
4 226264 Anna-Maria Rose
The actual question
First of all, I'm not completely sure if my regex expressions are correct. I was only able to display the rows and the columns but I'm not sure how to actually parse the industry and the list of the people into a IEnumerable<Person>. So how do I do that?
Snippet
// Program.cs
var excel = new ExcelParser();
var sheet1 = excel.Import(#"a.xlsx");
Console.OutputEncoding = Encoding.UTF8;
for (var i = 0; i < sheet1.Rows.Count; i++)
{
for (var j = 0; j < sheet1.Columns.Count; j++)
{
var cell = sheet1.Rows[i][j].ToString()?.Trim();
Console.Write($"Column: {cell} | ");
}
Console.WriteLine();
}
Console.ReadLine();
// ExcelParser.cs
public sealed class ExcelParser
{
public ExcelParser()
{
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
}
public DataTable Import(string filePath)
{
// does file exist?
if (!File.Exists(filePath))
{
throw new FileNotFoundException();
}
// .xls or .xlsx allowed
var extension = new FileInfo(filePath).Extension.ToLowerInvariant();
if (extension is not (".xls" or ".xlsx"))
{
throw new NotSupportedException();
}
// read .xls or .xlsx
using var stream = File.Open(filePath, FileMode.Open, FileAccess.Read);
using var reader = ExcelReaderFactory.CreateReader(stream);
var dataSet = reader.AsDataSet(new ExcelDataSetConfiguration
{
ConfigureDataTable = _ => new ExcelDataTableConfiguration
{
UseHeaderRow = false
}
});
// Sheet1
return dataSet.Tables[0];
}
}
The structure of files is the same except for the amount of the records
As long as the table is structured (or semi-structured), you can state one/two simple assumptions and parse the tables based on these assumptions, and in case the structure is not following the assumptions, you will return false (throw exception, etc..).
Actually, designing regexs to parse the table is kind of assumptions encoding.. I just want to Keep it simple, So, Based on the problem statement, here are my assumptions:
There will be a "industry" (or "industry:", call .ToLower()) string in a separate cell (regex will do nothing more than finding such a string), and industry's name will be in the same cell.[1]
First person's name will be next to the first 6-digits-number cell.[2]
Here is the code
public (string industryName, List<string> peopleNames) ParseSheet(DataTable sheet1)
{
// 1. Get Indices of industry cell and first Name in people names..
var industryCellIndex = (-1, -1, false);
var peopleFirstCellIndex = (-1, -1, false);
for (var i = 0; i < sheet1.Rows.Count; i++)
{
for (var j = 0; j < sheet1.Columns.Count; j++)
{
// .ToLower() added
var cell = sheet1.Rows[i][j].ToString()?.Trim().ToLower();
if (cell.StartsWith("industry"))
{
industryCellIndex = (i, j, true);
break;
}
// the name after the first 6-digits number cell will be the first name in people records
if (cell.Length == 6 && int.TryParse(cell, out _))
{
peopleFirstCellIndex = (i, j + 1, true);
break;
}
}
if (industryCellIndex.Item3 && peopleFirstCellIndex.Item3)
break;
}
if (!industryCellIndex.Item3 || !peopleFirstCellIndex.Item3)
{
// throw new Exception("Excel file is not normalized!");
return (null, null);
}
// 2. retrieve the desired data
var industryName = sheet1.Rows[industryCellIndex.Item1][industryCellIndex.Item2]
.Replace(":", ""); // will do nothing if there were no ":"
industryName = industryName.Substring(industryName.IndexOf("indusrty") + "indusrty".Length);
var peopleNames = new List<string>();
var colIndex = peopleFirstCellIndex.Item2;
for (var rowIndex = peopleFirstCellIndex.Item1;
rowIndex < sheet1.Rows.Count;
rowIndex++)
{
peopleNames.Add(sheet1.Rows[rowIndex][colIndex].ToString()?.Trim());
}
return (industryName, peopleNames);
}
[1] If this assumption needs some editing (like: the indusrty name might be the next cell that has "industry" string), the idea still the same.. you can consider this in parsing.
[2] And, for example, after the "#" cell by 2 columns and 1 row.

ITextSharp add three points to end of word

I have a table with sensors data. There are sensors with long names that do not fit into a cell.
I want to add three points to end of the long sensor names (like text-overflow: ellipsis in css). I want to do it flexible without hardcoded values. Because in the future number of columns may be different.
How can I do it?
I create table like this:
var table = new PdfPTable(columns.Length);
var widths = new List<float>();
for (var i = 0; i < columns.Length; i++)
{
widths.Add(1f);
}
table.SetWidths(widths.ToArray());
And fill it:
for (var i = 0; i < columns.Length; i++)
{
var cell = new PdfPCell(new Phrase(columns[i], tableDataFont));
cell.UseAscender = true;
cell.HorizontalAlignment = Element.ALIGN_CENTER;
cell.VerticalAlignment = Element.ALIGN_MIDDLE;
cell.BackgroundColor = new Color(204, 204, 204);
cell.MinimumHeight = 20f;
table.AddCell(cell);
}
Something like this should work...
var text = "testtesttesttesttest";
var maxLength = 7;
var displayname = text;
if (text.Length > maxLength)
{
displayname = text.Substring(0, maxLength) + "...";
}
Considering you didn't post any code of your own, I can only provide some logic.
What I do here is just take the real value (insert that into text).
Declare a maximum length you want the text in your fields to be (maxLength).
Create a storage variable to for manipulation of the name without losing the original data (in case you want to keep that).
Then check if the text is longer than the maximumlength and replace all that is too long with "...".
You can then return "displayname" to whatever field you want it to be in.
This most likely isn't what you're looking for, but it does answer your question as it is right now.

Create Two Tables Side by Side in Open XML Word Document

I can create two tables easy enough but I am having trouble getting them to appear side by side like this:
I am unsure how to achieve this using the Open XML SDK. I'm guessing it will either be a TableProperty or a trick with paragraphs but using the Productivity Tool I couldn't work it out. Code snippet:
int LeftWidth = 2000;
int RightWidth = 2000;
int NumberOfCols = 2;
Table leftTable = StartTable(NumberOfCols, LeftWidth); // Create a basic table
Table rightTable = StartTable(NumberOfCols, RightWidth);
body.Append(leftTable);
/// Do something to right table properties here?
body.Append(rightTable);
I am open to different methods, although ideally the idea would be transferable to three tables side by side too.
In the end I realised there are two main ways to achieve this is Word - either click a table and drag it's top left crosshair to where you want it or split the page into two columns and place a column break between the tables.
Floating Tables Method
int LeftWidth = 2000;
int RightWidth = 2000;
int NumberOfCols = 2;
Table leftTable = StartTable(NumberOfCols, LeftWidth); // Create a basic table
Table rightTable = StartTable(NumberOfCols, RightWidth);
/// Add table position properties and place table in top left
TableProperties tblProps = leftTable.Descendants<TableProperties>().First();
TablePositionProperties tblPos = new TablePositionProperties() { VerticalAnchor = VerticalAnchorValues.Text, TablePositionY = 1 };
TableOverlap overlap = new TableOverlap() { Val = TableOverlapValues.Overlap };
tblProps.Append(tblPos, overlap);
body.Append(leftTable);
/// Add position property to right table, set 8700 and 2400 to where you want the table
TableProperties tblProps2 = rightTable.Descendants<TableProperties>().First();
TablePositionProperties tblPos2 = new TablePositionProperties() { HorizontalAnchor = HorizontalAnchorValues.Page, VerticalAnchor = VerticalAnchorValues.Page, TablePositionX = 8700, TablePositionY = 2400 };
TableOverlap overlap2 = new TableOverlap() { Val = TableOverlapValues.Overlap };
tblProps2.Append(tblPos2, overlap2);
body.Append(rightTable);
Columns Method
Before the tables place this code to keep the prior content in one column:
/// Make sure everything else stays at one column
Paragraph oneColPara = body.AppendChild(new Paragraph());
/// Adjust current doc properties to keep things like landscape
SectionProperties sectionOneProps = null;
if (body.Descendants<SectionProperties>().Count() > 0)
sectionOneProps = (SectionProperties)body.Descendants<SectionProperties>().First().CloneNode(true);
else
sectionOneProps = new SectionProperties();
sectionOneProps.RemoveAllChildren<Columns>();
sectionOneProps.RemoveAllChildren<DocGrid>();
sectionOneProps.Append(new Columns(){ Space = "708" }, new DocGrid(){ LinePitch = 360 }, new SectionType { Val = SectionMarkValues.Continuous } );
oneColPara.Append(new ParagraphProperties(sectionOneProps));
Then do this in the tables part:
int LeftWidth = 2000;
int RightWidth = 2000;
int NumberOfCols = 2;
Table leftTable = StartTable(NumberOfCols, LeftWidth); // Create a basic table
Table rightTable = StartTable(NumberOfCols, RightWidth);
/// Need this blank para to line tables up
body.Append(new Paragraph());
body.Append(leftTable);
/// Place the tables side by side
body.Append(new Paragraph(new Run(new Break() { Type = BreakValues.Column })));
body.Append(rightTable);
/// Make section have 2 columns
Paragraph twoColPara = body.AppendChild(new Paragraph());
/// Adjust current doc properties to keep things like landscape
SectionProperties sectionTwoProps = null;
if (body.Descendants<SectionProperties>().Count() > 0)
sectionTwoProps = (SectionProperties)body.Descendants<SectionProperties>().First().CloneNode(true);
else
sectionTwoProps = new SectionProperties();
sectionTwoProps.RemoveAllChildren<Columns>();
sectionTwoProps.RemoveAllChildren<DocGrid>();
sectionTwoProps.Append(new Columns() { Space = "284", ColumnCount = 2 }, new DocGrid() { LinePitch = 360 }, new SectionType { Val = SectionMarkValues.Continuous });
twoColPara.Append(new ParagraphProperties(sectionTwoProps));

How to continue data writing on a new pdf page if it exceeds the page size in iTextShartp pdf

I have gone through this site but could not find the relative solution to my problem.
Here is my problem
I am creating a PDF file using iTextSharp and the file is being created very well. I am creating a table and assigning it a gridview data as follows.
//Add Actual columns from the datatable
for (int j = 0; j < dt.Columns.Count; j++) {
PdfPCell cellHeader = new PdfPCell(FormatHeaderPhrase(dt.Columns[j].ColumnName.ToString()));
cellHeader.HorizontalAlignment = 1;
table3.AddCell(cellHeader);// adds the header with proeprties
}
//Add the actual rows from the datatable
for (int i = 0; i < dt.Rows.Count; i++) {
for (int k = 0; k < dt.Columns.Count; k++) {
PdfPCell cellRows = new PdfPCell(FormatPhrase(dt.Rows[i][k].ToString().Replace("<br/>","\n").Replace("<sup>","")));
if (k != 2) {
cellRows.HorizontalAlignment = 1;
cellRows.VerticalAlignment = Element.ALIGN_MIDDLE;
}
else {
cellRows.HorizontalAlignment = 0;
}
table3.AddCell(cellRows);
table3.SplitRows = true;
table3.SplitLate = true;
}
}
I have a A3 page size. The concern here is, when my data in the table exceeds the page size, it trims the exceeded part and just shows the data which fits in one page. How to make it multipage(auto page break) based on the content of the table. Currently I am seeing the data which fits into one page of pdf. It should have generated more than one pages. How can I achieve this.
Please let me know your inputs. I would appreciate them. Thank you.
This may work, try creating new page after every n rows, suppose if current page fits only
10 rows, creating new page after every 10th record
Suppose you created a initial document instance
Document document =new Document(PageSize.A4);
After n rows, add new page by calling
document.NewPage();

Optimal Column Width OpenOffice Calc

I'm entering data from a CSV file into a OpenOffice spreadsheet.
This code gets the a new sheet in a spreadsheet:
Public Spreadsheet getSpreadsheet(int sheetIndex, XComponent xComp)
{
XSpreadsheet xSheets = ((XSpreadsheetDocument)xComp).getSheets();
XIndexAccess xSheetIA = (XIndexAccess)xSheets;
XSpreadsheet XSheet = (XSpreadsheet)xSheetsA.getByIndex(sheetIndex).Value;
return XSheet;
}
I then have method that enters a list into a cell range one cell at a time. I want to be able to automatically set the column size for these cells. which is something like
string final DataCell;
Xspreadsheet newSheet = getSpreadsheet(sheetIndex, xComp);
int numberOfRecords = ( int numberOfColumns * int numberOfRows);
for(cellNumber = 0; cellNumber < numberOfrecords; cellNumber++)
{
XCell tableData = newSheet.getCellbyPosition(columnValue, rowValue);
((XText)tableData).setString(finalDataCell);
column Value++;
if(columnValue > = numberOfColumns)
{
rowVal++ column = 0;
}
}
After googling i have found the function:
columns.OptimalWidth = True on http://forum.openoffice.org/en/forum/viewtopic.php?f=20&t=31292
but im unsure on how to use this. Could anyone explain this further or think of another way to have the cell autofit?
I understand the comments in the code are in Spanish I think, but the code is in English. I ran the comments through Google translate so now they are in English. I copied it from here:
//Auto Enlarge col width
private void largeurAuto(string NomCol)
{
XCellRange Range = null;
Range = Sheet.getCellRangeByName(NomCol + "1"); //Recover the range, a cell is
XColumnRowRange RCol = (XColumnRowRange)Range; //Creates a collar ranks
XTableColumns LCol = RCol.getColumns(); // Retrieves the list of passes
uno.Any Col = LCol.getByIndex(0); //Extract the first Col
XPropertySet xPropSet = (XPropertySet)Col.Value;
xPropSet.setPropertyValue("OptimalWidth", new one.Any((bool)true));
}
What this does it this: First it gets the range name and then gets the first column. The real code, though, is XpropertySet being used, which is explained REALLY well here.
public void optimalWidth(XSpreadsheet newSheet)
{
// gets the used range of the sheet
XSheetCellCursor XCursor = newSheet.createCursor();
XUsedAreaCursor xUsedCursor = (XUsedAreaCursor)XCursor;
xUsedCursor.gotoStartOfUsedArea(true);
xUsedCursor.gotoEndOfUsedArea(true);
XCellRangeAddressable nomCol = (XCellRangeAddressable)xUsedCursor;
XColumnRowRange RCol = (XColumnRowRange)nomCol;
XTableColumns LCol = RCol.getColumns();
// loops round all of the columns
for (int i = 0; i < nomCol.getRangeAddress().EndColumn;i++)
{
XPropertySet xPropSet = (XPropertySet)LCol.getByIndex(i).Value;
xPropSet.setPropertyValue("OptimalWidth", new uno.Any(true));
}
}

Categories

Resources