Serializing a list of objects to XDocument - c#

I'm trying to use the following code to serialize a list of objects into XDocument, but I'm getting an error stating that "Non white space characters cannot be added to content
"
public XDocument GetEngagement(MyApplication application)
{
ProxyClient client = new ProxyClient();
List<Engagement> engs;
List<Engagement> allEngs = new List<Engagement>();
foreach (Applicant app in application.Applicants)
{
engs = new List<Engagement>();
engs = client.GetEngagements("myConnString", app.SSN.ToString());
allEngs.AddRange(engs);
}
DataContractSerializer ser = new DataContractSerializer(allEngs.GetType());
StringBuilder sb = new StringBuilder();
System.Xml.XmlWriterSettings xws = new System.Xml.XmlWriterSettings();
xws.OmitXmlDeclaration = true;
xws.Indent = true;
using (System.Xml.XmlWriter xw = System.Xml.XmlWriter.Create(sb, xws))
{
ser.WriteObject(xw, allEngs);
}
return new XDocument(sb.ToString());
}
What am I doing wrong? Is it the XDocument constructor that doesn't take a list of objects? how do solve this?

I would think that last line should be
return XDocument.Parse(sb.ToString());
And it might be an idea to cut out the serializer altogether, it should be easy to directly create an XDoc from the List<> . That gives you full control over the outcome.
Roughly:
var xDoc = new XDocument( new XElement("Engagements",
from eng in allEngs
select new XElement ("Engagement",
new XAttribute("Name", eng.Name),
new XElement("When", eng.When) )
));

The ctor of XDocument expects other objects like XElement and XAttribute. Have a look at the documentation. What you are looking for is XDocument.Parse(...).
The following should work too (not tested):
XDocument doc = new XDocument();
XmlWriter writer = doc.CreateNavigator().AppendChild();
Now you can write directly into the document without using a StringBuilder. Should be much faster.

I have done the job this way.
private void button2_Click(object sender, EventArgs e)
{
List<BrokerInfo> listOfBroker = new List<BrokerInfo>()
{
new BrokerInfo { Section = "TestSec1", Lineitem ="TestLi1" },
new BrokerInfo { Section = "TestSec2", Lineitem = "TestLi2" },
new BrokerInfo { Section = "TestSec3", Lineitem ="TestLi3" }
};
var xDoc = new XDocument(new XElement("Engagements",
new XElement("BrokerData",
from broker in listOfBroker
select new XElement("BrokerInfo",
new XElement("Section", broker.Section),
new XElement("When", broker.Lineitem))
)));
xDoc.Save("D:\\BrokerInfo.xml");
}
public class BrokerInfo
{
public string Section { get; set; }
public string Lineitem { get; set; }
}

Related

Lucene.Net (4.8) AutoComplete / AutoSuggestion

