I am trying to save some changes back to an XML file using Linq and I have seen many examples on SO and tutorial sites doing it like this but for some reason I am getting an error on the xmlDoc.Save(customerXMLPath); line Argument 1: cannot convert from 'string' to 'System.IO.Stream'.
Most of the samples I have seen are ASP.Net samples but I wouldn't think that should make much a difference on the syntax (this is a UWP app). Can someone please tell me what I am doing wrong?
private void SaveChange_Click(object sender, RoutedEventArgs e)
{
string customerXMLPath =
Path.Combine(Package.Current.InstalledLocation.Path, "XML/Customers.xml");
XDocument xmlDoc = XDocument.Load(customerXMLPath);
var updateQuery = from r in xmlDoc.Descendants("Customer")
where r.Element("CustomerId").Value == txtCustomerId.Text
select r;
foreach (var query in updateQuery)
{
query.Element("State").SetValue(txtState.Text);
}
xmlDoc.Save(customerXMLPath);
}
Edit: according to the comments there is no overload for Save in UWP. So does that mean (based on another comment) that I have to save as a stream? If that is the case wouldn't I have to overwrite the file? <-- that doesn't make sense when I am just trying to change a few values but maybe I misunderstood the answer.
My assumption is that there is a way to update a XML file in UWP so am I just going about this all wrong? What is the recommended way? By the way SQLite is not an option right now because the files have to remain in XML format
Instead of using your own "slash" mark in the Path.Combine statement, let it do that for you. So you know your path is acceptable to the operating system you are using.
Switch this:
string customerXMLPath =
Path.Combine(Package.Current.InstalledLocation.Path, "XML/Customers.xml");
To this:
string customerXMLPath =
Path.Combine(Package.Current.InstalledLocation.Path, "XML", "Customers.xml");
But that isn't necessarily your problem. It appears you are trying to write to a READ ONLY location. See this post for more. I'll quote the text portion of the answer here.
...the problem is we cannot update file content of the installation folder, this folder a read-only one, so the workaround is to place your xml file in the folder which we have both read and write rights...
I think I might have found a solution for this, but it involves overriding files. It might be dirty, but it works for me.
I created a xml file in the ApplicationData.Current.LocalFolder.Path location. Working in the install directory (Package.Current.InstalledLocation.Path) can be difficult since all the files in there are sand-boxed, you will not have permissions to modify them.
So the first section will look something like this.
string customerXMLPath =
Path.Combine(ApplicationData.Current.LocalFolder.Path, "XML/Customers.xml");
After that you can run your XDocument code. But don’t bother calling xmlDoc.Save(customerXMLPath) because that won’t work in a UWP app.
So the second section will look something like this. Like you have already.
XDocument xmlDoc = XDocument.Load(customerXMLPath);
var updateQuery = from r in xmlDoc.Descendants("Customer")
where r.Element("CustomerId").Value == txtCustomerId.Text
select r;
foreach (var query in updateQuery)
{
query.Element("State").SetValue(txtState.Text);
}
The last part is where you would have to save the contents of your XDocument to the file, overriding any content already in the file. Since this is an await operation, it would be advised that you make your method async as well. StorageFolder and StorageFileshould be used to get the location and file recreation.
So the last part would look something like this.
StorageFolder storageFolder = ApplicationData.Current.LocalFolder;
var newfile = await storageFolder.CreateFileAsync("Customers.xml",
CreationCollisionOption.ReplaceExisting);
await FileIO.WriteTextAsync(newfile, xmlDoc.ToString());
So the full example will look like this.
using System.Xml.Linq;
using Windows.Storage;
using System.IO;
using System.Linq;
private void cmdStart_Click(object sender, RoutedEventArgs e)
{
SaveXml();
}
private async void SaveXml()
{
string XML_DATA_FILE = "Customer.xml";
string customerXMLPath = Path.Combine(ApplicationData.Current.LocalFolder.Path, XML_DATA_FILE);
if (File.Exists(xmlPath))
{
XDocument xmlDoc = XDocument.Load(customerXMLPath);
var updateQuery = from r in xmlDoc.Descendants("Customer")
where r.Element("CustomerId").Value == txtCustomerId.Text
select r;
foreach (var query in updateQuery)
{
query.Element("State").SetValue(txtState.Text);
}
StorageFolder storageFolder = ApplicationData.Current.LocalFolder;
var newfile = await storageFolder.CreateFileAsync(XML_DATA_FILE,
CreationCollisionOption.ReplaceExisting);
await FileIO.WriteTextAsync(newfile, xmlDoc.ToString());
}
}
I hope this helps and gives some guidance.
Cheers.
Related
I've got a file Save and file open picker that im now trying to integrate the ability to save the Path and FileName as public variable that will be used across the whole project through different methods etc.
I've currently got a SaveFileClass and OpenFileClass.
I've seen examples of using the OpenFileDialog to return the save directory although I don't believe these are suitable for what im after. Maybe in some shape or form but dont seem to make much sense for the FileOpenPicker and FileSavePicker I have in use currently.
What I have currently (minus the returning directories) is this:
public async Task<IStorageFile> OpenFileAsync()
{
FileOpenPicker openPicker = new FileOpenPicker
{
ViewMode = PickerViewMode.List,
SuggestedStartLocation = PickerLocationId.DocumentsLibrary
};
openPicker.FileTypeFilter.Add(".txt");
openPicker.FileTypeFilter.Add(".csv");
return await openPicker.PickSingleFileAsync();
}
This passes back to the MainPage.
Within here, i would like to have a variable to store the selected file path and the selected file name as a string. These will then be used around the project when it comes to quick saving/auto saving and when building my class to load files.
Im just after whether or not FilePicker has this functionality because my understanding of the documentation is a little limited when trying to integrate it with my scenario.
Your OpenFileAsync method returns a selected IStorageFile and this one has a Name property that gets you the name of the file including the file name extension and a Path property that gets you the full file-system path of the file. You can do whatever you want with these values:
private async void OpenFile_Click(object sender, RoutedEventArgs e)
{
OpenFileClass instance = new OpenFileClass();
IStorageFile file = await instance.OpenFileAsync();
if (file != null)
{
string fileName = file.Name;
string filePath = file.Path;
}
}
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 application, that reads a specific type of XML file. Those XML files can reference each other, e.g.:
<MyXml>
<Reference Path="pack://application:,,,/MyOtherXML.xml"/>
<!--More data-->
</MyXml>
This is mainly because they are quite long, and you don't want to repeat yourself with 180+ lines of XML.
However, I'm not sure how to check if the files exist if they are resources. I know that if they are normal files I can just use File.Exists, but I don't think you can do that for resources. I also found this, but the answer seems to be wrong. So how do you check if a resource exists on WPF?
You need to use GetManifestResourceStream to get resources and read collection of keys from the dictionary something like this -
public static string[] GetResourceNames()
{
var assembly = Assembly.GetExecutingAssembly();
string resName = assembly.GetName().Name + ".g.resources";
using (var stream = assembly.GetManifestResourceStream(resName))
{
using (var reader = new System.Resources.ResourceReader(stream))
{
return reader.Cast<DictionaryEntry>().Select(entry =>
(string)entry.Key).ToArray();
}
}
}
you can call Assembly.GetExecutingAssembly().GetManifestResourceNames() get all the resource names and check on the results for the resource you want
var names = Assembly.GetExecutingAssembly().GetManifestResourceNames();
if(names.Contains(resourceNameTosearch))
{
// exist
}
I m trying to edit a xml file. After update my value, i would like to save it in same original xml file. When i attend to save into this file, i have an error "could not save into this file because it still open". Need some idea | help.
Thanx :)
public void writeConfig(string withConfig, string param)
{
XmlTextReader reader = new XmlTextReader(pathFile);
XElement xmlFile = XElement.Load(reader);
reader.Close();
var query = from c in xmlFile.Elements("config").Attributes(withConfig) select c;
foreach (XAttribute config in query)
{
config.Value = param;
}
xmlFile.Save(pathFile);
}
It worked fine for me, even when I had the file open in TextPad.
Did you try looking into the current processes to see if any other program is holding that up? You can try using Process Explorer to look for such processes.
i just cant find a clean explanation on how to read a text file, line by line in Windows RT ( for Windows 8 Store).
Lets say i have a folder: MyFolder/Notes.txt
And i want to read the data from Notes.txt and add it to an array of strings.
How do i read/write from that file? I was using StreamReader before, but now it is very confusing. And the dev samples dont help that much.
Ive managed to find the answer myself. Thanks for the help.
// READ FILE
public async void ReadFile()
{
// settings
var path = #"MyFolder\MyFile.txt";
var folder = Windows.ApplicationModel.Package.Current.InstalledLocation;
// acquire file
var file = await folder.GetFileAsync(path);
var readFile = await Windows.Storage.FileIO.ReadLinesAsync(file);
foreach (var line in readFile)
{
Debug.WriteLine("" + line.Split(';')[0]);
}
}
MyFile.txt has:
Test1;Description1;
Test2;Description2;
//Output for ReadFile()
Test1
Test2
You don't have to use File.ReadLines. You try an implementation like this if you prefer:
using (StreamReader reader = new StreamReader("notes.txt"))
{
while (reader.Peek() >= 0)
{
Console.WriteLine(reader.ReadLine());
}
}
foreach (var line in File.ReadLines("MyFolder/Notes.txt"))
{
...
}
reads the file line by line. This is different from File.ReadAllLines which reads the entire file at once.
If you want to read everything at once, into an array, use the latter.