How to programmatically create multiple hyperlinks in a single Excel cell - c#

I am building an Excel Spreadsheet using EPPLUS:
I have a datatable for e.g.
CM | ID < columns
VW | 23, 24, 25 < values
foreach (ExcelRangeBase cell in ws2.Cells[2, 2, ToRow, 2])
{
string new_cell;
if (cell.Value == null)
{
continue;
}
else
{
new_cell = cell.Value.ToString();
string link = string.Empty;
string hyperlink = string.Empty;
string[] ints = new_cell.Split(',');
cell.Clear();
for (int i = 0; i < ints.Length; i++)
{
string s = ints[i];
link = #"myapplication/viewCar/ID=" + s;
cell.Hyperlink = new ExcelHyperLink(link) { Display = s };
}
}
}
so what the code does it takes the string as above: 23, 24, 25 comma separates it into an array and loops through, for each it generate a link and create a new cell.hyperlink. So, the issue i'm having is this:
cell.Hyperlink = new ExcelHyperLink(link) { Display = s };
it's only outputting the last element in the array, i know why because when it loops s = last element, what i don't know is how to fix the issue, not sure if this is possible aswell.
Any help would be appreciated.

Related

How to scrape datatable from a website with selenium c#?

I am trying to scrape data from the webpage. However, I am having a trouble scraping all of data in the table. I need to switch pages to get all the data and I am willing to get an output with DataGridTable. I am having a trouble figuring out how to do this even though there is a change with number of pages they have in the website. I would like to add information automatically on a data grid table pages by pages. My input(Website) is only showing 25 items. Thats why I have 25 items in DataGridTable. I would like to justify a "number of pages" from "go to end page button"'s element. So that my program knows how many pages are there to scrape from the website. but, if there's a different way, I wanna know thank you.
This is my code for now.
DataTable dt = new DataTable();
var header = driver.FindElement(By.CssSelector("#gridComponent > div.k-grid-header"));
foreach (var row in header.FindElements(By.TagName("tr")))
{
//Configure Number of Col and row
int cellIndex = 0;
string[] arr = new string[32];
//Get Cell Data
foreach (var cell in row.FindElements(By.TagName("th")))
{
// Check the header cell for a checkbox child. If no
// such child exists, add the column.
var headerCheckboxes = cell.FindElements(By.CssSelector("input[type='checkbox']"));
if (headerCheckboxes.Count == 0)
{
//Number of Col Data Load
if (cellIndex <= 29)
{
arr[cellIndex] = cell.Text;
dt.Columns.Add(cell.Text);
}
else
cellIndex++;
}
}
Console.WriteLine(arr);
}
var table = driver.FindElement(By.CssSelector("#gridComponent"));
//Get Row value
foreach (var row in table.FindElements(By.TagName("tr")))
{
//Configure Number of Col and row
int cellIndex = 0;
// Use a list instead of an array
List<string> arr = new List<string>();
//Get Cell Data
foreach (var cell in row.FindElements(By.TagName("td")))
{
// Skip the first column in the row by checking
// if the cell index is 0.
if (cellIndex != 0)
{
string cellValue = "";
Console.WriteLine(cell);
var checkboxes = cell.FindElements(By.CssSelector("input[type='checkbox']"));
if (checkboxes.Count > 0)
{
bool isChecked = false;
isChecked = checkboxes[0].Selected;
cellValue = isChecked.ToString();
}
else
{
cellValue = cell.Text;
}
arr.Add(cellValue);
}
cellIndex++;
}
dt.Rows.Add(arr.ToArray());
}
dataGridView1.DataSource = dt;
driver.FindElement(By.CssSelector("#gridComponent > div.k-pager-wrap.k-grid-pager.k-widget.k-floatwrap > ul > li:nth-child(3)")).Click();
}
This is the table that I am trying to scrape from.
This is the code for the following element that is shown picture above.
<span class="k-icon k-i-arrow-end-right"></span>
Thank you so much.
You may want to consider the index information "1 - 25 out of 64 items", since it is a good indicator of the total number of pages.
Batch = 1 - 25 i.e. 25 items per page
Total items = 64
No. of pages = roundup (64 / 25)
PS: A better option, without any computations, maybe to get the "data-page" attribute of the last page button.
I Finally got the answer for this.
private List<List<string>> GetRecords(IWebElement table)
{
List<List<string>> rows = new List<List<string>>(); ;
//Get Row value
foreach (var row in table.FindElements(By.TagName("tr")))
{
//Configure Number of Col and row
int cellIndex = 0;
// Use a list instead of an array
List<string> cols = new List<string>();
//Get Cell Data
foreach (var cell in row.FindElements(By.TagName("td")))
{
// Skip the first column in the row by checking
// if the cell index is 0.
if (cellIndex != 0)
{
string cellValue = "";
Console.WriteLine(cell);
var checkboxes = cell.FindElements(By.CssSelector("input[type='checkbox']"));
if (checkboxes.Count > 0)
{
bool isChecked = false;
isChecked = checkboxes[0].Selected;
cellValue = isChecked.ToString();
}
else
{
cellValue = cell.Text;
}
cols.Add(cellValue);
}
cellIndex++;
}
rows.Add(cols);
}
return rows;
}
private void button1_Click(object sender, EventArgs e)
{
//Configure to Hide CMD
var chromeDriverService = ChromeDriverService.CreateDefaultService();
chromeDriverService.HideCommandPromptWindow = true;
//Configure to Hide Chrome
ChromeOptions option = new ChromeOptions();
option.AddArgument("--headless");
//HIDING CHROME UN-COMMNET THE SECOND ONE TO SHOW
//IWebDriver driver = new ChromeDriver(chromeDriverService, option);
IWebDriver driver = new ChromeDriver();
driver.Url = "**************";
driver.Manage().Window.Maximize();
driver.SwitchTo().DefaultContent();
//Log-in
driver.FindElement(By.Id("username")).SendKeys("*****");
driver.FindElement(By.Id("password")).SendKeys("******" + OpenQA.Selenium.Keys.Enter);
//Entering Access Code
driver.FindElement(By.Id("password")).SendKeys("*******");
driver.FindElement(By.Id("accesscode")).SendKeys("********" + OpenQA.Selenium.Keys.Enter);
//go to CustomerList
driver.Navigate().GoToUrl("***********");
driver.Navigate().GoToUrl("*****************");
//Wait till load 3 seconds
waitOnPage(2);
DataTable dt = new DataTable();
var header = driver.FindElement(By.CssSelector("#gridComponent > div.k-grid-header"));
foreach (var row in header.FindElements(By.TagName("tr")))
{
//Configure Number of Col and row
int cellIndex = 0;
string[] arr = new string[32];
//Get Cell Data
foreach (var cell in row.FindElements(By.TagName("th")))
{
// Check the header cell for a checkbox child. If no
// such child exists, add the column.
var headerCheckboxes = cell.FindElements(By.CssSelector("input[type='checkbox']"));
if (headerCheckboxes.Count == 0)
{
//Number of Col Data Load
if (cellIndex <= 29)
{
arr[cellIndex] = cell.Text;
dt.Columns.Add(cell.Text);
}
else
cellIndex++;
}
}
Console.WriteLine(arr);
}
var table = driver.FindElement(By.CssSelector("#gridComponent"));
List<List<string>> records = GetRecords(table);
// Supposing you want the footer information
var lastPageStr = table.FindElement(By.ClassName("k-pager-last")).GetAttribute("data-page");
var lastPage = Convert.ToInt16(lastPageStr);
// You can select other info lik this
// class="k-link k-pager-nav" data-page="1"
driver.FindElement(By.CssSelector("#gridComponent > div.k-pager-wrap.k-grid-pager.k-widget.k-floatwrap > ul > li:nth-child(3)")).Click();
// Cycle over the pages
for (int p = 0; p < (lastPage - 1); p++)
{
driver.FindElement(By.CssSelector("#gridComponent > div.k-pager-wrap.k-grid-pager.k-widget.k-floatwrap > a:nth-child(4) > span")).Click();
waitOnPage(2);
var rows = GetRecords(table);
records.AddRange(rows);
}
// Add all rows to DT
//dt.Rows.Add(records[4].ToArray());
foreach(var row in records)
{
dt.Rows.Add(row.ToArray());
}
dataGridView1.DataSource = dt;
}

