I am working on a class that creates or opens an xml file:
namespace QTabs
{
public class DataLayer
{
XmlDocument XMLDocObject = new XmlDocument();
StorageFolder storageFolder = ApplicationData.Current.LocalFolder;
StorageFile storageFile;
string xmlFile = "DataStore.xml";
public DataLayer()
{
loadXML();
}
public async void loadXML()
{
//if XML doc doesnt exist create it
try
{
storageFile = await ApplicationData.Current.LocalFolder.GetFileAsync(xmlFile);
}
catch (FileNotFoundException)
{
storageFile = null;
}
if (storageFile == null)
{
storageFile = await storageFolder.CreateFileAsync(xmlFile);
// Manipulate the xml document.
var root = XMLDocObject.CreateElement("SyncTimes");
XMLDocObject.AppendChild(root);
var childTag1 = XMLDocObject.CreateElement("id");
var childTag2 = XMLDocObject.CreateElement("dateTime");
root.AppendChild(childTag1);
root.AppendChild(childTag2);
await XMLDocObject.SaveToFileAsync(storageFile);
}
else
{
storageFile = await storageFolder.GetFileAsync(xmlFile);
}
}
public async void addSyncTime()
{
XmlElement x1 = XMLDocObject.CreateElement("SyncTime");
XmlElement x11 = XMLDocObject.CreateElement("id");
x11.InnerText = "1";
x1.AppendChild(x11);
XmlElement x12 = XMLDocObject.CreateElement("dateTime");
x12.InnerText = DateTime.Now.ToString();
x1.AppendChild(x12);
await XMLDocObject.SaveToFileAsync(storageFile);
}
}
}
My issue is with the above addSyncTime() method. When it runs it results in the XML file being empty, including removing the elements added on creation.
What am I doing wrong?
Hard to say without a good, minimal, complete code example that reliably reproduces the problem. However, your code appears to have two different problems:
When the XML file is already present, you don't ever bother to actually load it, leaving XMLDocObject referencing an empty document. (Indeed, the storageFile != null case seems completely out of place; not only does it not load the XML from storageFile as I'd expect, it actually re-retrieves the storageFile object by calling GetFileAsync() again).
Your addSyncTime() method creates new elements, but never adds them to the XML document tree.
Given the above, I would expect the program behavior to be:
The first time you run the program, the XML document is created.
Any subsequent time you run the program (unless you delete the XML document), attempts to update the XML document will empty the document.
In neither case would I expect new SyncTime elements to be added. The first time you run the program, you'll get the root SyncTimes element and its initial id and dateTime children, but not any SyncTime elements. After that, the XML document will be emptied, and you'll still not get any new SyncTime elements.
If the above does not address your concern, please provide a good code example as described in the link I provide above.
Related
I need to save and load a listview / observablecollection when I open and close the app.
I have search around here and other places, and tried some things, but nothing seems to work.
I've pasted the code I found, that I thought did the trick. maybe it does with some changes.
//This one is for adding items, and it works fine.
try
{
Tanknings.Add(new Tankning { Date = Dato.Date.ToString("dd-MM-yyyy"),
KmTaeller = KmTaeller.Text,
LiterTanket = Math.Round(Convert.ToDouble(LiterTanket.Text), 2).ToString(),
Pris = Math.Round(Convert.ToDouble(Pris.Text), 2).ToString(),
KmKoert = (Convert.ToInt32(KmTaeller.Text) - Convert.ToInt32(AktuelKmTaeller.Text)).ToString(),
PrisPrLiter = Math.Round((Convert.ToDouble(Pris.Text) / Convert.ToDouble(LiterTanket.Text)), 2).ToString(),
KmPrLiter = Math.Round(((Convert.ToDouble(KmTaeller.Text) - (Convert.ToDouble(AktuelKmTaeller.Text))) / Convert.ToDouble(LiterTanket.Text)), 2).ToString() });
}
catch
{
}
//This i what i tried to save and load the items.
private async void saveTankninger()
{
XmlSerializer xs = new XmlSerializer(typeof(ObservableCollection<Tankning>));
using (StreamWriter wr = new StreamWriter("Files/Tankninger.xml"))
{
xs.Serialize(wr, Tanknings);
}
/* Firstly we will use StorageFolder class from the Windows.Storage namespace
to get path to the LocalFolder for our application: */
StorageFolder storageFolder = ApplicationData.Current.LocalFolder;
/* Then we need to have reference to the file where we can store notes:
Note that if file exists we do not want to create another one: */
StorageFile notesFile = await storageFolder.CreateFileAsync("Tankninger.txt", CreationCollisionOption.OpenIfExists);
// Now we want to serialize list with the notes to save it in the JSON format ine the file:
var serializedNotesList = JsonConvert.SerializeObject(Tanknings);
// Last step is to write serialized list with notes to the text file:
await FileIO.WriteTextAsync(notesFile, serializedNotesList);
}
private async void loadTankninger()
{
/* Firstly we will use StorageFolder class from the Windows.Storage namespace
to get path to the LocalFolder for our application: */
StorageFolder storageFolder = ApplicationData.Current.LocalFolder;
/* Then we need to have reference to the file where we can store notes:
Note that if file exists we do not want to create another one: */
StorageFile notesFile = await storageFolder.CreateFileAsync("Tankninger.txt", CreationCollisionOption.OpenIfExists);
// Read serialized notes list from the file:
string serializedNotesList = await FileIO.ReadTextAsync(notesFile);
// Deserialize JSON list to the ObservableCollection:
if (serializedNotesList != null)
{
Tanknings = JsonConvert.DeserializeObject<ObservableCollection<Tankning>>(serializedNotesList);
tankningTable.ItemsSource = Tanknings;
}
}
Assuming that you are using newtonsoft JSON library and have populated List
Serialise :
using Newtonsoft.Json;
List<Class_name> aList = getListFromSomehwere();
string json = JsonConvert.SerializeObject(aList);
// Do whatever you want with string, e.g. save to file
Deserialise :
using Newtonsoft.Json;
string json = ReadFile(); // Your function to read all text from file
List<Class_name> aList = (JsonConvert.DeserializeObject<IEnumerable<Class_name>>(json)).ToList();
// Do whatever you want with this list
To save on close - just add event to your application closing event, serialise list and save it to file.
To open on load - add window_loaded event (or form shown or whatever you prefer), read all file, deserialise json. There's many ways to approach this and it is up to you to decide what you want to do.
Edit : JSON.NET DeserializeObject to List of Objects might help
I am trying to figure out how to read and write files in a UWA application. I understand that I need to open a FileStreamm, but I can't figure out how to do that.
I started with this code:
FileStream fs = new FileStream(#"C:\XML\test.txt", FileMode.Create, FileAccess.Write);
seems to work, no red lines.
At the end of all of that I am told to put in Flush and Close, like this:
FileStream fs = new FileStream(#"C:\XML\test.txt", FileMode.Create,
...
fs.Flush();
fs.Close();
Now, this is where I hit a snag, because fs.Close(); is not even on the list of functions on fs. I just get a red line in my IDE if I try to hardcode it.
Can someone please take the time to help me understand how to do this with UWA? For some reason it seems like there is a different approach in Windows 10 apps, and I have a VERY hard time finding anything that shows me how to do it right. All the tutorials and SOF forum input are about older versions (non-UWA).
When I do this in a console application it all works as expected.
My end goal is to be able to read and write to an XML file in this kind of fashion:
XDocument doc = XDocument.Load(input);
XElement person = doc.Element("Person");
person.Add(new XElement("Employee",
new XElement("Name", "David"),
new XElement("Dept", "Chef")));
doc.Save(output);
I'm going down this path because an answer to my previous question told me to use a FileStream, but I simply cannot make that work in UWA.
You cannot just access any file from a Universal Windows App. Access to the file system is restricted.
See the documentation for details.
To help you further we need to know more about your application. What kind of files do you want to access for what reason?
Example on how to read an Xml File, modify it and store it in an Universal app. You need a button with the following Click handler and a TextBox named "TextBoxLog".
private async void ButtonDemo_Click(object sender, RoutedEventArgs e)
{
// Get our local storage folder
var localFolder = ApplicationData.Current.LocalFolder;
XmlDocument xmlDocument;
// Try to get file
var file = await localFolder.TryGetItemAsync("MyData.xml") as IStorageFile;
if(file != null)
{
// File exists -> Load into XML document
xmlDocument = await XmlDocument.LoadFromFileAsync(file);
}
else
{
// File does not exist, create new document in memory
xmlDocument = new XmlDocument();
xmlDocument.LoadXml(#"<?xml version=""1.0"" encoding=""UTF-8""?>" + Environment.NewLine + "<root></root>");
}
// Now show the current contents
TextBoxLog.Text = "";
var lEntries = xmlDocument.GetElementsByTagName("Entry");
foreach(var lEntry in lEntries)
{
TextBoxLog.Text += lEntry.InnerText + Environment.NewLine;
}
// Now add a new entry
var node = xmlDocument.CreateElement("Entry");
node.InnerText = DateTime.Now.ToString();
xmlDocument.DocumentElement.AppendChild(node);
// If the file does not exist yet, create it
if(file == null)
{
file = await localFolder.CreateFileAsync("MyData.xml");
}
// Now save the document
await xmlDocument.SaveToFileAsync(file);
}
Okay, the (simple) solution is to put the xml-file in the PROJECTFOLDER/bin/x86/debug/appX and then write the data to a list this way:
public class dataRaw
{
public string data { get; set; }
public string firstName { get; set; }
public string lastName { get; set; }
}
//You can call this class with x = collectionGenerator.getList() (it returns a list<T>)
public class collectionGenerator
{
public static List<dataRaw> getList()
{
//This is the xml file in the folder
var doc = XDocument.Load("Data.xml");
//This parse the XML and adds in to the list "dataList"
var dataList = doc.Root
.Descendants("Person")
.Select(node => new dataRaw
{
//data, firstName and lastName are in app variables from dataRaw put into listData.
//Number, FirstName and LastName are the nodes in the XML file.
data = node.Element("Number").Value,
firstName = node.Element("FirstName").Value,
lastName = node.Element("LastName").Value,
})
.ToList();
return dataList;
}
}
I am working on a project that requires all SQL connection and query information to be stored in XML files. To make my project configurable, I am trying to create a means to let the user configure his sql connection string information (datasource, catalog, username and password) via a series of text boxes. This input will then be saved to the appropriate node within the SQL document.
I can get the current information from the XML file, and display that information within text boxes for the user's review and correction, but I'm encountering an error when it comes time to save the changes.
Here is the code I'm using to update and save the xml document.
protected void submitBtn_Click(object sender, EventArgs e)
{
SPFile file = methods.web.GetFile("MyXMLFile.xml");
myDoc = new XmlDocument();
byte[] bites = file.OpenBinary();
Stream strm1 = new MemoryStream(bites);
myDoc.Load(strm1);
XmlNode node;
node = myDoc.DocumentElement;
foreach (XmlNode node1 in node.ChildNodes)
{
foreach (XmlNode node2 in node1.ChildNodes)
{
if (node2.Name == "name1")
{
if (node2.InnerText != box1.Text)
{
}
}
if (node2.Name == "name2")
{
if (node2.InnerText != box2.Text)
{
}
}
if (node2.Name == "name3")
{
if (node2.InnerText != box3.Text)
{
node2.InnerText = box3.Text;
}
}
if (node2.Name == "name4")
{
if (node2.InnerText != box4.Text)
{
}
}
}
}
myDoc.Save(strm1);
}
Most of the conditionals are empty at this point because I'm still testing.
The code works great until the last line, as I said. At that point, I get the error "Memory Stream is not expandable." I understand that using a memory stream to update a stored file is incorrect, but I can't figure out the right way to do this.
I've tried to implement the solution given in the similar question at Memory stream is not expandable but that situation is different from mine and so the implementation makes no sense to me. Any clarification would be greatly appreciated.
Using the MemoryStream constructor that takes a byte array as an argument creates a non-resizable instance of a MemoryStream. Since you are making changes to the file (and therefore the underlying bytes), you need a resizable MemoryStream. This can be accomplished by using the parameterless constructor of the MemoryStream class and writing the byte array into the MemoryStream.
Try this:
SPFile file = methods.web.GetFile("MyXMLFile.xml");
myDoc = new XmlDocument();
byte[] bites = file.OpenBinary();
using(MemoryStream strm1 = new MemoryStream()){
strm1.Write(bites, 0, (int)bites.Length);
strm1.Position = 0;
myDoc.Load(strm1);
// all of your edits to the file here
strm1.Position = 0;
// save the file back to disk
using(var fs = new FileStream("FILEPATH",FileMode.Create,FileAccess.ReadWrite)){
myDoc.Save(fs);
}
}
To get the FILEPATH for a Sharepoint file, it'd be something along these lines (I don't have a Sharepoint development environment set up right now):
SPFile file = methods.web.GetFile("MyXMLFile.xml")
var filepath = file.ParentFolder.ServerRelativeUrl + "\\" + file.Name;
Or it might be easier to just use the SaveBinary method of the SPFile class like this:
// same code from above
// all of your edits to the file here
strm1.Position = 0;
// don't use a FileStream, just SaveBinary
file.SaveBinary(strm1);
I didn't test this code, but I've used it in Sharepoint solutions to modify XML (mainly OpenXML) documents in Sharepoint lists. Read this blogpost for more information
You could look into using the XDocument class instead of XmlDocument class.
http://msdn.microsoft.com/en-us/library/system.xml.linq.xdocument.aspx
I prefer it because of the simplicity and it eliminates having to use Memory Stream.
Edit: You can append to the file like this:
XDocument doc = XDocument.Load('filePath');
doc.Root.Add(
new XElement("An Element Name",
new XAttribute("An Attribute", "Some Value"),
new XElement("Nested Element", "Inner Text"))
);
doc.Save(filePath);
Or you can search for an element and update like this:
doc.Root.Elements("The element").First(m =>
m.Attribute("An Attribute").Value == "Some value to match").SetElementValue(
"The element to change", "Value to set element to");
doc.Save('filePath');
I have an app for Windows Store and what i am trying to do is read text from a file. I have two textFields. The descriptionTextField accepts new lines.
// Read from file
public async Task ReadFile()
{
try
{
// get the file
StorageFile notesStorageFile = await localFolder.GetFileAsync("NotesData.txt");
var readThis = await FileIO.ReadLinesAsync(notesStorageFile);
foreach (var line in readThis)
{
notesRepository.Add(new Note(line.Split(';')[0], line.Split(';')[1]));
}
Debug.WriteLine("File read successfully.");
}
catch (FileNotFoundException ex)
{
Debug.WriteLine("Error1: " + ex);
}
}
Now if NotesData.txt has:
Eggs;description eggs;
it works file.
But if NotesData.txt has:
Groceries;buy 10 eggs
buy 1 kg meat;
I get the index out of bound error. I just cant figure out how to fix the ReadFile() code.
The exception appears when i am calling the method. The problem i believe is with the descriptionTextBox that can accept new lines.
NotesData.txt
Apples;description apples; // works ok
Pears; description line 1
description line 2
description line 3; // problem
Pears; description line 1; // works ok
It seems to me you're trying to read back contents of a file you have previously saved and the problems you're having are just a consequence of the format you have selected for saving the data in the first place. Looking at it, new lines are not the only difficulty you're going to be having. What if the user decides to enter a semicolon in one of the textboxes? Are you preventing that?
I suggest you abandon your own serialization format and rather use one of the existing ones. If your notesRespository is a List<Note> this could be your (de)serialization code for XML:
private async Task Save(List<Note> notesRepository)
{
var xmlSerializer = new XmlSerializer(typeof (List<Note>));
using (var stream = await ApplicationData.Current.LocalFolder.OpenStreamForWriteAsync("notes.xml", CreationCollisionOption.ReplaceExisting))
{
xmlSerializer.Serialize(stream, notesRepository);
}
}
private async Task<List<Note>> Load()
{
var xmlSerializer = new XmlSerializer(typeof(List<Note>));
using (var stream = await ApplicationData.Current.LocalFolder.OpenStreamForReadAsync("notes.xml"))
{
return (List<Note>) xmlSerializer.Deserialize(stream);
}
}
And this for JSON:
private async Task Save(List<Note> notesRepository)
{
var jsonSerializer = new DataContractJsonSerializer(typeof (List<Note>));
using (var stream = await ApplicationData.Current.LocalFolder.OpenStreamForWriteAsync("notes.json", CreationCollisionOption.ReplaceExisting))
{
jsonSerializer.WriteObject(stream, notesRepository);
}
}
private async Task<List<Note>> Load()
{
var jsonSerializer = new DataContractJsonSerializer(typeof(List<Note>));
using (var stream = await ApplicationData.Current.LocalFolder.OpenStreamForReadAsync("notes.json"))
{
return (List<Note>)jsonSerializer.ReadObject(stream);
}
}
When the repository gets too large to always load and save it as a whole you could even consider a structured storage like SQLite.
This line:
notesRepository.Add(new Note(line.Split(';')[0], line.Split(';')[1]));
assumes that you'll always have at least one semi-colon in a line. If you've got a line in your file which doesn't have that (e.g. a blank line) then it will fail.
It's not clear where that's where your problem is, because you haven't said where the exception's coming from, but that would be my first guess.
I'd also only do the split once:
string[] bits = line.Split(';');
if (bits.Length >= 2)
{
// What do you want to do with lines with more than one semi-colon?
notesRepository.Add(bits[0], bits[1]);
}
else
{
// Handle lines without a semi-colon at all.
}
How do i know if my XML file has data besides the name space info:
Some of the files contain this:
<?xml version="1.0" encoding="UTF-8"?>
And if i encounter such a file, i want to place the file in an error directory
You could use the XmlReader to avoid the overhead of XmlDocument. In your case, you will receive an exception because the root element is missing.
string xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>";
using (StringReader strReader = new StringReader(xml))
{
//You can replace the StringReader object with the path of your xml file.
//In that case, do not forget to remove the "using" lines above.
using (XmlReader reader = XmlReader.Create(strReader))
{
try
{
while (reader.Read())
{
}
}
catch (XmlException ex)
{
//Catch xml exception
//in your case: root element is missing
}
}
}
You can add a condition in the while(reader.Read()) loop after you checked the first nodes to avoid to read the entire xml file since you just want to check if the root element is missing.
I think the only way is to catch an exception when you try and load it, like this:
try
{
System.Xml.XmlDocument doc = new System.Xml.XmlDocument();
doc.Load(Server.MapPath("XMLFile.xml"));
}
catch (System.Xml.XmlException xmlEx)
{
if (xmlEx.Message.Contains("Root element is missing"))
{
// Xml file is empty
}
}
Yes, there is some overhead, but you should be performing sanity checks like this anyway. You should never trust input and the only way to reliably verify it is XML is to treat it like XML and see what .NET says about it!
XmlDocument xDoc = new XmlDocument();
if (xDoc.ChildNodes.Count == 0)
{ // xml document is empty }
if (xDoc.ChildNodes.Count == 1)
{ // in xml document is only declaration node. (if you are shure that declaration is allways at the begining }
if (xDoc.ChildNodes.Count > 1)
{ // there is declaration + n nodes (usually this count is 2; declaration + root node) }
Haven't tried this...but should work.
try
{
XmlDocument doc = new XmlDocument();
doc.Load("test.xml");
}
catch (XmlException exc)
{
//invalid file
}
EDIT: Based on feedback comments
For large XML documents see Thomas's answer. This approach can have performance issues.
But, if it is a valid xml and the program wants to process it then this approach seems better.
If you aren't worried about validity, just check to see if there is anything after the first ?>. I'm not entirely sure of the C# syntax (it's been too long since I used it), but read the file, look for the first instance of ?>, and see if there is anything after that index.
However, if you want to use the XML later or you want to process the XML later, you should consider PK's answer and load the XML into an XmlDocument object. But if you have large XML documents that you don't need to process, then a solution more like mine, reading the file as text, might have less overhead.
You could check if the xml document has a node (the root node) and check it that node has inner text or other children.
As long as you aren't concerned with the validity of the XML document, and only want to ensure that it has a tag other than the declaration, you could use simple text processing:
var regEx = new RegEx("<[A-Za-z]");
bool foundTags = false;
string curLine = "";
using (var reader = new StreamReader(fileName)) {
while (!reader.EndOfStream) {
curLine = reader.ReadLine();
if (regEx.Match(curLine)) {
foundTags = true;
break;
}
}
}
if (!foundTags) {
// file is bad, copy.
}
Keep in mind that there's a million other reasons that the file may be invalid, and the code above would validate a file consisting only of "<a". If your intent is to validate that the XML document is capable of being read, you should use the XmlDocument approach.