I'd like to implement a searchable index using Lucene.Net 4.8 that supplies a user with suggestions / autocomplete for single words & phrases.
The index has been created successfully; the suggestions are where I've stalled.
Version 4.8 seems to have introduced a substantial number of breaking changes, and none of the available samples I've found work.
Where I stand
For reference, LuceneVersion is this:
private readonly LuceneVersion LuceneVersion = LuceneVersion.LUCENE_48;
Solution 1
I've tried this, but can't get past reader.Terms:
public void TryAutoComplete()
{
var analyzer = new EnglishAnalyzer(LuceneVersion);
var config = new IndexWriterConfig(LuceneVersion, analyzer);
RAMDirectory dir = new RAMDirectory();
using (IndexWriter iw = new IndexWriter(dir, config))
{
Document d = new Document();
TextField f = new TextField("text","",Field.Store.YES);
d.Add(f);
f.SetStringValue("abc");
iw.AddDocument(d);
f.SetStringValue("colorado");
iw.AddDocument(d);
f.SetStringValue("coloring book");
iw.AddDocument(d);
iw.Commit();
using (IndexReader reader = iw.GetReader(false))
{
TermEnum terms = reader.Terms(new Term("text", "co"));
int maxSuggestsCpt = 0;
// will print:
// colorado
// coloring book
do
{
Console.WriteLine(terms.Term.Text);
maxSuggestsCpt++;
if (maxSuggestsCpt >= 5)
break;
}
while (terms.Next() && terms.Term.Text.StartsWith("co"));
}
}
}
reader.Terms no longer exists. Being new to Lucene, it's unclear how to refactor this.
Solution 2
Trying this, I'm thrown an error:
public void TryAutoComplete2()
{
using(var analyzer = new EnglishAnalyzer(LuceneVersion))
{
IndexWriterConfig config = new IndexWriterConfig(LuceneVersion, analyzer);
RAMDirectory dir = new RAMDirectory();
using(var iw = new IndexWriter(dir,config))
{
Document d = new Document()
{
new TextField("text", "this is a document with a some words",Field.Store.YES),
new Int32Field("id", 42, Field.Store.YES)
};
iw.AddDocument(d);
iw.Commit();
using (IndexReader reader = iw.GetReader(false))
using (SpellChecker speller = new SpellChecker(new RAMDirectory()))
{
//ERROR HERE!!!
speller.IndexDictionary(new LuceneDictionary(reader, "text"), config, false);
string[] suggestions = speller.SuggestSimilar("dcument", 5);
IndexSearcher searcher = new IndexSearcher(reader);
foreach (string suggestion in suggestions)
{
TopDocs docs = searcher.Search(new TermQuery(new Term("text", suggestion)), null, Int32.MaxValue);
foreach (var doc in docs.ScoreDocs)
{
System.Diagnostics.Debug.WriteLine(searcher.Doc(doc.Doc).Get("id"));
}
}
}
}
}
}
When debugging, speller.IndexDictionary(new LuceneDictionary(reader, "text"), config, false); throws a The object cannot be set twice! error, which I can't explain.
Any thoughts are welcome.
Clarification
I'd like to return a list of suggested terms for a given input, not the documents or their full content.
For example, if a document contains "Hello, my name is Clark. I'm from Atlanta," and I submit "Atl," then "Atlanta" should come back as a suggestion.
If I am understanding you correctly you may be over-complicating your index design a bit. If your goal is to use Lucene for auto-complete, you want to create an index of the terms you consider complete. Then simply query the index using a PrefixQuery using a partial word or phrase.
using Lucene.Net.Analysis;
using Lucene.Net.Analysis.En;
using Lucene.Net.Documents;
using Lucene.Net.Index;
using Lucene.Net.Search;
using Lucene.Net.Store;
using Lucene.Net.Util;
using System;
using System.Linq;
namespace LuceneDemoApp
{
class LuceneAutoCompleteIndex : IDisposable
{
const LuceneVersion Version = LuceneVersion.LUCENE_48;
RAMDirectory Directory;
Analyzer Analyzer;
IndexWriterConfig WriterConfig;
private void IndexDoc(IndexWriter writer, string term)
{
Document doc = new Document();
doc.Add(new StringField(FieldName, term, Field.Store.YES));
writer.AddDocument(doc);
}
public LuceneAutoCompleteIndex(string fieldName, int maxResults)
{
FieldName = fieldName;
MaxResults = maxResults;
Directory = new RAMDirectory();
Analyzer = new EnglishAnalyzer(Version);
WriterConfig = new IndexWriterConfig(Version, Analyzer);
WriterConfig.OpenMode = OpenMode.CREATE_OR_APPEND;
}
public string FieldName { get; }
public int MaxResults { get; set; }
public void Add(string term)
{
using (var writer = new IndexWriter(Directory, WriterConfig))
{
IndexDoc(writer, term);
}
}
public void AddRange(string[] terms)
{
using (var writer = new IndexWriter(Directory, WriterConfig))
{
foreach (string term in terms)
{
IndexDoc(writer, term);
}
}
}
public string[] WhereStartsWith(string term)
{
using (var reader = DirectoryReader.Open(Directory))
{
IndexSearcher searcher = new IndexSearcher(reader);
var query = new PrefixQuery(new Term(FieldName, term));
TopDocs foundDocs = searcher.Search(query, MaxResults);
var matches = foundDocs.ScoreDocs
.Select(scoreDoc => searcher.Doc(scoreDoc.Doc).Get(FieldName))
.ToArray();
return matches;
}
}
public void Dispose()
{
Directory.Dispose();
Analyzer.Dispose();
}
}
}
Running this:
var indexValues = new string[] { "apple fruit", "appricot", "ape", "avacado", "banana", "pear" };
var index = new LuceneAutoCompleteIndex("fn", 10);
index.AddRange(indexValues);
var matches = index.WhereStartsWith("app");
foreach (var match in matches)
{
Console.WriteLine(match);
}
You get this:
apple fruit
appricot