Setting format as percent in a cell using Google Sheets API

I'm trying to set a percent format when I create a cell, this is my code.
new Data.CellData
{
UserEnteredValue = new Data.ExtendedValue
{
FormulaValue = "=(B2/C2)-1"
},
EffectiveFormat = new Data.CellFormat
{
NumberFormat = new Data.NumberFormat
{
Pattern = "00.0%",
Type = "NUMBER"
}
}
}
Current value shown is "-0.2488570834" and I need this "-24.88%". What I'm missing ?
Thanks in advance.
Try following the Number format examples provided in the document:
I've also found a sample code to test this (converting format to your sample):
var ss = SpreadsheetApp.getActiveSpreadsheet();
var range = ss.getActiveRange();
var rows = range.getNumRows();
var cols = range.getNumColumns();
for(var row = 1; row <= rows; row++) {
for(var col = 1; col <= cols; col++) {
var cell = range.getCell(row, col);
var value = cell.getValue();
if(typeof(value) == 'number') {
cell.setNumberFormat("##.#%");
}
}
}
Result:
The code sample is for Apps script but the implementation to C# would be the same. Change Pattern = "00.0%", to Pattern = "##.#%",.
Hope this helps.

Displaying parsed csv files

Ive been working at this for hours and cant seem to figure out how to correctly display the data in a table
using (TextFieldParser csvParser = new TextFieldParser(path)) {
csvParser.CommentTokens = new string[] { "#" };
csvParser.SetDelimiters(new string[] { "," });
csvParser.HasFieldsEnclosedInQuotes = true;
csvParser.ReadLine();
int pointX = 30;
int pointY = 40;
while (!csvParser.EndOfData) {
string[] fields = csvParser.ReadFields();
int rowNums = fields.Length;
int index = 0;
for(index = 0; index < rowNums;index++) {
string Name = fields[index];
TextBox n = new TextBox();
n.Text = Name;
n.Location = new Point(pointX, pointY);
panel2.Controls.Add(n);
panel2.Show();
pointY += 20;
if(index != 0) {
pointX += 100;
}
}
}
}
Whats happening so far is im grabbing a csv file stored in the path variable and reading it the output is accessible through fields[] This works fine then I am trying to create textbox to put the data into based on rows however what i currently have comes out look like this
Program Display
I would like to display the column names and rows correctly in order here is an example image of what it looks like in notepad
Notepad Display
In notepad you will see each new line is a row and every , dictates a new entry in the row and i wanna display it this way in my program but in textbox
Also note that not all csv files that this program will be opening are short most will be large files with thousands or rows or more so theres no way that it could be simply putting fields[0] hard coded
You are much better off using a DataGridView to display this type of data in a table format.
From the toolbox add the DataGridView control to your form. You will need to build a DataTable that will bind to your DataGridview.
Below is what you can use(I commented out where you are skipping the header in your CSV file, and am using that line to get the column headers to be used in the datagrid)
var dt = new DataTable();
var lineNo = 0;
using (var csvParser = new TextFieldParser(path))
{
csvParser.CommentTokens = new string[] { "#" };
csvParser.SetDelimiters(new string[] { "," });
csvParser.HasFieldsEnclosedInQuotes = true;
//csvParser.ReadLine();
while (!csvParser.EndOfData)
{
var fields = csvParser.ReadFields();
var rowNums = fields.Length;
var row = dt.NewRow();
lineNo += 1;
int index = 0;
for (index = 0; index < rowNums; index++)
{
if (lineNo==1)
{
dt.Columns.Add(fields[index]);
}
else
{
row[index] = fields[index];
}
}
if (lineNo == 1) continue;
dt.Rows.Add(row);
dt.AcceptChanges();
}
}
dataGridView1.DataSource = dt;
I think the controls are overlapping, so you can not see them. That they are overlapping like chairs is the problem. You are not resetting your coordinates.
Here an improvement:
using (TextFieldParser csvParser = new TextFieldParser(path)) {
csvParser.CommentTokens = new string[] { "#" };
csvParser.SetDelimiters(new string[] { "," });
csvParser.HasFieldsEnclosedInQuotes = true;
csvParser.ReadLine();
int offsetX = 30;
int offsetY = 40;
int counter;
while (!csvParser.EndOfData) {
int pointerY = ++counter * offsetY; // first counter increments by one, then counter times offsetY occurs
int pointerX;
string[] fields = csvParser.ReadFields();
int rowNums = fields.Length;
for(int index = 0; index < rowNums;index++) {
pointerX = (index + 1) * offsetX;
string name = fields[index];
TextBox n = new TextBox() { Text = name, Location = new Point(pointerX, pointerY) };
panel2.Controls.Add(n);
panel2.Show(); // should be unnecessary
}
}
}

