xdocument re writing xml document [duplicate] - c#

I have a method which load XML to a XDocument and modify its elements then save.
But when I reload it. I got this error :
Unexpected XML declaration. The XML declaration must be the first node in the document, and no white space characters are allowed to appear before it.
I checking the XML and see that the XDocument didn't save the changed but create a duplicate and save.
It save the old one and the new one like this example xml :
<?xml version="1.0" encoding="UTF-8"?>
<Ungdungs>
<Ungdung>
<Name>HERE City Lens</Name>
<Id>b0a0ac22-cf9e-45ba-8120-815450e2fd71</Id>
<Path>/Icon/herecitylens.png</Path>
<Version>Unknown</Version>
<Category>HERE</Category>
<Date>Uknown</Date>
</Ungdung>
<?xml version="1.0" encoding="UTF-8"?>
<Ungdungs>
<Ungdung>
<Name>HERE City Lens</Name>
<Id>b0a0ac22-cf9e-45ba-8120-815450e2fd71</Id>
<Path>/Icon/herecitylens.png</Path>
<Version>1.0.0.0</Version>
<Category>HERE</Category>
<Date>Uknown</Date>
</Ungdung>
Here the code I used to modify and save XML :
using (Stream stream = storage.OpenFile("APPSDATA.xml", FileMode.Open, FileAccess.ReadWrite))
{
//var xdoc = XDocument.Load("APPSDATA.xml");
var xdoc = XDocument.Load(stream, LoadOptions.None);
var listapp = from c in xdoc.Descendants("Ungdung") select c;
foreach (XElement app in listapp)
{
var xElement = app.Element("Name");
if (xElement != null)
progressIndicator.Text = "Checking " + xElement.Value + "...";
var element = app.Element("Id");
if (element != null)
{
var appId = element.Value;
var appVersion = await GetAppsVersion(appId);
app.Element("Version").Value = appVersion.ToString();
}
}
xdoc.Save(stream);
}
How can I solve this problem ?

Looks like you're appending modified document at the end of current file content. That's why you can't parse it later again.
I would split read and write parts into different using statements:
XDocument xdoc;
using (Stream stream = storage.OpenFile("APPSDATA.xml", FileMode.Open, FileAccess.Read))
{
xdoc = XDocument.Load(stream, LoadOptions.None);
}
var listapp = from c in xdoc.Descendants("Ungdung") select c;
foreach (XElement app in listapp)
{
var xElement = app.Element("Name");
if (xElement != null)
progressIndicator.Text = "Checking " + xElement.Value + "...";
var element = app.Element("Id");
if (element != null)
{
var appId = element.Value;
var appVersion = await GetAppsVersion(appId);
app.Element("Version").Value = appVersion.ToString();
}
}
using (Stream stream = storage.OpenFile("APPSDATA.xml", FileMode.Truncate, FileAccess.Write))
{
xdoc.Save(stream);
}
Setting FileMode.Truncate on second using statement will clear previous file content, what should fix your problem.

Related

String.Replace not replacing