Append a record to Xml ASP.Net MVC5

Based on the index in the controller, how would I append a record to an XML file? I've done some research, but I can't seem to wrap my head around it.
Index(controller)
public ActionResult Index(string sortOrder)
{
XmlDocument doc = new XmlDocument();
doc.Load("C:\\Users\\Matt.Dodson\\Desktop\\SampleWork\\PersonsApplicationFromXMLFile\\PersonsApplicationFromXMLFile\\DAL\\Personal.xml");
IEnumerable<Personal> persons = doc.SelectNodes("/Persons/record").Cast<XmlNode>().Select(node => new Personal()
{
ID = node["ID"].InnerText,
Name = node["Name"].InnerText,
Email = node["Email"].InnerText,
DateOfBirth = node["DateOfBirth"].InnerText,
Gender = node["Gender"].InnerText,
City = node["City"].InnerText
});
switch (sortOrder)
{
case "ID":
persons = persons.OrderBy(Personal => Personal.ID);
break;
case "Name":
persons = persons.OrderBy(Personal => Personal.Name);
break;
case "City":
persons = persons.OrderBy(Personal => Personal.City);
break;
default:
break;
}
return View(persons.ToList());
}
What I've tried:
Create(Controller)
[HttpPost]
public ActionResult Create(FormCollection collection)
{
string xmlFile = "C:\\Users\\Matt.Dodson\\Desktop\\SampleWork\\PersonsApplicationFromXMLFile\\PersonsApplicationFromXMLFile\\DAL\\Personal.xml";
try
{
XmlDocument doc = new XmlDocument();
doc.Load(xmlFile);
IEnumerable<Personal> persons = doc.SelectNodes("/Persons/record")
.Cast<XmlNode>()
.Select(node => new Personal()
{
ID = node["ID"].InnerText,
Name = node["Name"].InnerText,
Email = node["Email"].InnerText,
DateOfBirth = node["DateOfBirth"].InnerText,
Gender = node["Gender"].InnerText,
City = node["City"].InnerText
});
persons.appendTo(xmlFile);
return RedirectToAction("Index");
}
catch
{
return View();
}
}
I am bad at syntax so this is probably all wrong.
For starters that are a few classes available in C#, suing the specific class that you are using, you should be able to do the following
XDocument doc = XDocument.Load(xmlFile);
var parent = doc.Descendants("NameOfParentTag").FirstOrDefault();//if there is only one
parent.Add(new XElement("personal",
new XElement("ID", ID_Value),
new XElement("Name" = Name_Value),
new XElement("Email", Email_value)));
doc.Save(xmlFile);
That will append the value to your xmlFile, the NameOfParentTag is the name of the parentLocation where you want to insert the value, in your case I would assume that would be record
The second way would be
doc.Load(xmlFile);
XmlNode editNode = doc.SelectSingleNode("targetNode");
XmlNode node = nodes[0];//get the specific node, not sure about your xml structure
XmlElement elem = node.OwnerDocument.CreateElement("personal");
elem.InnerXml = personal.SerializeAsXml();
node.AppendChild(elem);
where the SerializeAsXml method looks like
public static string SerializeAsXml(this Personal personal)
{
var emptyNamepsaces = new XmlSerializerNamespaces(new[] { XmlQualifiedName.Empty });
var settings = new XmlWriterSettings();
settings.Indent = true;
settings.OmitXmlDeclaration = true;
//serialize the binding
string xmlOutput = string.Empty;
using (StringWriter stream = new StringWriter())
using (XmlWriter xmlWriter = XmlWriter.Create(stream, settings))
{
XmlSerializer serializer = new XmlSerializer(personal.GetType());
serializer.Serialize(xmlWriter, obj, emptyNamepsaces);
xmlOutput = stream.ToString();
}
return xmlOutput;
}
NB, for the above method you would need to decorate your XML with the necessary attributes

