C# Selenium, getting text of Invisible elements is slow - c#

I am trying to get invisible text that is on a given page using C# Selenium EdgeDriver. I am able to do it, i.e. I get the elements (such as within span, p or b tags, then I am filtering out the elements into a list based on Displayed property, and finally I am calling GetAttribute("textContent") to get the text. The problem I am having is that is slow, about 10 seconds for the page I am doing that on, do you think there is any better way, or making this faster?
Thanks,
public static string GetInvisibleText()
{
Stopwatch s500 = new Stopwatch();
s500.Start();
string returnable = "\r\n";
var elements = driver.FindElements(By.XPath("//b | //span | //p | //a | //h1 | //h2 | //h3 | //h4 | //h5 | //h6 | //div"));
List<string> list = new List<string>();
var displayed_elements = elements.Where(e => !e.Displayed);
foreach(var el in displayed_elements)
{
try
{
string val = el.GetAttribute("textContent");
val = val.Trim();
val = Regex.Replace(val, #"\s+", " ");
list.Add(val);
}
catch (Exception ex)
{
}
}
list = list.Distinct(StringComparer.OrdinalIgnoreCase).ToList();
foreach (string line in list)
{
returnable = returnable + line + "\r\n";
}
s500.Stop();
return returnable;
}

Related

C# Web API: Iterating Array and Append to HTML table

I am trying to create a POST endpoint where on success, it will send an HTML email with the POST data. I am struggling trying to iterate over the JSON array of objects and appending that data to a HTML table.
JSON data:
{
"Submitter":[
{
"Obj1":[
"test",
"test2"
]
},
{
"Obj2":[
"test3",
"test4"
]
}
]
}
TestingController:
public class Testing2Controller : ControllerBase
{
public class Submitter
{
public List<string> Obj1 { get; set; }
public List<string> Obj2 { get; set; }
}
public class MyData
{
public List<Submitter> Submitter { get; set; }
}
public string POST([FromBody] MyData data)
{
string composeTableObj1 = #"";
string composeTableObj2 = #"";
foreach (var item in data.Submitter)
{
composeTableObj1 += $"<tr>"; //start row tag
//Column 1 Obj1 data
if (item.Obj1 != null)
{
foreach (var objItem in item.Obj1)
{
composeTableObj1 += $"<td>{objItem}</td>";
}
}
//Column 2 Obj2 data
if (item.Obj2 != null)
{
foreach (var objItem in item.Obj2)
{
composeTableObj1 += $"<td>{objItem}</td>";
}
}
composeTableObj1 += $"</tr>"; //end row tag
}
string body = #$"<table>
<thead>
<tr>
<th>Object 1</th>
<th>Object 2</th>
</tr>
</thead>
<tbody>
{composeTableObj1}
</tbody>
</table>
";
return body;
}
}
The above code gives me the following undesired result:
| Object 1 | Object 2 |
|----------|----------|
| test | test2 |
| test3 | test4 |
This is the desired result I'm after:
| Object 1 | Object 2 |
|----------|----------|
| test | test3 |
| test2 | test4 |
Been stuck on this for quite some time, TIA!
Looks like your HTML is a bit off - writing HTML like this can be tedious. I am guessing based off of the hard coded table headers in the HTML and the structure of the Submitter class that you will only ever have two Objn items in your JSON. If this is true, you can ditch the foreach loop for a for loop, and get the value out of data.Submitter[n] using the loops iterator:
public string Post([FromBody] MyData data)
{
string composeTableObj = "";
for (int i = 0; i < data.Submitter.Count(); i++)
{
composeTableObj += $"<tr>"; //start row tag
composeTableObj += $"<td>{data.Submitter[0].Obj1[i]}</td>";
composeTableObj += $"<td>{data.Submitter[1].Obj2[i]}</td>";
composeTableObj += $"</tr>"; //end row tag
}
return #$"<table><thead><tr><th>Object 1</th><th>Object 2</th></tr></thead><tbody>{composeTableObj}</tbody></table>";
}
Rendered:
Returned HTML:
<table><thead><tr><th>Object 1</th><th>Object 2</th></tr></thead><tbody><tr><td>test</td><td>test3</td></tr><tr><td>test2</td><td>test4</td></tr></tbody></table>
Obviously, this isn't very dynamic. If you find you need more Objn, you'll need to update the Submitter class, and you will have to add to the code within the loop (and add another header for your HTML).
If you have more items in the Obj arrays, you can do the following:
public string Post([FromBody] MyData data)
{
string composeTableObj = "";
int obj1Count = data.Submitter[0].Obj1.Count;
int obj2Count = data.Submitter[1].Obj2.Count;
int loopCount = obj1Count >= obj2Count ? obj1Count : obj2Count;
for (int i = 0; i < loopCount; i++)
{
string obj1String = obj1Count <= i ? " " : data.Submitter[0].Obj1[i];
string obj2String = obj2Count <= i ? " " : data.Submitter[1].Obj2[i];
composeTableObj += $"<tr>"; //start row tag
composeTableObj += $"<td>{obj1String}</td>";
composeTableObj += $"<td>{obj2String}</td>";
composeTableObj += $"</tr>"; //end row tag
}
return #$"<table><thead><tr><th>Object 1</th><th>Object 2</th></tr></thead><tbody>{composeTableObj}</tbody></table>";
}
This first gets the greater of the two .Counts between your Obj lists to set the loops condition, and then performs an index bounds check. If the objnCount is less than or equal to i, set it to a space, otherwise take the value for its respective Obj list. With 6 items in Obj2 and only 2 items in Obj1, the HTML looked like this:
Returned HTML:
<table><thead><tr><th>Object 1</th><th>Object 2</th></tr></thead><tbody><tr><td>test</td><td>test3</td></tr><tr><td>test2</td><td>test4</td></tr><tr><td> </td><td>test5</td></tr><tr><td> </td><td>test6</td></tr><tr><td> </td><td>test7</td></tr><tr><td> </td><td>test8</td></tr></tbody></table>

Multiple lines and columns in Specflow

I have a Specflow table that looks like this.
When I Perform POST Operation for "/example/" with body
| answerValue1 | answerValue2 | countryCode | Cash |
| Yes | Yes | AD | 76-100% |
| | | AF | |
The column CountryCode is the only one that can be multiple choices.
What I tried to do was to add the columns to dictionary with a simple tableExtenstions
public class TableExtensions
{
public static Dictionary<string, string> ToDictionary(Table table)
{
var dictionary = new Dictionary<string, string>();
foreach (var row in table.Rows)
{
dictionary.Add(row[0], row[1]);
}
return dictionary;
}
}
and call it from the method.
var dictionary = TableExtensions.ToDictionary(table);
var countryCode = dictionary["countryCode"];
Unfortnally I get error The given key was not present in the dictionary,
since the dictionary only returns two values from the first and the second Key
Ofcourse if I change the keys to row[2], row[3] it gets the right columns.
But I would like to reuse the Table Extension.
Also i tried to increment them, but it only took the first to columns
var i = 0;
foreach (var row in table.Rows)
{
dictionary.Add(row[i], row[i]);
i++;
}
Does anyone has a better solution?
I'm not entirely sure what you want the dictionary to ultimately contain, but as you mention that manually changing the rows it looks for to:
row[2], row[3]
gives the data you want, perhaps this would give you the reusability you're looking for:
public class TableExtensions
{
public static Dictionary<string, string> ToDictionary(Table table, int columnOne, int columnTwo)
{
int i = 0;
var dictionary = new Dictionary<string, string>();
foreach (var row in table.Rows)
{
dictionary.Add(row[columnOne], row[columnTwo]);
}
return dictionary;
}
}
Usage:
var dictionary = TableExtensions.ToDictionary(table, 2, 3);
This produces a dictionary with the following contents:
You could get the country code like this:
foreach (var row in dictionary)
{
var countryCode = row.Key;
var score = row.Value ?? string.empty;
}
Given the simplicity of the country codes I would make them a comma separated list and use a vertical table instead:
When I Perform POST Operation for "/example/"
| Field | Value |
| answerValue1 | ... |
| answerValue2 | ... |
| countryCodes | AD, AF |
| cash | ... |
In your step definition:
var example = table.CreateInstance<ExampleRow>();
// use example.GetCountryCodes();
And the ExampleRow class to parse the table into an object:
public class ExampleRow
{
public string AnswerValue1 { get; set; }
public string AnswerValue2 { get; set; }
private string[] countryCodes;
public string CountryCodes
{
get => string.Join(", ", countryCodes);
set => countryCodes = value.Split(", ");
}
public string[] GetCountryCodes()
{
return countryCodes;
}
}

Trying to count & List names in a txt file

I'm having some issues with the following:
I have a txt file with roughly two thousand names, with a bunch of duplicate entries. I'm trying to create something where it will list the amount of times a name appears. So for example:
John Doe | 48 times
Jane Doe | 20 times
etc etc.
I found examples here on how i could count this, but i have absolutely no idea how i could have this output this to a richTextbox or other file for example.
.Select(s => new { Key = s.Key, Count = s.Count()})
.ToDictionary(d => d.Key, d => d.Count);```
Data enter into the file names.txt file.
John Deo
John Deo
John Deo
John wick
John wick
Testing
Testing
I have made file into the project called names.txt and read him in code which are below.
string[] lines = File.ReadAllLines("../../names.txt");
Then grouping the names and print into the console application.
var mylines = lines.GroupBy(g => g).Select(s => new { Name = s, Count = s.Count() });
foreach (var line in mylines)
{
Console.WriteLine($"{line.Name.Key} | {line.Count}");
}
Result#
John Deo | 11
John wick | 2
Testing | 5
There are many different ways to do this. If all you want is to determine the counts and how it's displayed doesn't matter then you could output the counts to a CSV file and look at it with Excel. I don't know exactly how your names are formatted in the simple example below I assume one name per line.
class Program
{
static void Main(string[] args)
{
try
{
using (var reader = new StreamReader("names.txt"))
{
var names = GetNames(reader).ToLookup(k => k);
using (var writer = new StreamWriter("names.counted.csv"))
{
foreach (var name in names)
{
writer.WriteLine($"{name.Key},{name.Count()}");
}
}
}
}
catch (Exception ex)
{
Console.Error.WriteLine(ex.ToString());
}
}
static IEnumerable<string> GetNames(TextReader reader)
{
string line;
while ((line = reader.ReadLine()) != null)
yield return line;
}
}
Assumption:
Lets say we already have each line of the text file in an List<string> object we will call names and that each line in the text file represents a whole name spelled correctly.
Grouping/Printing the data:
Using LINQ we can group these values by themselves (similar to SQL) and then convert the IGrouping results into the objects we want to use later in our application. For example:
var totals = names.GroupBy(x => x)
.Select(group => new { Name = group.Key, Count = group.Count() });
foreach ( var total in totals )
{
Console.WriteLine($"{total.Name} | {total.Count} times");
}
Another option would be to use your existing code and just print out the values of the dictionary
var totals = names
.Select(s => new { Key = s.Key, Count = s.Count()})
.ToDictionary(d => d.Key, d => d.Count);
foreach ( var kvp in totals )
{
Console.WriteLine($"{kvp.Key} | {kvp.Value} times");
}
Saving/Displaying the data:
If you want to do something other then print to console you could simply manipulate the data into the value you want. For example if you want to save it to another file:
var csvContent = totals
.Select(total => $"{total.Name},${total.Count} times")
.ToArray();
File.WriteAllLines(filePath, csvContent);
Or you could create a string (e.g. in above: String.Join("\n", csvContent)) and update a RichTextBox like so
You could iterate over the results of your query and add each name/count to a StringBuilder, then output the final string to your RichTextBox:
StringBuilder sb = new StringBuilder();
foreach(var KVP in yourDictionaryVariableName)
{
sb.AppendLine(KVP.Key + " | " + KVP.Value.ToString());
}
richTextBox1.Text = sb.ToString();

In C#, what is the best way to parse this WIKI markup?

I need to take data that I am reading in from a WIKI markup page and store it as a table structure. I am trying to figure out how to properly parse the below markup syntax into some table data structure in C#
Here is an example table:
|| Owner || Action || Status || Comments ||
| Bill | Fix the lobby | In Progress | This is easy |
| Joe | Fix the bathroom | In Progress | Plumbing \\
\\
Electric \\
\\
Painting \\
\\
\\ |
| Scott | Fix the roof | Complete | This is expensive |
and here is how it comes in directly:
|| Owner|| Action || Status || Comments || | Bill\\ | fix the lobby |In Progress | This is eary| | Joe\\ |fix the bathroom\\ | In progress| plumbing \\Electric \\Painting \\ \\ | | Scott \\ | fix the roof \\ | Complete | this is expensive|
So as you can see:
The column headers have "||" as the separator
A row columns have a separator or "|"
A row might span multiple lines (as in the second data row example above) so i would have to keep reading until I hit the same number of "|" (cols) that I have in the header row.
I tried reading in line by line and then concatenating lines that had "\" in between then but that seemed a bit hacky.
I also tried to simply read in as a full string and then just parse by "||" first and then keep reading until I hit the same number of "|" and then go to the next row. This seemed to work but it feel like there might be a more elegant way using regular expressions or something similar.
Can anyone suggest the correct way to parse this data?
I have largely replaced the previous answer, due to the fact that the format of the input after your edit is substantially different from the one posted before. This leads to a somewhat different solution.
Because there are no longer any line breaks after a row, the only way to determine for sure where a row ends, is to require that each row has the same number of columns as the table header. That is at least if you don't want to rely on some potentially fragile white space convention present in the one and only provided example string (i.e. that the row separator is the only | not preceded by a space). Your question at least does not provide this as the specification for a row delimiter.
The below "parser" provides at least the error handling validity checks that can be derived from your format specification and example string and also allows for tables that have no rows. The comments explain what it is doing in basic steps.
public class TableParser
{
const StringSplitOptions SplitOpts = StringSplitOptions.None;
const string RowColSep = "|";
static readonly string[] HeaderColSplit = { "||" };
static readonly string[] RowColSplit = { RowColSep };
static readonly string[] MLColSplit = { #"\\" };
public class TableRow
{
public List<string[]> Cells;
}
public class Table
{
public string[] Header;
public TableRow[] Rows;
}
public static Table Parse(string text)
{
// Isolate the header columns and rows remainder.
var headerSplit = text.Split(HeaderColSplit, SplitOpts);
Ensure(headerSplit.Length > 1, "At least 1 header column is required in the input");
// Need to check whether there are any rows.
var hasRows = headerSplit.Last().IndexOf(RowColSep) >= 0;
var header = headerSplit.Skip(1)
.Take(headerSplit.Length - (hasRows ? 2 : 1))
.Select(c => c.Trim())
.ToArray();
if (!hasRows) // If no rows for this table, we are done.
return new Table() { Header = header, Rows = new TableRow[0] };
// Get all row columns from the remainder.
var rowsCols = headerSplit.Last().Split(RowColSplit, SplitOpts);
// Require same amount of columns for a row as the header.
Ensure((rowsCols.Length % (header.Length + 1)) == 1,
"The number of row colums does not match the number of header columns");
var rows = new TableRow[(rowsCols.Length - 1) / (header.Length + 1)];
// Fill rows by sequentially taking # header column cells
for (int ri = 0, start = 1; ri < rows.Length; ri++, start += header.Length + 1)
{
rows[ri] = new TableRow() {
Cells = rowsCols.Skip(start).Take(header.Length)
.Select(c => c.Split(MLColSplit, SplitOpts).Select(p => p.Trim()).ToArray())
.ToList()
};
};
return new Table { Header = header, Rows = rows };
}
private static void Ensure(bool check, string errorMsg)
{
if (!check)
throw new InvalidDataException(errorMsg);
}
}
When used like this:
public static void Main(params string[] args)
{
var wikiLine = #"|| Owner|| Action || Status || Comments || | Bill\\ | fix the lobby |In Progress | This is eary| | Joe\\ |fix the bathroom\\ | In progress| plumbing \\Electric \\Painting \\ \\ | | Scott \\ | fix the roof \\ | Complete | this is expensive|";
var table = TableParser.Parse(wikiLine);
Console.WriteLine(string.Join(", ", table.Header));
foreach (var r in table.Rows)
Console.WriteLine(string.Join(", ", r.Cells.Select(c => string.Join(Environment.NewLine + "\t# ", c))));
}
It will produce the below output:
Where "\t# " represents a newline caused by the presence of \\ in the input.
Here's a solution which populates a DataTable. It does require a litte bit of data massaging (Trim), but the main parsing is Splits and Linq.
var str = #"|| Owner|| Action || Status || Comments || | Bill\\ | fix the lobby |In Progress | This is eary| | Joe\\ |fix the bathroom\\ | In progress| plumbing \\Electric \\Painting \\ \\ | | Scott \\ | fix the roof \\ | Complete | this is expensive|";
var headerStop = str.LastIndexOf("||");
var headers = str.Substring(0, headerStop).Split(new string[1] { "||" }, StringSplitOptions.None).Skip(1).ToList();
var records = str.Substring(headerStop + 4).TrimEnd(new char[2] { ' ', '|' }).Split(new string[1] { "| |" }, StringSplitOptions.None).ToList();
var tbl = new DataTable();
headers.ForEach(h => tbl.Columns.Add(h.Trim()));
records.ForEach(r => tbl.Rows.Add(r.Split('|')));
This makes some assumptions but seems to work for your sample data. I'm sure if I worked at I could combine the expressions and clean it up but you'll get the idea.
It will also allow for rows that do not have the same number of cells as the header which I think is something confluence can do.
List<List<string>> table = new List<List<string>>();
var match = Regex.Match(raw, #"(?:(?:\|\|([^|]*))*\n)?");
if (match.Success)
{
var headersWithExtra = match.Groups[1].Captures.Cast<Capture>().Select(c=>c.Value);
List<String> headerRow = headersWithExtra.Take(headersWithExtra.Count()-1).ToList();
if (headerRow.Count > 0)
{
table.Add(headerRow);
}
}
match = Regex.Match(raw + "\r\n", #"[^\n]*\n" + #"(?:\|([^|]*))*");
var cellsWithExtra = match.Groups[1].Captures.Cast<Capture>().Select(c=>c.Value);
List<string> row = new List<string>();
foreach (string cell in cellsWithExtra)
{
if (cell.Trim(' ', '\t') == "\r\n")
{
if (!table.Contains(row) && row.Count > 0)
{
table.Add(row);
}
row = new List<string>();
}
else
{
row.Add(cell);
}
}
This ended up very similar to Jon Tirjan's answer, although it cuts the LINQ to a single statement (the code to replace that last one was horrifically ugly) and is a bit more extensible. For example, it will replace the Confluence line breaks \\ with a string of your choosing, you can choose to trim or not trim whitespace from around elements, etc.
private void ParseWikiTable(string input, string newLineReplacement = " ")
{
string separatorHeader = "||";
string separatorRow = "| |";
string separatorElement = "|";
input = Regex.Replace(input, #"[ \\]{2,}", newLineReplacement);
string inputHeader = input.Substring(0, input.LastIndexOf(separatorHeader));
string inputContent = input.Substring(input.LastIndexOf(separatorHeader) + separatorHeader.Length);
string[] headerArray = SimpleSplit(inputHeader, separatorHeader);
string[][] rowArray = SimpleSplit(inputContent, separatorRow).Select(r => SimpleSplit(r, separatorElement)).ToArray();
// do something with output data
TestPrint(headerArray);
foreach (var r in rowArray) { TestPrint(r); }
}
private string[] SimpleSplit(string input, string separator, bool trimWhitespace = true)
{
input = input.Trim();
if (input.StartsWith(separator)) { input = input.Substring(separator.Length); }
if (input.EndsWith(separator)) { input = input.Substring(0, input.Length - separator.Length); }
string[] segments = input.Split(new string[] { separator }, StringSplitOptions.None);
if (trimWhitespace)
{
for (int i = 0; i < segments.Length; i++)
{
segments[i] = segments[i].Trim();
}
}
return segments;
}
private void TestPrint(string[] lst)
{
string joined = "[" + String.Join("::", lst) + "]";
Console.WriteLine(joined);
}
Console output from your direct input string:
[Owner::Action::Status::Comments]
[Bill::fix the lobby::In Progress::This is eary]
[Joe::fix the bathroom::In progress::plumbing Electric Painting]
[Scott::fix the roof::Complete::this is expensive]
A generic regex solution that populate a datatable and is a little flexible with the syntax.
var text = #"|| Owner|| Action || Status || Comments || | Bill\\ | fix the lobby |In Progress | This is eary| | Joe\\ |fix the bathroom\\ | In progress| plumbing \\Electric \\Painting \\ \\ | | Scott \\ | fix the roof \\ | Complete | this is expensive|";
// Get Headers
var regHeaders = new Regex(#"\|\|\s*(\w[^\|]+)", RegexOptions.Compiled);
var headers = regHeaders.Matches(text);
//Get Rows, based on number of headers columns
var regLinhas = new Regex(String.Format(#"(?:\|\s*(\w[^\|]+)){{{0}}}", headers.Count));
var rows = regLinhas.Matches(text);
var tbl = new DataTable();
foreach (Match header in headers)
{
tbl.Columns.Add(header.Groups[1].Value);
}
foreach (Match row in rows)
{
tbl.Rows.Add(row.Groups[1].Captures.OfType<Capture>().Select(col => col.Value).ToArray());
}
Here's a solution involving regular expressions. It takes a single string as input and returns a List of headers and a List> of rows/columns. It also trims white space, which may or may not be the desired behavior, so be aware of that. It even prints things nicely :)
using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;
namespace parseWiki
{
class Program
{
static void Main(string[] args)
{
string content = #"|| Owner || Action || Status || Comments || | Bill\\ | fix the lobby |In Progress | This is eary| | Joe\\ |fix the bathroom\\ | In progress| plumbing \\Electric \\Painting \\ \\ | | Scott \\ | fix the roof \\ | Complete | this is expensive|";
content = content.Replace(#"\\", "");
string headerContent = content.Substring(0, content.LastIndexOf("||") + 2);
string cellContent = content.Substring(content.LastIndexOf("||") + 2);
MatchCollection headerMatches = new Regex(#"\|\|([^|]*)(?=\|\|)", RegexOptions.Singleline).Matches(headerContent);
MatchCollection cellMatches = new Regex(#"\|([^|]*)(?=\|)", RegexOptions.Singleline).Matches(cellContent);
List<string> headers = new List<string>();
foreach (Match match in headerMatches)
{
if (match.Groups.Count > 1)
{
headers.Add(match.Groups[1].Value.Trim());
}
}
List<List<string>> body = new List<List<string>>();
List<string> newRow = new List<string>();
foreach (Match match in cellMatches)
{
if (newRow.Count > 0 && newRow.Count % headers.Count == 0)
{
body.Add(newRow);
newRow = new List<string>();
}
else
{
newRow.Add(match.Groups[1].Value.Trim());
}
}
body.Add(newRow);
print(headers, body);
}
static void print(List<string> headers, List<List<string>> body)
{
var CELL_SIZE = 20;
for (int i = 0; i < headers.Count; i++)
{
Console.Write(headers[i].Truncate(CELL_SIZE).PadRight(CELL_SIZE) + " ");
}
Console.WriteLine("\n" + "".PadRight( (CELL_SIZE + 2) * headers.Count, '-'));
for (int r = 0; r < body.Count; r++)
{
List<string> row = body[r];
for (int c = 0; c < row.Count; c++)
{
Console.Write(row[c].Truncate(CELL_SIZE).PadRight(CELL_SIZE) + " ");
}
Console.WriteLine("");
}
Console.WriteLine("\n\n\n");
Console.ReadKey(false);
}
}
public static class StringExt
{
public static string Truncate(this string value, int maxLength)
{
if (string.IsNullOrEmpty(value) || value.Length <= maxLength) return value;
return value.Substring(0, maxLength - 3) + "...";
}
}
}
Read the input string one character at a time and use a state-machine to decide what should be done with each input character. This approach probably needs more code, but it will be easier to maintain and to extend than regular expressions.

C# output is somehow program name and class name

I am trying to do the crazy formatting instructions my teacher gave me. After perusing for probably an hour (This is my first C# program), I came up with this line of code.
`Console.WriteLine(String.Format("{0," + -longestTitle + "} | {1," + -longestAlbumTitle + "} | {2," + -longestArtist + "} | {3:0.00, 8} | {4," + -longestYearAndRating + "} |", songArray[arraySearcher].title, songArray[arraySearcher].albumTitle, songArray[arraySearcher].artist, songArray[arraySearcher].length, songArray[arraySearcher].yearAndRating));`
longestX is an int containing the number of characters of the longestX (where x = title, album, etc).
The output I would like looks something like this:
Stuff | morestuff | extrastuff | 5.92 | 1992:R |
Stuf | est | sfafe | 232.44 | 2001:PG |
S uf | e | sfe | .44 | 2001:G |
(Where all padding is determined dynamically based on the longest title input by the user or file).
The output I get looks like this:
Program_Example.ClassName
Program_Example.ClassName
(or, specifically, Tyler_Music_Go.Song)
I have printed songArray[arraySearcher].title in this same method, and it works fine.
Could someone please help me?
Full relevant code:
class Song {
public string title, albumTitle, yearAndRating, artist;
public float length;
public Song(string titl, string albumTitl, string art, float leng, string yrNRating)
{
title = titl;
albumTitle = albumTitl;
yearAndRating = yrNRating;
length = leng;
artist = art;
}
}
//This class contains a Song array (with all Songs contained within), an array index, a search index, and ints to determine the longest of each category.
class SongList
{
Song[] songArray;
private int arrayKeeper, longestTitle, longestArtist, longestAlbumTitle, longestYearAndRating, checker;
int arraySearcher = 0;
public SongList()
{
songArray = new Song[10000];
arrayKeeper = 0;
longestTitle = 0;
longestArtist = 0;
longestAlbumTitle = 0;
longestYearAndRating = 0;
}
public void AddSong(string title, string albumTitle, string artist, float length, string yearAndRating)
{
songArray[arrayKeeper] = new Song(title, albumTitle, artist, length, yearAndRating);
arrayKeeper++;
checker = 0;
//This section of code is responsible for formatting the output. Since the longest values are already known, the list can be displayed quickly.
//Once a song is deleted, however, previously calculated longest lengths still stand.
foreach (char check in title)
{
checker++;
}
if (checker > longestTitle)
{
longestTitle = checker;
}
foreach (char check in albumTitle)
{
checker++;
}
if (checker > longestAlbumTitle)
{
longestAlbumTitle = checker;
}
foreach (char check in artist)
{
checker++;
}
if (checker > longestArtist)
{
longestArtist = checker;
}
foreach (char check in yearAndRating)
{
checker++;
}
if (checker > longestYearAndRating)
{
longestYearAndRating = checker;
}
}
//public bool RemoveSong(string title)
// {
//}
public void DisplayData()
{
Console.WriteLine("| Title | Album Title | Artist | Length | Year and Rating |");
for (arraySearcher = 0; arraySearcher < arrayKeeper; arraySearcher++)
{
//This line for testing purposes. (works)
Console.WriteLine(songArray[arraySearcher].title);
Console.WriteLine(songArray[arraySearcher].ToString());
}
}
public override string ToString()
{
//This line for testing purposes. (works)
Console.WriteLine(songArray[arraySearcher].title);
return String.Format("{0," + -longestTitle + "} | {1," + -longestAlbumTitle + "} | {2," + -longestArtist + "} | {3:0.00, 8} | {4," + -longestYearAndRating + "} |", songArray[arraySearcher].title, songArray[arraySearcher].albumTitle, songArray[arraySearcher].artist, songArray[arraySearcher].length, songArray[arraySearcher].yearAndRating);
}
}
`
EDIT:
Well, now I feel all manor of stupid. I was overwriting the tostring() method for the SongList, and then calling the tostring method for Song. Guy who answered made me realize it. Thanks to everyone who gave me advice, though.
You have to either access a property directly (songVariable.Title) or override ToString() in your song class to have that output the title.
public class Song
{
public string Title {get; set;}
public override string ToString()
{
return Title;
}
}

Categories