Customizing tables returned from Google.DataTable.Net.Wrapper with Format

I am making a custom report using Google Visualization API.
It will have 6 sections with each section having tables on either side and a chart in the middle.
Since the formats differ slightly I was spending a lot of time defining classes for each one-off case.
I decided to try Google.DataTable.Net.Wrapper 3.1.0.0.
I created a stored procedure that returns a DataSet and then walk through the DataSet in my Controller and pass each table that I need.
The Data looks something like this
rownum charttypeid charttypename
----------- ----------- ------------------
1 1 Membership Sales
rownum chartareaid chartareaname
----------- ----------- -------------------------
1 1 Membership Sales Overview
2 2 Membership Sales Chart
title value display
------------------------- ----------- ----------
# of Walk-ins 25 25
# of Tours 17 17
# of New Members 35 35
Tour Conversion 78 78%
Percent to Goal 87 87%
Month value display goalvalue goaldisplay
----- ----------- ---------- ----------- -----------
Sep 3125 $3,125.00 1500 $1,500.00
Oct 4500 $4,500.00 1500 $1,500.00
Sometimes the charts will have money formats or other display formats, sometimes dates etc.
I can't figure out how to add/modify the "f" part of the cell which provides a string format for display.
My Controller code looks like this
[ResponseType(typeof(List<ChartPanel>))]
public IHttpActionResult GetChart(int gym, string dateCategory, string iso8601date, int id = -1)
{
if (!String.IsNullOrWhiteSpace(dateCategory))
{
dateCategory = dateCategory.ToLower();
string strConnString = ConfigurationManager.ConnectionStrings["PrimaryDBConnection"].ConnectionString;
// return DataSet From USP
DataSet dashBoardDataSet = GetDataSQL(strConnString, gym, dateCategory, iso8601date, 0);
if (dashBoardDataSet != null)
{
int chartPanelCount = dashBoardDataSet.Tables[0].Rows.Count;
List<ChartPanel> chartTypeList = new List<ChartPanel>(); // list for all the panels
// first table describes the Chart Panels
int tableCount = 0;
for (int chartPanelLoop = 0; chartPanelLoop < chartPanelCount; chartPanelLoop++)
{ // for every panel
tableCount++;
ChartPanel chartPanel = new ChartPanel();
chartPanel.name = dashBoardDataSet.Tables[0].Rows[chartPanelLoop][2].ToString();
// second table describes the following chart areas for the panel
int panelAreaCount = dashBoardDataSet.Tables[1].Rows.Count;
List<ChartArea> chartAreaList = new List<ChartArea>();
int areaTableCount = tableCount;
for (int panelAreaLoop = 0; panelAreaLoop < panelAreaCount; panelAreaLoop++)
{ // for every area
int areaTable = areaTableCount;
ChartArea chartArea = new ChartArea();
chartArea.name = dashBoardDataSet.Tables[areaTable].Rows[panelAreaLoop][2].ToString();
int chartAreaRowNum = panelAreaLoop + 1;
System.Data.DataTable systDT = new System.Data.DataTable();
systDT = dashBoardDataSet.Tables[areaTable + chartAreaRowNum];
var dt = systDT.ToGoogleDataTable(); //convert with wrapper
//issue ==> //dt = RemoveColumnsWithTitleLikeDisplayAndPassCellContentsAsFormattedStringToPreviousCell(dt);
chartArea.table = JsonConvert.DeserializeObject(dt.GetJson());
chartAreaList.Add(chartArea);
//}
if (chartAreaList.Count() > 0) chartPanel.areas = chartAreaList;
tableCount++;
}
if (chartPanel.areas != null && chartPanel.areas.Count() > 0) chartTypeList.Add(chartPanel);
}
return Ok(chartTypeList);
}
else { return NotFound(); }
}
else { return NotFound(); }
}
Is there a better way to do this?
Figured it out. Here is my working code with a hack to look for any column where (colName.Contains("_display")) and make it be the formatted ("f") data for the previous column.
To map the column to the formatting column I made a custom class.
Custom Class
class ColumnDisplayMap
{
public int columnToFormat { get; set; }
public int formatColumn { get; set; }
}
Method For Building Charts
[ResponseType(typeof(List<ChartPanel>))]
public IHttpActionResult GetChart(int gym, string dateCategory, string iso8601date, int id = -1)
{
if (!String.IsNullOrWhiteSpace(dateCategory))
{
dateCategory = dateCategory.ToLower();
string strConnString = ConfigurationManager.ConnectionStrings["PrimaryDBConnection"].ConnectionString;
// return DataSet From USP
DataSet dashBoardDataSet = GetDataSQL(strConnString, gym, dateCategory, iso8601date, 0);
if (dashBoardDataSet != null)
{
int chartPanelCount = dashBoardDataSet.Tables[0].Rows.Count;
List<ChartPanel> chartTypeList = new List<ChartPanel>(); // list for all the panels
// first table describes the Chart Panels
int tableCount = 0;
for (int chartPanelLoop = 0; chartPanelLoop < chartPanelCount; chartPanelLoop++)
{ // for every panel
ChartPanel chartPanel = new ChartPanel();
chartPanel.name = dashBoardDataSet.Tables[0].Rows[chartPanelLoop][2].ToString();
// second table describes the following chart areas for the panel
DataRow[] areaTableRows = dashBoardDataSet.Tables[1].Select("charttype = " + (chartPanelLoop + 1).ToString());
int panelAreaCount = areaTableRows.Count();
List<ChartArea> chartAreaList = new List<ChartArea>();
for (int panelAreaLoop = 0; panelAreaLoop < panelAreaCount; panelAreaLoop++)
{ // for every area
int areaTable = 1;
ChartArea chartArea = new ChartArea();
chartArea.name = areaTableRows[panelAreaLoop][3].ToString(); // dashBoardDataSet.Tables[areaTable].Rows[panelAreaLoop][3].ToString();
DataColumnCollection columns = dashBoardDataSet.Tables[areaTable + tableCount + 1].Columns;
DataRowCollection rows = dashBoardDataSet.Tables[areaTable + tableCount + 1].Rows;
Google.DataTable.Net.Wrapper.DataTable gdt = new Google.DataTable.Net.Wrapper.DataTable();
List<ColumnDisplayMap> cMap = new List<ColumnDisplayMap>();
foreach (DataColumn col in columns)
{
string colName = col.ToString();
if (!colName.Contains("_display"))
{
ColumnType type = ColumnType.Number;
if (!col.IsNumeric()) type = ColumnType.String;
gdt.AddColumn(new Column(type, col.ToString(), col.ToString()));
}else
{
ColumnDisplayMap cdm = new ColumnDisplayMap(){columnToFormat = col.Ordinal - 1, formatColumn = col.Ordinal};
cMap.Add(cdm);
}
}
foreach (DataRow row in rows)
{
var r = gdt.NewRow();
for (int cellItem = 0; cellItem < row.ItemArray.Count(); cellItem++)
{
if (cMap.Any(c => c.columnToFormat.Equals(cellItem)))
{
r.AddCell(new Cell(row.ItemArray[cellItem], row.ItemArray[cellItem + 1].ToString()));
}
else if (cMap.Any(c => c.formatColumn.Equals(cellItem)))
{
// do nothing
}
else
{
r.AddCell(new Cell(row.ItemArray[cellItem], row.ItemArray[cellItem].ToString()));
}
}
gdt.AddRow(r);
}
chartArea.table = JsonConvert.DeserializeObject(gdt.GetJson());
chartAreaList.Add(chartArea);
//}
if (chartAreaList.Count() > 0) chartPanel.areas = chartAreaList;
tableCount++;
}
if (chartPanel.areas != null && chartPanel.areas.Count() > 0) chartTypeList.Add(chartPanel);
}
return Ok(chartTypeList);
}
else { return NotFound(); }
}
else { return NotFound(); }
}