Xml Tags get wrong encoded

I'm trying to write a list of objects to valid xml. My code looks basicly like this:
public class Person
{
public string First { get; set; }
public string Last { get; set; }
}
List<Person> people = new List<Person>();
XElement elements = new XElement("People",
from p in people
select new XElement("Person",
new XElement("First", p.First),
new XElement("Last", p.Last)));
string output = elements.ToString();
Unfortunately output when written to a file looks like this:
<People>
<Person>
<First>Tom</First>
<Last>Hanks</Last>
</Person>
</People>
XDeclaration declaration = new XDeclaration("1.0", "utf-8", "yes");
XProcessingInstruction procInstruction = new XProcessingInstruction("xml-stylesheet", "type='text/xsl'");
XElement root = new XElement("Store");
XDocument settingsFile = new XDocument(declaration, procInstruction, root);
foreach (string key in persistentSettings.Keys)
{
string value = persistentSettings[key];
if (!string.IsNullOrEmpty(value))
{
XElement setting = new XElement("Setting", new XAttribute("Name", key));
setting.Value = value;
root.Add(setting);
}
}
settingsFile.Save(SettingsFileName);
What is that I'm doing wrong here?
I had to use the parse function in the Constructor of xElement
XElement setting
= new XElement("Setting", new XAttribute("Name", key), XElement.Parse(value));
instead of
XElement setting = new XElement("Setting", new XAttribute("Name", key));
setting.Value = value;

Add Object to existing Xelement