I'm doing some file clean up in an xml file and trying to use string.Replace to replace certain text blocks but it does not seem to be replacing the text that I am searching on.
My clean up code is follows
private Stream PrepareFile(string path)
{
string data = File.ReadAllText(path);
var newData = data.Replace("<a:FMax xmlns:b=\"http://www.w3.org/2001/XMLSchema\" i:type=\"b:string\"/>", "<a:FMax>0</a:FMax>")
.Replace("<a:KVy xmlns:b=\"http://www.w3.org/2001/XMLSchema\" i:type=\"b:string\"/>", "<a:KVy>0</a:KVy>")
.Replace("<a:Td xmlns:b=\"http://www.w3.org/2001/XMLSchema\" i:type=\"b:string\"/>", "<a:Td>0</a:Td>")
.Replace("<a:VyLim xmlns:b=\"http://www.w3.org/2001/XMLSchema\" i:type=\"b:string\"/>", "<a:VyLim>0</a:VyLim>");
var newData2 = newData.Replace("<a:VxTableSxI3_2I xmlns:b=\"http://www.w3.org/2001/XMLSchema\" i:type=\"b:string\"/>", "<a:VxTableSxI3_2I>0</a:VxTableSxI3_2I>");
byte[] bytes = Encoding.ASCII.GetBytes(newData2);
return new MemoryStream(bytes);
}
I should be able to write back to the original 'data' variable, but I split the variables out to be able to compare the strings before and after the replace. My xml file contains the following values(copied verbatim)
<a:LongitudinalTracker z:Id="i58">
<Name xmlns="http://schemas.datacontract.org/2004/07/HmsSim.EntityModule.BaseTypes" i:nil="true"/>
<a:FMax xmlns:b="http://www.w3.org/2001/XMLSchema" i:type="b:string"/>
<a:K>2</a:K>
<a:KVy xmlns:b="http://www.w3.org/2001/XMLSchema" i:type="b:string"/>
<a:Td xmlns:b="http://www.w3.org/2001/XMLSchema" i:type="b:string"/>
<a:VyLim xmlns:b="http://www.w3.org/2001/XMLSchema" i:type="b:string"/>
</a:LongitudinalTracker>
And the before and after strings look identical. I'm sure I am missing something silly, but I can't see what it is. Most of the answers to similar questions point out that the original code is not using the return value, but in this case I am definitely using the return value.
As suggested I am posting the code that ended up solving this.
private Stream PrepareFile(string path)
{
string data = File.ReadAllText(path);
var xml = XDocument.Parse(data);
XNamespace ns = "http://schemas.datacontract.org/2004/07/HmsSim.EntityModule.Entities.SimulationEntities.Track";
var longTracker = from item in xml.Descendants(ns + "LongitudinalTracker") select item;
foreach (var xElement in longTracker.Elements())
{
XNamespace nsI = "http://www.w3.org/2001/XMLSchema-instance";
if (xElement.Attribute(nsI + "type") != null)
{
xElement.Attribute(nsI + "type").Remove();
XAttribute attribute = new XAttribute(nsI + "nil", "true");
xElement.Add(attribute);
}
}
var latTracker = from item in xml.Descendants(ns + "LateralTracker") select item;
foreach (var xElement in latTracker.Elements())
{
XNamespace nsI = "http://www.w3.org/2001/XMLSchema-instance";
if (xElement.Attribute(nsI + "type") != null)
{
xElement.Attribute(nsI + "type").Remove();
XAttribute attribute = new XAttribute(nsI + "nil", "true");
xElement.Add(attribute);
}
}
Stream stream = new MemoryStream();
xml.Save(stream);
// Rewind the stream ready to read from it elsewhere
stream.Position = 0;
return stream;
}
This code works and is less brittle than the original code. As always, suggestions are welcome. Thanks to everyone who commented and led me towards this answer, I appreciate it.

Creating a dynamic xml document

Hi you is it possible to create a dynamica xml with xdocument I've been trying but it appears that it returns an exception about having a wrong structure
My code is the following
public string ReadTest(Stream csvFile)
{
XDocument responseXml = new XDocument( new XDeclaration("1.0", "utf-8", "yes"));
try
{
if ( csvFile != null || csvFile.Length!=0)
{
responseXml.Add(new XElement("root"));
//using(CsvFileReader reader=new CsvFileReader(File.OpenRead(#"C:\Users\toshibapc\Documents\Visual Studio 2013\Projects\WCFLecturaCSV\WCFLecturaCSV\App_Data\archivo.csv"))){
using (CsvFileReader reader = new CsvFileReader(csvFile))
{
CsvRow row = new CsvRow();
List<String> headers = new List<string>();
while (reader.ReadRow(row))
{
int cont = 0;
XElement dato = new XElement("AccountInfos", new XElement("Info"));
XElement datos=null;
foreach (String s in row)
{
if(s.Equals("AccountIDToMove", StringComparison.OrdinalIgnoreCase)|| s.Contains("AccountNameToMove") || s.Contains("NewParentAccountID") || s.Contains("NewParentAccountName")){
headers.Add(s);
}
else{
if (s != String.Empty)
{
datos = new XElement(headers[cont], s); //.Append("<" + headers[cont] + ">" + s + "<" + headers[cont] + "/>");
dato.Add(datos);
}
}
cont++;
}
if (headers.Count == 4 && datos != null)
responseXml.Add(dato);
} // fin de while
}
} // Check if no file i sent or not info on file
}
catch (Exception ex) {
//oError = ex.Message;
}
return responseXml.ToString();
}
What i would like to acomplish by using this code is to get an xml like this
<xml version="1.0">
<root>
<AccountInfos>
<Info>
<AccountIDToMove>312456</AccountIDToMove>
<AccountNameToMove>Burger Count</AccountNameToMove>
<NewParentAccountID>453124</NewParentAccountID>
<NewParentAccountName> Testcom sales 1</NewParentAccountName>
</Info>
<Info>
<AccountIDToMove>874145</AccountIDToMove>
<AccountNameToMove>Mac Count</AccountNameToMove>
<NewParentAccountID>984145</NewParentAccountID>
<NewParentAccountName> Testcom sales 1</NewParentAccountName>
</Info>
</AccountInfos>
</root>
For any answer or help thank you so much
You are adding multiple roots to your document. You initially add one here:
responseXml.Add(new XElement("root"));
And later add more root elements in a loop here:
responseXml.Add(dato);
However, each XML document must have exactly one single root element. Thus you probably want to do:
responseXml.Root.Add(dato);

