I have a HTML row (<tr>) generated on client side, I want to convert the string which contains the row-cells information in a HtmlTableRow control. That is what I have done so far using the example on Convert string to WebControls - asp.net. Thanks
string row = "<tr><td>item</td><td><input name=\"radio0\" type=\"radio\"/></td></tr>";
Dictionary<string, HtmlContainerControl> controlConstructor = new Dictionary<string, HtmlContainerControl>
{
{"tr", new HtmlTableRow()},
{"td", new HtmlTableCell()}
};
var htmlDoc = XElement.Parse(row);
Func<XElement, HtmlControl> constructHtmlStructure = null;
constructHtmlStructure = (o =>
{
var control = controlConstructor[o.Name.ToString()];
if (o.HasElements)
{
control.Controls.Add(constructHtmlStructure(o.Elements().Single())); //Exception: Sequence contains more than one element (When is a input item)
}
else
{
control.InnerText = o.Value;
}
return control;
});
HtmlTableRow structure = (HtmlTableRow)constructHtmlStructure(htmlDoc);
why don't you use much simpler ways to parse your string to HtmlTableRow.
your Dictionary<string, HtmlContainerControl> controlConstructor considers only tr and td, what about input control nested inside them?
so even if you get away through that "sequence contains more than one element", using a foreach loop, you will get an error "key doesn't exist".
and even if you manage overcome that(by adding input key in your dictionary), you cannot parse it to a HtmlContainerControl.
and even if you do, by updating your Dictionary<string, HtmlContainerControl> to Dictionary<string, HtmlControl> , you will have to think of ways to handle that input control because, for that you cannot do control.InnerText = o.value;
hence a much simpler way:
string row = "<tr><td>item</td><td><input name=\"radio0\" type=\"radio\"/></td></tr>";
XmlDocument doc = new XmlDocument();
doc.LoadXml(row);
HtmlTableRow tblRow = new HtmlTableRow();
foreach (XmlNode node in doc.SelectSingleNode("tr").ChildNodes)
{
HtmlTableCell cell = new HtmlTableCell();
cell.InnerText = node.InnerXml;
foreach (XmlNode childNode in node.ChildNodes)
{
if (childNode.Name == "input")
{
if (childNode.Attributes["type"] != null)
{
switch (childNode.Attributes["type"].Value.ToString())
{
case "radio":
HtmlInputRadioButton rad = new HtmlInputRadioButton();
rad.Name = childNode.Attributes["name"].ToString();
cell.Controls.Add(rad);
break;
///other types of input controls
default:
break;
}
}
else
{
HtmlInputButton button = new HtmlInputButton("button");
cell.Controls.Add(button);
}
}
}
tblRow.Cells.Add(cell);
}
as you can see, it is a very rough and strict logic: what best you can do is to come up with a recursive function, to construct your HtmlTableRow
Linq's Single method returns the first and only element of a sequence - if there's more than one element it throws the exception you're seeing. As your table row contains two table cells, its o.Elements() method returns an enumerable with two members, and using Single makes it fall over.
Instead of using:
control.Controls.Add(constructHtmlStructure(o.Elements().Single()));
...use:
foreach(var element in o.Elements())
{
control.Controls.Add(constructHtmlStructure(element));
}
That way you iterate over each of the table row's cells, and add each one to the control.
Edit
There's another change you need to make. Your dictionary contains exactly one HtmlTableRow, and exactly one HtmlTableRow, both already created by the time you get into constructHtmlStructure. Instead of having pre-existing objects (and therefore only one instance of each available) you should have your dictionary create a new instance of each type on demand.
So instead of this (adjusted for spacing):
var controlConstructor = new Dictionary<string, HtmlContainerControl>
{
{ "tr", new HtmlTableRow() },
{ "td", new HtmlTableCell() }
};
...
var control = controlConstructor[o.Name.ToString()];
...try this:
var controlConstructor = new Dictionary<string, Func<XElement, HtmlControl>>
{
{ "tr", o => new HtmlTableRow() },
{ "td", o => new HtmlTableCell() }
};
...
var control = controlConstructor[o.Name.ToString()].Invoke(o);
You'll also need a function to create an input from an XElement, something like this:
private static HtmlControl CreateInputFromElement(XElement element)
{
// Create an appropriate HtmlInputControl...
}
...which you can add to your dictionary like this:
var controlConstructor = new Dictionary<string, Func<XElement, HtmlControl>>
{
{ "tr", o => new HtmlTableRow() },
{ "td", o => new HtmlTableCell() },
{ "input", CreateInputFromElement }
};
Related
I have the code below. To explain there will always be values for the 'tl' variable.
At the moment its hard coded to always assume 4 columns in the row, but I want to make it work based on the count of the columns and make it build the levels based on how many columns there are, but there also needs to be a value in the column.
So at the moment if there is a value in column 2, it will build the 'ltwo' variable, and then if there is a value in column 3 it does the 'lthree'.
I want to make it build as many levels as it needs to so im not repeating code and having the same code over and over.
public static List<AdditionalPropertyType> SQLAddPropsStructured(DataTable dataTable, List<AdditionalPropertyType> currentadditionalproperties)
{
foreach (DataRow row in dataTable.Rows)
{
var tl = new AdditionalPropertyType
{
Name = row[0].ToString(),
Value = row[1].ToString()
};
if (!String.IsNullOrEmpty(row[2].ToString()))
{
var ltwo = new AdditionalPropertyType
{
Name = row[2].ToString()
};
var ltwolist = new List<AdditionalPropertyType>();
ltwolist.Add(tl);
ltwo.AdditionalProperties = ltwolist;
if (!String.IsNullOrEmpty(row[3].ToString()))
{
var lthree = new AdditionalPropertyType
{
Name = row[3].ToString()
};
var lthreelist = new List<AdditionalPropertyType>();
lthreelist.Add(ltwo);
lthree.AdditionalProperties = lthreelist;
currentadditionalproperties.Insert(0, lthree);
}
else
currentadditionalproperties.Insert(0, ltwo);
}
else
currentadditionalproperties.Insert(0, tl);
}
return currentadditionalproperties;
}
You can get the columns using the Columns property of the DataTable:
foreach (DataRow row in dataTable.Rows)
{
foreach(DataColumn column in dataTable.Columns)
{
Trace.WriteLine(column.ColumnName + " = " + row[column]);
}
}
You probably want to do something like this: (written on the websites, some minor typos can be present)
You need to iterate the additional columns and check if there is a value present. When there is a value, create a backup reference and renew your property.
public static List<AdditionalPropertyType> SQLAddPropsStructured(DataTable dataTable, List<AdditionalPropertyType> currentadditionalproperties)
{
// check if there are atleast 2 columns defined
if(dataTable.Columns.Count < 2)
throw new Exception("At least two columns are required");
// The result
var currentadditionalproperties = new List<AdditionalPropertyType>();
// iterate the rows
foreach (DataRow row in dataTable.Rows)
{
// create the base property
var tl = new AdditionalPropertyType
{
Name = row[0].ToString(),
Value = row[1].ToString()
};
// check the rest of the columns for additional names
foreach(int index=2;index<dataTable.Columns.Count;index++)
{
var columnValue = row[index].ToString();
// if the column is empty, discontinue the iteration
if(String.IsNullOrEmpty(columnValue))
break;
// create a backup reference.
var previous = tl;
// create a new AdditionalPropertyType
var tl = new AdditionalPropertyType { Name = columnValue };
// Create the list
tl.AdditionalProperties = new List<AdditionalPropertyType>();
// add the previous (backup reference)
tl.AdditionalProperties.Add(previous);
}
// insert the 'chain' of additional properties on the list at possition 0
currentadditionalproperties.Insert(0, tl);
}
// return the list
return currentadditionalproperties;
}
The first step is to reverse your condition and make use of the keyword continue
public static List<AdditionalPropertyType> SQLAddPropsStructured(DataTable dataTable, List<AdditionalPropertyType> currentadditionalproperties)
{
foreach (DataRow row in dataTable.Rows)
{
var tl = new AdditionalPropertyType
{
Name = row[0].ToString(),
Value = row[1].ToString()
};
if (String.IsNullOrEmpty(row[2].ToString())){
currentadditionalproperties.Insert(0, tl);
continue;
}
var ltwo = new AdditionalPropertyType
{
Name = row[2].ToString()
};
var ltwolist = new List<AdditionalPropertyType>();
ltwolist.Add(tl);
ltwo.AdditionalProperties = ltwolist;
if (String.IsNullOrEmpty(row[3].ToString())) {
currentadditionalproperties.Insert(0, ltwo);
continue;
}
var lthree = new AdditionalPropertyType
{
Name = row[3].ToString()
};
var lthreelist = new List<AdditionalPropertyType>();
lthreelist.Add(ltwo);
lthree.AdditionalProperties = lthreelist;
currentadditionalproperties.Insert(0, lthree);
}
return currentadditionalproperties;
}
Now, the code is clearer. The next step is to collect the repeating cases. Note the second case onward is repeating. Thus, do further simplification:
public static List<AdditionalPropertyType> SQLAddPropsStructured(DataTable dataTable, List<AdditionalPropertyType> currentadditionalproperties)
{
foreach (DataRow row in dataTable.Rows)
{
var tlprev = new AdditionalPropertyType
{
Name = row[0].ToString(),
Value = row[1].ToString()
};
bool isTlUpdated = true;
for (int i = 2; i <= 3; ++i) { //change this according to your need
if (String.IsNullOrEmpty(row[i].ToString()) && isTlUpdated){
currentadditionalproperties.Insert(0, tlprev);
isTlUpdated = false;
break; //note that this will now change to break to break from the current for-loop
}
var lnext = new AdditionalPropertyType
{
Name = row[i].ToString()
};
var lnextlist = new List<AdditionalPropertyType>();
lnextlist.Add(tlprev);
lnext.AdditionalProperties = lnextlist;
tlprev = lnext; //need to record this for the next loop or end of the case
isTlUpdated = true;
}
if (isTlUpdated) //correction by Jeroen
currentadditionalproperties.Insert(0, tlprev);
}
return currentadditionalproperties;
}
The key is to simplify the code step-by-step
You haven't posted all your code, so I had to guess in a couple of places (such as what the "currentAdditionalProperties" does).
I think that the below code illustrates what you want to do by making the logic extendable depending on how many columns the data table has.
The trick is to just store the "last thing" in a variable, so it can be used for the "current thing". At the end, whatever was the "last thing" is what you want to store in your "currentAdditionalProperties" object. I have commented so you can see the logic.
private List<AdditionalPropertyType> SQLAddPropsStructured(DataTable dataTable)
{
AdditionalPropertyType lastNewType; // to remember the previous new instance
// for all rows...
foreach (DataRow row in dataTable.Rows)
{
// the first type takes name and value from the first two fields
AdditionalPropertyType newType = new AdditionalPropertyType();
newType.Name = row[0].ToString();
newType.Value = row[1].ToString();
// remember this type: it is used as the AdditionalProperties for the NEXT type
lastNewType = newType;
// additional types start from field 2
int field = 2;
// iterate until we find a NULL field.
// If you want to check for the end of the fields rather than a NULL value, then instead use:
// while(field < dataTable.Columns.Count)
while(!String.IsNullOrEmpty(row[field].ToString()))
{
// create new type
var newSubType = new AdditionalPropertyType();
// get name
Name = row[field].ToString();
// new type takes the PREVIOUS type as its additional parameters
List<AdditionalPropertyType> propertyData = new List<AdditionalPropertyType>();
propertyData.Add(lastNewType);
newSubType.AdditionalProperties = propertyData;
// remember THIS type for the NEXT type
lastNewType = newSubType;
// process next field (if valid)
field++;
}
// put the last set of properties found into the current properties
currentAdditionalProperties.Insert(0, lastNewType);
return currentAdditionalProperties;
}
}
I am trying to use NRefactory to modify an existing piece of C# code. I have tried to use the method described in this article. I'm getting an exception complaining about duplicate changes. Can someone point out what I am doing wrong, and maybe point me in the right direction?
I have the following section of code:
// contains the source code that needs to be modified
var text = "...";
// is the name of the file in which the target class exists
var fileName = "Foo";
// is the name of the target class
var className = "Foo.cs";
// FormattingOptions and TextEditorOptions are properties available to
// the class containing this code...
var parser = new CSharpParser();
var syntaxTree = parser.Parse(text, fileName);
syntaxTree.Freeze();
var document = new StringBuilderDocument();
document.Text = syntaxTree.ToString(FormattingOptions);
using (var documentScript = new DocumentScript(document, FormattingOptions, TextEditorOptions))
{
var typeDeclarations = syntaxTree.Descendants.OfType<TypeDeclaration>().Where(typeDeclaration => typeDeclaration.Name == className);
var templateProviderDeclaration = typeDeclarations.SingleOrDefault();
if (templateProviderDeclaration != null)
{
var constructorDeclarations = templateProviderDeclaration.Descendants.OfType<ConstructorDeclaration>();
foreach (var constructorDeclaration in constructorDeclarations)
{
var assignmentExpressions = constructorDeclaration.Descendants.OfType<AssignmentExpression>();
foreach (var assignmentExpression in assignmentExpressions)
{
var leftExpression = assignmentExpression.Left;
if (leftExpression != null && leftExpression.ToString(FormattingOptions) == "this.templates")
{
var rightExpression = assignmentExpression.Right;
foreach (var arrayInitializerExpression in rightExpression.Children.OfType<ArrayInitializerExpression>())
{
// uses AddTemplate(), which is an extension method
// which takes in an expression and modifies it, and
// returns an expression
var newExpression = ((ArrayInitializerExpression)arrayInitializerExpression).AddTemplate();
documentScript.Replace(arrayInitializerExpression, newExpression);
}
}
}
}
}
// text retrieved here is *clobbered*, by a few characters
text = documentScript.CurrentDocument.Text;
}
Other Code...
public static class ExtensionMethods
{
public static ArrayInitializerExpression AddTemplate(this ArrayInitializerExpression expression)
{
// TODO : will modify the AST for the target expression
return (ArrayInitializerExpression)expression.Clone();
}
}
Update:
I am trying to change a line like this:
this.templates = new Dictionary<string, ITemplate>
{
{ "Foo", new FooTemplate() }
};
to:
this.templates = new Dictionary<string, ITemplate>
{
{ "Foo", new FooTemplate() },
{ "Bar", new BarTemplate() }
};
For the purposes of this question, the exact specifics of the change I am trying to make aren't important though. I suspect that I have something not constructed quite correctly, but I cannot figure out if that's the issue.
I currently have a dictionary which contains a key value and a list associated with that key.
I have read How to bind Dictionary to ListBox in winforms and when I try to implement that it just displays the key value.
What I am trying to do is have two separate listboxs. In box 1 you select the key value, when this happens box 2 displays the list. The current code is below:
var xmlDoc2 = new XmlDocument();
xmlDoc2.Load(textBox1.Text);
Dictionary<string, List<string>> dict = new Dictionary<string, List<string>>(StringComparer.OrdinalIgnoreCase);
var node = xmlDoc2.SelectNodes("pdml/packet/proto[#name='ip']/#showname");
foreach (XmlAttribute attribute1 in node)
{
string ip = attribute1.Value;
var arr = ip.Split(); var src = arr[5]; var dst = arr[8];
List<string> l;
if (!dict.TryGetValue(src, out l))
{
dict[src] = l = new List<string>();
}
l.Add(dst);
listBoxSRC.DataSource = new BindingSource(dict, null);
listBoxSRC.DisplayMember = "Value";
listBoxSRC.ValueMember = "Key";
}
What this does so far is displays the key value in listBoxSRC, which is fine. What I need to do is display the list in listBoxDST.
I also looked at using ListView to rectify this issue but I couldn't figure out how it worked.
I know there should be a listBoxSRC_SelectedIndexChange somewhere but I keep getting 'dict doesn't appear in this context' errors.
Thanks
I jotted up something real quick with a pair of list boxes. Just make any form with a pair listboxes in it and wire up the event to try it yourself. By using the SelectedItem and casting it as the KeyValuePair that it is, you don't have to declare that dictionary outside of the method scope, as shown below.
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
listBox1.DataSource = new BindingSource(new Dictionary<string, List<string>>
{
{"Four-Legged Mammals", new List<string>{"Cats", "Dogs", "Pigs"}},
{"Two-Legged Mammals", new List<string>{"Humans", "Chimps", "Apes"}}
}, null);
listBox1.DisplayMember = "Value";
listBox1.ValueMember = "Key";
}
private void listBox1_SelectedValueChanged(object sender, EventArgs e)
{
if (listBox1.SelectedItem != null)
{
var keyValue = (KeyValuePair<string, List<String>>) listBox1.SelectedItem;
listBox2.DataSource = keyValue.Value;
}
else
{
listBox2.DataSource = null;
}
}
I have a IEnumerable<object> dataSource which contains a collection anonymous types. The actual structure of the anonymous type won't be known at design time, so I'm trying to find a generic solution that can handle any anonymous type.
How can I load them into epplus to create a spreadsheet? I have a worksheet called ws and I tried:
ws.Cells["A1"].LoadFromCollection(dataSource, true);
However when that runs it outputs all of the anonymous type's properties into a single cell:
{ Id = 10000, Title = This is a test }
I've tried passing in MemberInfo using:
var members = dataSource.First().GetType().GetMembers();
ws.Cells["A1"].LoadFromCollection(this._dataSource, true,
TableStyles.Medium1, BindingFlags.Public, members);
But that throws an exception:
Supplied properties in parameter Properties must be of the same type as T
Any suggestions on how I can create a spreadsheet using anonymous types in c#?
I have tested
using (var excel = new OfficeOpenXml.ExcelPackage())
{
var sheet = excel.Workbook.Worksheets.Add("Test");
sheet.Cells["A1"].LoadFromCollection(dataSource, true);
excel.SaveAs(new FileInfo(#"C:\Temp\Test.xlsx"));
}
with this sample data:
var dataSource = Enumerable.Range(1, 100).Select(i => new{ ID=i, Title="Title " + i });
It works fine. It creates two columns with the correct headers and 100 rows.
But you should use anonymous types only if you know the structure at compile time.
You could use a DataTable and LoadFromDataTable instead. Since i don't know how you create the anonymous type i show you just a small sample:
DataTable dataSource = new DataTable();
dataSource.Columns.Add("Id"); // default type is string
dataSource.Columns.Add("Title");
// add other columns
dataSource.Rows.Add("1", "Title1");
// add other rows
using (var excel = new OfficeOpenXml.ExcelPackage())
{
var sheet = excel.Workbook.Worksheets.Add("Test");
sheet.Cells["A1"].LoadFromDataTable(dataSource, true);
excel.SaveAs(new FileInfo(#"C:\Temp\Test.xlsx"));
}
You could group the anonymous types to make it easier for exporting with dataTables. The bug "Supplied properties in parameter Properties must be of the same type as T" is still there and a workaround is using DataTables.
// Imagine list is your main datasource
IEnumerable<object> list = Enumerable.Empty<object>(); // Data Source of <object>
// Added anon types at runtime added to the object list
var anonTypesOne = new object[]
{
new { GuidID = Guid.NewGuid(), StringProperty = "the string property" },
new { IntegerID = 1, IntegerProperty = 99 }
};
var anonTypesTwo = new object[]
{
new { StringID = "1", BooleanProperty = true, NumberProperty = 3, StringProperty = "Four" },
new { GuidID = Guid.NewGuid(), NumberThree = 3 },
new { GuidID = Guid.NewGuid(), NumberThree = 3 },
new { GuidID = Guid.NewGuid(), NumberThree = 3 }
};
list = list.Concat(anonTypesOne).Concat(anonTypesTwo);
// Grouping works on anon types so we can group the export into their own tables
var groupings = list.GroupBy(i => i.GetType());
using(var package = new ExcelPackage(new FileInfo("C:\\Temp\\Anon.xlsx")))
{
var ws = package.Workbook.Worksheets.Add("Anonymous Types");
// add each "anon type matched grouping"
foreach(var grouping in groupings)
{
var isNew = ws.Dimension == null; // the sheet is empty if Dimension is null.
var row = 0;
if(isNew)
{
row = 1; // start from the first row
}
else
{
// otherwise there are tables already, start from the bottom
row = ws.Dimension.End.Row;
}
// because of EPP inheritance bug of T, we can just use dataTable
DataTable dt = new DataTable(grouping.Key.Name);
var properties = grouping.Key.GetProperties(); // Get anon type Properties
foreach(var property in properties)
{
dt.Columns.Add(property.Name);
}
foreach(var item in grouping.ToList())
{
var dataRow = dt.NewRow();
foreach(var p in properties) // populate a single row
{
dataRow[p.Name] = p.GetValue(item); // item is anon object instance
}
dt.Rows.Add(dataRow);
}
if(isNew) // load into the top most left cell of the worksheet
ws.Cells[1, 1].LoadFromDataTable(dt, PrintHeaders: true);
else // load from the dimension of current items + 1 row for spacing
ws.Cells[ws.Dimension.End.Row + 1, 1].LoadFromDataTable(dt, PrintHeaders: true);
ws.InsertRow(ws.Dimension.End.Row + 2, 5); // Insert some padding between each group
}
package.Save();
}
I was, this thread is older, but I'm looking for the same problem.
With the following code (VB) I have success.
Carsten
Dim targetFile = New IO.FileInfo(sFN)
Dim dataSource = Enumerable.Range(0, 1).Select(Function(i) New With {.ID = 1000, .Titel = "This is a test "}).ToList
Using epp = New OfficeOpenXml.ExcelPackage(targetFile)
Dim ws = epp.Workbook.Worksheets.Add("lst_Anonymous")
ws.Cells(1, 1).LoadFromCollection(dataSource, True,
OfficeOpenXml.Table.TableStyles.Medium1,
Reflection.BindingFlags.Public,
dataSource.GetType.GetGenericArguments()(0).GetProperties)
epp.Save()
End Using
I need to print a list of countries that use dollars as a currency from a web service.
the data comes form a class called country services which contains this tuple:
public static IEnumerable<Tuple<string, string, string, string>> GetCountryData()
{
var countryService = new CountryServiceProxy.country();
var xmlStringResult = countryService.GetCurrencies();
var result = new List<Tuple<string, string, string, string>>();
var xPathDoc = new XPathDocument(new XmlTextReader(new StringReader(xmlStringResult)));
var navigator = xPathDoc.CreateNavigator();
var nodes = navigator.Select("//Table");
var nodeNames = new[] {"Name", "CountryCode", "Currency", "CurrencyCode"};
while (nodes.MoveNext())
{
var nodeValues = new[] {string.Empty, string.Empty, string.Empty, string.Empty};
for (var i = 0; i < nodeNames.Length; i++)
{
var node = nodes.Current.SelectSingleNode(nodeNames[i]);
if (node != null)
{
nodeValues[i] = node.Value;
}
}
result.Add(new Tuple<string, string, string, string>(nodeValues[0], nodeValues[1], nodeValues[2], nodeValues[3]));
}
return result;
}
I need to call that method and use it to print out a list with countries that use dollars:
private static IEnumerable<Country> Excercise4()
{
// var data = CountryService.GetCountryData().Where(x => x.Item3.Contains("Dollar"));
// ////var data = CountryService.GetCountryData().Where(x => x.Item3 == "Dollar");
// //Console.WriteLine(data);
return new List<Country>
{
new Country("da", "C1", "$", "Dollar"),
new Country("Country2", "C3", "$", "Dollar")
};
}
so far my method looks like this. i cant seem to figure out how to print out the tuple , havent used tuple before.
my write method is as follows:
ConsoleHelper.PrintCountryResults(Excercise4(), "Return a list of Country objects who's currency is Dollar");
ConsoleHelper.Pause(PauseMessage);
and the console helper class looks like this :
public static void PrintCountryResults(IEnumerable<Country> results, string title)
{
PrintHeader(title);
foreach (var result in results)
{
Console.WriteLine("{0, -30}{1, -5}{2, -15}{3, -5}", result.CountryName, result.CountryCode, result.CurrencyName, result.CurrencyCode);
}
}
any help would be appreciated as i said havent used tuple before first try at them ..
Thanx
You can filter the properties of the tuple by referencing the ItemX property with X being the 1-based index of the property that you're interested in. in you case if the currency is the third item, filtering the tuples would look like
var countriesUsingDollarAsACurrency = GetCountryData().Where(tuple => tuple.Item3 == "Dollar");