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.
Related
A little background on problem:
We have an ASP.NET MVC5 Application where we use FlexMonster to show the data in grid. The data source is a stored procedure that brings all the data into the UI grid, and once user clicks on export button, it exports the report to Excel. However, in some cases export to excel is failing.
Some of the data has some invalid characters, and it is not possible/feasible to fix the source as suggested here
My approach so far:
EPPlus library fails on initializing the workbook as the input excel file contains some invalid XML characters. I could find that the file is dumped with some invalid character in it. I looked into the possible approaches .
Firstly, I identified the problematic character in the excel file. I first tried to replace the invalid character with blank space manually using Notepad++ and the EPPlus could successfully read the file.
Now using the approaches given in other SO thread here and here, I replaced all possible occurrences of invalid chars. I am using at the moment
XmlConvert.IsXmlChar
method to find out the problematic XML character and replacing with blank space.
I created a sample program where I am trying to work on the problematic excel sheet.
//in main method
String readFile = File.ReadAllText(filePath);
string content = RemoveInvalidXmlChars(readFile);
File.WriteAllText(filePath, content);
//removal of invalid characters
static string RemoveInvalidXmlChars(string inputText)
{
StringBuilder withoutInvalidXmlCharsBuilder = new StringBuilder();
int firstOccurenceOfRealData = inputText.IndexOf("<t>");
int lastOccurenceOfRealData = inputText.LastIndexOf("</t>");
if (firstOccurenceOfRealData < 0 ||
lastOccurenceOfRealData < 0 ||
firstOccurenceOfRealData > lastOccurenceOfRealData)
return inputText;
withoutInvalidXmlCharsBuilder.Append(inputText.Substring(0, firstOccurenceOfRealData));
int remaining = lastOccurenceOfRealData - firstOccurenceOfRealData;
string textToCheckFor = inputText.Substring(firstOccurenceOfRealData, remaining);
foreach (char c in textToCheckFor)
{
withoutInvalidXmlCharsBuilder.Append((XmlConvert.IsXmlChar(c)) ? c : ' ');
}
withoutInvalidXmlCharsBuilder.Append(inputText.Substring(lastOccurenceOfRealData));
return withoutInvalidXmlCharsBuilder.ToString();
}
If I replaces the problematic character manually using notepad++, then the file opens fine in MSExcel. The above mentioned code successfully replaces the same invalid character and writes the content back to the file. However, when I try to open the excel file using MS Excel, it throws an error saying that file may have been corrupted and no content is displayed (snapshots below). Moreover, Following code
var excelPackage = new ExcelPackage(new FileInfo(filePath));
on the file that I updated via Notepad++, throws following exception
"CRC error: the file being extracted appears to be corrupted. Expected 0x7478AABE, Actual 0xE9191E00"}
My Questions:
Is my approach to modify content this way correct?
If yes, How can I write updated string to an Excel file?
If my approach is wrong then, How can I proceed to get rid of invalid XML chars?
Errors shown on opening file (without invalid XML char):
First Pop up
When I click on yes
Thanks in advance !
It does sounds like a binary (presumable XLSX) file based on your last comment. To confirm, open the file created by the FlexMonster with 7zip. If it opens properly and you see a bunch of XML files in folders, its a XLSX.
In that case, a search/replace on a binary file sounds like a very bad idea. It might work on the XML parts but might also replace legit chars in other parts. I think the better approach would be to do as #PanagiotisKanavos suggests and use ZipArchive. But you have to do rebuild it in the right order otherwise Excel complains. Similar to how it was done here https://stackoverflow.com/a/33312038/1324284, you could do something like this:
public static void ReplaceXmlString(this ZipArchive xlsxZip, FileInfo outFile, string oldString, string newstring)
{
using (var outStream = outFile.Open(FileMode.Create, FileAccess.ReadWrite))
using (var copiedzip = new ZipArchive(outStream, ZipArchiveMode.Update))
{
//Go though each file in the zip one by one and copy over to the new file - entries need to be in order
foreach (var entry in xlsxZip.Entries)
{
var newentry = copiedzip.CreateEntry(entry.FullName);
var newstream = newentry.Open();
var orgstream = entry.Open();
//Copy non-xml files over
if (!entry.Name.EndsWith(".xml"))
{
orgstream.CopyTo(newstream);
}
else
{
//Load the xml document to manipulate
var xdoc = new XmlDocument();
xdoc.Load(orgstream);
var xml = xdoc.OuterXml.Replace(oldString, newstring);
xdoc = new XmlDocument();
xdoc.LoadXml(xml);
xdoc.Save(newstream);
}
orgstream.Close();
newstream.Flush();
newstream.Close();
}
}
}
When it is used like this:
[TestMethod]
public void ReplaceXmlTest()
{
var datatable = new DataTable("tblData");
datatable.Columns.AddRange(new[]
{
new DataColumn("Col1", typeof (int)),
new DataColumn("Col2", typeof (int)),
new DataColumn("Col3", typeof (string))
});
for (var i = 0; i < 10; i++)
{
var row = datatable.NewRow();
row[0] = i;
row[1] = i * 10;
row[2] = i % 2 == 0 ? "ABCD" : "AXCD";
datatable.Rows.Add(row);
}
using (var pck = new ExcelPackage())
{
var workbook = pck.Workbook;
var worksheet = workbook.Worksheets.Add("source");
worksheet.Cells.LoadFromDataTable(datatable, true);
worksheet.Tables.Add(worksheet.Cells["A1:C11"], "Table1");
//Now similulate the copy/open of the excel file into a zip archive
using (var orginalzip = new ZipArchive(new MemoryStream(pck.GetAsByteArray()), ZipArchiveMode.Read))
{
var fi = new FileInfo(#"c:\temp\ReplaceXmlTest.xlsx");
if (fi.Exists)
fi.Delete();
orginalzip.ReplaceXmlString(fi, "AXCD", "REPLACED!!");
}
}
}
Gives this:
Just keep in mind that this is completely brute force. Anything you can do to make the file filter smarter rather then simply doing ALL xml files would be a very good thing. Maybe limit it to the SharedString.xml file if that is where the problem lies or in the xml files in the worksheet folders. Hard to say without knowing more about the data.
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.
I have a very large xml-file (let's say it has about 300000 elements). In my part of the application I only have to know if the name of the root-element is ApplicationLog and if there is an attribute called LogId in the root-element.
To read the XML I use:
XDocument document;
using (StreamReader streamReader = new StreamReader(filePath, true))
{
document = XDocument.Load(streamReader);
}
and to get the information I need:
try
{
if (document.Root != null)
{
if (string.Equals(document.Root.Name.LocalName, "ApplicationLog", StringComparison.InvariantCultureIgnoreCase) &&
document.Root.HasAttributes && (from o in document.Root.Attributes() where string.Equals(o.Name.LocalName, "LogId", StringComparison.InvariantCultureIgnoreCase) select o).Any())
{
isRelevantFile = true;
}
}
}
catch (Exception e)
{
}
This just works fine.
The problem is that the XDocument.Load takes about 15 seconds to load a XML-File which is about 20MB.
I also tried it with the XmlDocument, but there I have the same problem.
My first idea for a solution was to read the file as text and parse the first lines for the searched element/attribute. But this seems to be not so professional to me.
Does anybody know a better way to achieve this?
Use the XmlReader API with
using (XmlReader xr = XmlReader.Create(filePath))
{
xr.MoveToContent();
if (xr.LocalName == "ApplicationLog" ...)
}
You can try the solution provided here or use/develop a SAX reader such as this one. You can find more information on SAX here.
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 a list of songs which I want to output to an external XML file using a smart device (pocket pc).
String path = GetAppDir();
string filePath = path + #"\output\songs.xml";
XmlWriter xmlOut = XmlWriter.Create(filePath, settings);
xmlOut.WriteStartDocument();
xmlOut.WriteStartElement("Songs");
foreach (Song songTmp in finalbasket)
{
xmlOut.WriteStartElement("Songs");
xmlOut.WriteAttributeString("Name", songTmp.SongName);
xmlOut.WriteElementString("Artist", songTmp.SongArtist);
xmlOut.WriteElementString("Price", Convert.ToString(songTmp.SongPrice));
xmlOut.WriteEndElement();
}
xmlOut.WriteEndElement();
xmlOut.Close();
The application seems to write the xml document but it always comes up empty. There are indeed items in the 'finalbasket' list. Any ideas what I am doing wrong?
I think you need an
xmlOut.WriteEndDocument();
right before the xmlOut.Close(). Also, I'm not sure if this is part of your problem, but this line:
xmlOut.WriteStartElement("Songs");
should probably be this:
xmlOut.WriteStartElement("Song");