Read the data from XML file into flat files(txt) and with formated data

I have an XML file with nodes and data...I need to write that into a text file as normal data. The nodes being the headers of the data
that follow.
EG XML:
<Bank>
<accountholder>Georgina Wax</accountholder>
<accountnumber>408999703657</accountnumber>
<accounttype>cheque</accounttype>
<bankname>National Bank</bankname>
<branch>Africa</branch>
<amount>2750.00</amount>
<date>12/01/2012</date>
</Bank>
To txt file and formatted as :
accountholder accountnumber accounttype bankname
Georgina Wax 408999703657 cheque National Bank
I can't seem to have it to have spaces between the data and hearders.
Below is what I tried :
StreamWriter writer = File.CreateText(#"C:\\Test.txt");
XmlDocument doc = new XmlDocument();
doc.Load(#"C:\\bank.xml");
writer.WriteLine(string.Join("|",doc.SelectSingleNode("/debitorders/deduction").ChildNodes.C ast<XmlElement>().Select(e => doc.SelectSingleNode("/debitorders/deduction/bankname").ToString())));
foreach (XmlElement book in doc.SelectNodes("/debitorders/deduction"))
{
writer.WriteLine(book.ChildNodes.Cast<XmlElement>().Select(e => e.InnerText).ToArray());
}
Please help.
This will produce output like you want.
private static void LoadAndWriteXML()
{
string headerFiles = "";
string values = "";
using (XmlReader reader = XmlReader.Create(#"C:\\bank.xml"))
{
while (reader.Read())
{
if (reader.NodeType == XmlNodeType.Element && !reader.Name.Equals("Bank")) // we have to skip root node means bank node.
{
headerFiles += reader.Name + " ";
values += reader.ReadString() + " ";
}
}
}
StreamWriter writer = new StreamWriter(#"C:\\Test.txt");
writer.WriteLine(headerFiles.Trim());
writer.WriteLine(values.Trim());
writer.Close();
}
XDocument xdoc = new XDocument();
xdoc = XDocument.Load(fname);
xdoc.Save(fname1);
will save the file with the tags alignment formating

Returning elements of an XML file as a string

I'm attempting to display XML content tags from a word document that the user uploads, but I'm not sure how to explicitly pull out the data, and then display it as a string.
I think the below SHOULD find the correct descendent (or element, either should work right?) and then allow me to create a string out of it and display it, but I can't get the file to recognise xdoc). What I'm trying to return is located as "w.tag" in the bottom section of code.
Any ideas?
WordprocessingDocument _TempDoc = WordprocessingDocument.Open(Server.MapPath("~/") + filename, true);
//query to find particular descendants
var lv1s = from lv1 in xdoc.Descendants("table")
select new
{
Header = lv1.Attribute("name").Value,
Children = lv1.Descendants("tag")
};
//Loop through results
StringBuilder result = new StringBuilder();
foreach (var lv1 in lv1s)
{
result.AppendLine(lv1.Header);
foreach (var lv2 in lv1.Children)
result.AppendLine(" " + lv2.Attribute("name").Value);
}
//the label should contain the content controls of the document, using the class, XMLfromDocument
labelContentControls.Text = fileUpload_Displayx(XMLfromDocument.GetContentControls(_TempDoc));
public static XDocument GetXDocument(this OpenXmlPart part)
{
XDocument xdoc = part.Annotation<XDocument>();
if (xdoc != null)
return xdoc;
using (Stream str = part.GetStream())
using (StreamReader streamReader = new StreamReader(str))
using (XmlReader xr = XmlReader.Create(streamReader))
xdoc = XDocument.Load(xr);
part.AddAnnotation(xdoc);
return xdoc;
}
//following method gets the structure of the content controls / XML in the document
public static XElement GetContentControls( WordprocessingDocument document)
{
XElement contentControls = new XElement("ContentControls",
document
.MainDocumentPart
.GetXDocument()
.Root
.Element(W.body)
.Elements(W.sdt)
.Select(tableContentControl =>
new XElement("Table",
new XAttribute("Name", (string)tableContentControl
.Element(W.sdtPr).Element(W.tag).Attribute(
W.val)),
tableContentControl
.Descendants(W.sdt)
.Select(fieldContentControl =>
new XElement("Field",
new XAttribute("Name",
(string)fieldContentControl
.Element(W.sdtPr)
.Element(W.tag)
.Attribute(W.val)
)
)
)
)
)
);
// you cannot access the inner XML of the elemnt directly, you must concatenate to the child elements.
// string contentTitle = string.Concat(W.sdtPr);
//return W.tag;
return contentControls;

Updating data in XML (Linq To XML) generate wrongclosing tags

I have this XmlFile (tasks.xml):
<?xml version="1.0" encoding="utf-8"?>
<Tasks>
<Task>
<Id>dc77b03f-468c-4709-b8dc-4d9741c984dd</Id>
<Name>buy paper</Name>
<Category>inbox</Category>
<DueDate></DueDate>
<Project></Project>
<Context></Context>
<Note></Note>
<Created>03/16/2013 15:33:29</Created>
<Finished></Finished>
</Task>
</Tasks>
And when I update element like this everything works ok:
try
{
var item = (CheckBox)sender;
dynamic itemDC = item.DataContext;
var folder = ApplicationData.Current.LocalFolder;
var file = await folder.GetFileAsync("tasks.xml");
var readStream = await FileIO.ReadTextAsync(file);
using (var writeStream = await file.OpenStreamForWriteAsync())
{
XElement xElem = XElement.Parse(readStream);
IEnumerable<XElement> singleTask = from task in xElem.Elements("Task")
where (string)task.Element("Id") == itemDC.Id
select task;
foreach (XElement task in singleTask)
{
task.SetElementValue("Finished", DateTime.Now.ToString("MM") + "/" + DateTime.Now.ToString("dd") + "/" + DateTime.Now.ToString("yyyy HH:mm:ss"));
}
xElem.Save(writeStream);
}
}
catch (Exception ex)
{
new MessageDialog(ex.Message).ShowAsync();
}
but if I want to edit <Finnished> element again and set value to "" it will generate two wrong closing tags in my XML file.
Edit again code:
try
{
var item = (CheckBox)sender;
dynamic itemDC = item.DataContext;
var folder = ApplicationData.Current.LocalFolder;
var file = await folder.GetFileAsync("tasks.xml");
var readStream = await FileIO.ReadTextAsync(file);
using (var writeStream = await file.OpenStreamForWriteAsync())
{
XElement xElem = XElement.Parse(readStream);
IEnumerable<XElement> singleTask = from task in xElem.Elements("Task")
where (string)task.Element("Id") == itemDC.Id
select task;
foreach (XElement task in singleTask)
{
task.SetElementValue("Finished", "");
}
xElem.Save(writeStream);
}
}
catch (Exception ex)
{
new MessageDialog(ex.Message).ShowAsync();
}
Wrong generated XML after editing <Finished> to "":
<?xml version="1.0" encoding="utf-8"?>
<Tasks>
<Task>
<Id>dc77b03f-468c-4709-b8dc-4d9741c984dd</Id>
<Name>dnešní úkol</Name>
<Category>inbox</Category>
<DueDate></DueDate>
<Project></Project>
<Context></Context>
<Note></Note>
<Created>03/16/2013 15:33:29</Created>
<Finished></Finished>
</Task>
</Tasks> </Task>
</Tasks>
If I edit Again with string same size as date string, everything work OK, but clear "" value do that error.
I spend a lot of time to figure it out but now I don't have any idea how to solve this problem.
What I'm doing wrong?
Your problem is not related to Linq to XML in any way. It's just how streams work. In first case you're getting a longer file therefore all of the previous contents are overwritten. In second case the new file is shorter therefore the rest of the contents remain unchanged.
You should overwrite the existing file when you're saving the modified XML:
var folder = ApplicationData.Current.LocalFolder;
var file = await folder.GetFileAsync("tasks.xml");
var readStream = await FileIO.ReadTextAsync(file);
XElement xElem = XElement.Parse(readStream);
IEnumerable<XElement> singleTask = from task in xElem.Elements("Task")
where (string)task.Element("Id") == itemDC.Id
select task;
foreach (XElement task in singleTask)
{
task.SetElementValue("Finished", "");
}
file = await folder.CreateFileAsync("tasks.xml", CreationCollisionOption.ReplaceExisting);
using (var writeStream = await file.OpenStreamForWriteAsync())
{
xElem.Save(writeStream);
}
Also, when saving DateTime to XML you really should be serializing it with XmlConvert:
task.SetElementValue("Finished", XmlConvert.ToString(DateTime.Now));

Categories