I have a object obj with 2 properties p1, p2. and a XElement like:
<root><AA><BB>BB</BB></AA></root>
I'd like to make my Xelement as:
<root><AA><BB>BB</BB><CC><p1>val1</p1><p2>val2</p2></CC></AA></root>
I make a new XElement from obj
XElement x = new XElement("CC",new XElement("p1", obj.p1),new XElement("p2", obj.p2));
and insert it in AA element. is ther a better way by serializing my obj and convert it to XElement? (Because My object can change in the future) . Thanks for any help.
Here is my attempt to use XmlSerializer:
XElement xelem = reqRet.RequestDefinition;
xelem.Descendants("AA").ToList().ForEach(reqitem =>
{
using (MemoryStream ms = new MemoryStream())
{
using (TextWriter tw = new StreamWriter(ms))
{
XmlSerializer ser = new XmlSerializer(typeof(obj));
ser.Serialize(tw, ObjVAL);
schElem = new XElement( XElement.Parse(Encoding.ASCII.GetString(ms.ToArray())));
reqitem.Add(schElem);
}
}
reqitem.Add(schElem);
});
Since you're open to using XmlSerializer, use the XmlRoot attribute; try adding the following to your class declaration:
[XmlRoot(Namespace = "www.contoso.com",
ElementName = "CC",
DataType = "string",
IsNullable=true)]
public class MyObj
{
...
See https://msdn.microsoft.com/en-us/library/system.xml.serialization.xmlrootattribute%28v=vs.110%29.aspx for more information.
After that, you can use this code:
XElement xelem = XElement.Parse("<root><AA><BB>BB</BB></AA></root>");
MyObj myObj = new MyObj();
XmlSerializer ser = new XmlSerializer(typeof(MyObj));
foreach (XElement reqitem in xelem.Descendants("AA"))
{
using (MemoryStream ms = new MemoryStream())
{
ser.Serialize(ms, myObj);
reqitem.Add(XElement.Parse(Encoding.UTF8.GetString(ms.ToArray())));
}
}
This gives the desired output.
If you want to remove the XMLNS declarations, you can use .Attributes.Remove() after creating the XElement.

How to use XmlSerializer to deserialize a simple collection into an instance of List<string>

I haven't been able to find a question related to my specific problem.
What I am trying to do is take a list of Xml nodes, and directly deserialize them to a List without having to create a class with attributes.
So the xml (myconfig.xml) would look something like this...
<collection>
<item>item1</item>
<item>item2</item>
<item>item3</item>
<item>etc...</item>
</collection>
In the end I would like a list of items as strings.
The code would look like this.
XmlSerializer serializer = new XmlSerializer( typeof( List<string> ) );
using (XmlReader reader = XmlReader.Create( "myconfig.xml" )
{
List<string> itemCollection = (List<string>)serializer.Deserialize( reader );
}
I'm not 100% confident that this is possible, but I'm guessing it should be. Any help would be greatly appreciated.
Are you married to the idea of using a serializer? If not, you can try Linq-to-XML. (.NET 3.5, C# 3 [and higher])
Based on your provided XML file format, this is the simple code.
// add 'using System.Xml.Linq' to your code file
string file = #"C:\Temp\myconfig.xml";
XDocument document = XDocument.Load(file);
List<string> list = (from item in document.Root.Elements("item")
select item.Value)
.ToList();
Ok, interestingly enough I may have found half the answer by serializing an existing List.
The result I got is as follows...
This following code:
List<string> things = new List<string> { "thing1", "thing2" };
XmlSerializer serializer = new XmlSerializer(typeof(List<string>), overrides);
using (TextWriter textWriter = new StreamWriter("things.xml"))
{
serializer.Serialize(textWriter, things);
}
Outputs a result of:
<?xml version="1.0" encoding="utf-8"?>
<ArrayOfString xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<string>thing1</string>
<string>thing2</string>
</ArrayOfString>
I can override the root node by passing an XmlAttributeOverrides instance to the second parameter of the XmlSerializer constructor. It is created like this:
XmlAttributeOverrides overrides = new XmlAttributeOverrides();
XmlAttributes attributes = new XmlAttributes { XmlRoot = new XmlRootAttribute("collection") };
overrides.Add( typeof(List<string>), attributes );
This will change "ArrayOfString" to "collection". I still have not figured out how to control the name of the string element.
To customize List element names using XmlSerializer, you have to wrap the list.
[XmlRoot(Namespace="", ElementName="collection")]
public class ConfigWrapper
{
[XmlElement("item")]
public List<string> Items{ get; set;}
}
Usage:
var itemsList = new List<string>{"item1", "item2", "item3"};
var cfgIn = new ConfigWrapper{ Items = itemsList };
var xs = new XmlSerializer(typeof(ConfigWrapper));
string fileContent = null;
using (var sw = new StringWriter())
{
xs.Serialize(sw, cfgIn);
fileContent = sw.ToString();
Console.WriteLine (fileContent);
}
ConfigWrapper cfgOut = null;
using (var sr = new StringReader(fileContent))
{
cfgOut = xs.Deserialize(sr) as ConfigWrapper;
// cfgOut.Dump(); //view in LinqPad
if(cfgOut != null)
// yields 'item2'
Console.WriteLine (cfgOut.Items[1]);
}
Output:
// fileContent:
<?xml version="1.0" encoding="utf-16"?>
<collection xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<item>item1</item>
<item>item2</item>
<item>item3</item>
</collection>
If you don't want to wrap the list, the DataContractSerializer will allow you to custom name the elements if you subclass it:
[CollectionDataContract(Name = "collection", ItemName = "item", Namespace = "")]
public class ConfigWrapper : List<string>
{
public ConfigWrapper() : base() { }
public ConfigWrapper(IEnumerable<string> items) : base(items) { }
public ConfigWrapper(int capacity) : base(capacity) { }
}
Usage And Output:
var cfgIn = new ConfigWrapper{ "item1", "item2", "item3" };
var ds = new DataContractSerializer(typeof(ConfigWrapper));
string fileContent = null;
using (var ms = new MemoryStream())
{
ds.WriteObject(ms, cfgIn);
fileContent = Encoding.UTF8.GetString(ms.ToArray());
Console.WriteLine (fileContent);
}
// yields: <collection xmlns:i="http://www.w3.org/2001/XMLSchema-instance"><item>item1</item><item>item2</item><item>item3</item></collection>
ConfigWrapper cfgOut = null;
using (var sr = new StringReader(fileContent))
{
using(var xr = XmlReader.Create(sr))
{
cfgOut = ds.ReadObject(xr) as ConfigWrapper;
// cfgOut.Dump(); //view in LinqPad
if(cfgOut != null)
// yields 'item2'
Console.WriteLine (cfgOut[1]);
}
}

Categories

Resources