How can I delete the selected Lines in Multi line Text Box?

My code below here I used "SelectedLines" to find the current selected lines by user, AllLines to find the no of total lines in Multi line text box. in last for loop when i=1 it runs successfully but when i is +1 = 2 then it gives me error
Error : "Index was out of range. Must be non-negative and less than the size of the collection. Parameter name: index?"
// Retrieve selected lines
List<string> SelectedLines = Regex.Split(txtNewURLs.SelectedText, #"\r\n").ToList();
// Check for nothing, Regex.Split returns empty string when no text is inputted
if (SelectedLines.Count == 1)
{
if (String.IsNullOrWhiteSpace(SelectedLines[0]))
{
SelectedLines.Remove("");
}
}
// Retrieve all lines from textbox
List<string> AllLines = Regex.Split(txtNewURLs.Text, #"\r\n").ToList();
// Check for nothing, Regex.Split returns empty string when no text is inputted
if (AllLines.Count == 1)
{
if (String.IsNullOrWhiteSpace(AllLines[0]))
{
AllLines.Remove("");
}
}
string SelectedMessage = "The following lines have been selected";
int numSelected = 0;
// Find all selected lines
foreach (string IndividualLine in AllLines)
{
if (SelectedLines.Any(a => a.Equals(IndividualLine)))
{
SelectedMessage += "\nLine #" + AllLines.FindIndex(a => a.Equals(IndividualLine));
// changing the status of the selected lene from 0 to 1
AddURL objAddURL = new AddURL();
objAddURL.Where.SURL.Value = IndividualLine;
objAddURL.Where.ILicenseID.Value = CommonMethods.iLicenseID;
objAddURL.Query.Load();
if (objAddURL.RowCount > 0)
{
AddURL objaddurl1 = new AddURL();
objaddurl1.LoadByPrimaryKey(objAddURL.IAddURLID);
if (objaddurl1.RowCount > 0)
{
objaddurl1.IStatus = 1;
objaddurl1.Save();
}
// AllLines.Remove(IndividualLine);
}
numSelected++;
}
}
// int[] lineNo = new int[AllLines.Count];
int linesNO = SelectedLines.Count;
for (int i = 1; i <= linesNO; i++)
{
SelectedLines.RemoveAt(i);
}
// MessageBox.Show((numSelected > 0) ? SelectedMessage : "No lines selected.");
Could any body please help me to solve this issue or suggest me new code?
Indices in c# are zero based.
for (int i = 0; i < linesNO; i++)
SelectedLines.RemoveAt(i);
But if you want to delete the selected lines from your textBox your code should rather look like
List<string> SelectedLines = new List<string> { "b", "c" };
textBox1.Lines = textBox1.Lines.ToList().Except(SelectedLines).ToArray();

Categories

Resources