XDocument.Save() unable to access file - c#

Hoping I might be able to get some help with an infuriating problem which I can't seem to find a definitive answer for anywhere.
I am appending data to an xml document using an XDocument object, and then overwriting the existing file using xDoc.save(path), but for some reason the first time I run the code an error is thrown even though the file is not in use by any other process.
*"The process cannot access the file "C:\XXX\XXXX\Telemetry\2011_11_22.tlm because it is being used by another process."*
Subsequent iterations do not cause a problem.
Here is my code that I am using with try/catch removed for readability:-
XElement x = GenerateTelemetryNode(h); //Create a new element to append
if (File.Exists(path))
{
if (xDoc == null)
{
xDoc = XDocument.Load(new StreamReader(path));
}
}
else
{
xDoc = new XDocument();
xDoc.Add(new XElement("TSD"));
}
xDoc.Element("TSD").Add(x);
xmlPath = path;
xDoc.Save(path);
I'm sure there's a very simple explanation for it.
Many thanks in advance for any replies.

I would expect the problem is the StreamReader hasn't been disposed in which case it will still be attached to the document. I would suggest using wrapping the StreamReader creation in a using clause to ensure that is disposed of immediately after the document has been loaded:
if (xDoc == null)
{
using (var sr = new StreamReader(path))
{
xDoc = XDocument.Load(new StreamReader(sr));
}
}

Use the overload of XDocument.Load that takes a Uri (the file name) and not a stream.

if (File.Exists(path))
{
if (xDoc == null)
{
StreamReader stream = new StreamReader(path);
using (stream)
{
xDoc = XDocument.Load(stream);
}
}
}
else
{
xDoc = new XDocument();
xDoc.Add(new XElement("TSD"));
}
xDoc.Element("TSD").Add(x);
xmlPath = path;
xDoc.Save(path);

Can't test for the moment, but I'd suspect the StreamReader to be using it, especially if you first iteration only causes this Exception. Have you tried :
XElement x = GenerateTelemetryNode(h); //Create a new element to append
if (File.Exists(path))
{
if (xDoc == null)
{
StreamReader reader = new StreamReader(path);
xDoc = XDocument.Load(reader);
reader.Close();
reader.Dispose();
}
}
else
{
xDoc = new XDocument();
xDoc.Add(new XElement("TSD"));
}
xDoc.Element("TSD").Add(x);
xmlPath = path;
xDoc.Save(path);

Related

If XML Doesn't exist, Create one in order to append it. (C# , ASP.net)

I want to check if my file does exist first, but if not.. How can i make it exist?
if (File.Exists(Filepath))
{
// if it does exist, itll show datas as data grid view
dataGridView2.DataSource = ds.Tables[0].DefaultView;
}
else
{
// if it doesnt exist, how can i make it exist? or create an XML file
}
You could use XmlDocument and XmlTextWriter
XmlDocument doc = new XmlDocument();
doc.LoadXml("<sample></sample>"); //your content here
// Save the document to a file
XmlTextWriter writer = new XmlTextWriter("sample.xml", null);
doc.Save(writer);
You could also just use a FileStream with the File class.
if (!File.Exists(Filepath))
{
using (FileStream fs = File.Create(Filepath))
{
Byte[] info = new UTF8Encoding(true).GetBytes("Text in the file.");
// Add some information to the file.
fs.Write(info, 0, info.Length);
}
}
Another approach by using LINQ to XML types without explicit using of writers
if (File.Exists(Filepath))
{
// do something
}
else
{
var document =
new XDocument(new XElement("root",
new XElement("one", "value 1"),
new XElement("two", "value 2"));
document.Save(FilePath);
}
using System.Xml.Linq;
XDocument doc = new XDocument(
new XElement("YourNodeName")
);
doc.Save("your_doc_name.xml");
Use StreamWriter to create a new file, specify the path.
You can do this:
using(var tw = new StreamWriter(path, true))
{
tw.WriteLine("New file content");
}

Load XML document in read-only access mode

How do I load an XML document in read-only mode?
I have an XML file which is opened in another process and I want to load it in my C# application as read-only.
XmlDocument.Load("file.xml") obviously throws this error:
Process cannot access a file because it is being used by another
process
So I tried stream reader too:
FileStream fs = new FileStream("file.xml", FileMode.Open, FileAccess.Read);
xmldoc.Load(fs);
But it also throws the same error.
So how can I access my XML Document in read-only mode?
Update
I tried XPathDocument and FileStream("file.xml", FileMode.Open, FileAccess.Read,FileShare.Read) as well. But neither of them solved the problem.
This class Shows read xml file in read only mode.
public List<string[]> GetRunningOrderOnTable(string tableNo, int shopid)
{
try
{
XmlDocument xmlDoc = new XmlDocument();
string xmlFilePath = #"C:\inetpub\wwwroot\ShopAPI\XmlData\RunningTables.xml";
//string xmlFilePath = HttpContext.Current.Server.MapPath("~/XmlData/RunningTables.xml");
// Option 1
// FileStream xmlFile = new FileStream(xmlFilePath, FileMode.Open,
//FileAccess.Read, FileShare.Read);
// xmlDoc.Load(xmlFile);
// Option 2
using (Stream s = File.OpenRead(xmlFilePath))
{
xmlDoc.Load(s);
}
//xmlDoc.Load(xmlFilePath);
List<string[]> st = new List<string[]>();
XmlNodeList userNodes = xmlDoc.SelectNodes("//Tables/Table");
if (userNodes != null)
{
foreach (XmlNode userNode in userNodes)
{
string tblNo = userNode.Attributes["No"].Value;
string sid = userNode.Attributes["ShopID"].Value;
if (tblNo == tableNo && sid == shopid.ToString())
{
string[] str = new string[5];
str[0] = userNode.Attributes["No"].Value;
str[1] = userNode.InnerText; // OrderNumber
str[2] = userNode.Attributes["OrderID"].Value;
str[3] = userNode.Attributes["OrderedOn"].Value;
str[4] = userNode.Attributes["TotalAmount"].Value;
st.Add(str);
}
}
}
else return new List<string[]>();
return st;
}
catch (Exception ex)
{
CustomLogging.Log("RunningTables.xml GetRunningOrderOnTable Error " + ex.StackTrace, LoggingType.XMLRead);
return new List<string[]>();
}
}
Given you've said that FileShare.Read doesn't work, it would appear that the other process has the file open for writing.
You could try opening it with FileAccess.Read and FileShare.ReadWrite, in which case you'll need to handle any errors that may occur if the other process does actually write to the file.
If that doesn't work, it's likely that the other process has it opened with FileShare.None, in which case there's nothing you can do about it. To check this, try opening the file with, say, Notepad.
But is it still possible for FileShare.ReadWrite to throws error if it works in most cases?
You will only get an error if another process has already opened the file using FileShare.None. You've confirmed that this isn't the case when it's open in Microsoft Word, so you should be OK.

Editing custom XML part in word document sometimes corrupts document

We have a system that stores some custom templating data in a Word document. Sometimes, updating this data causes Word to complain that the document is corrupted. When that happens, if I unzip the docx file and compare the contents to the previous version, the only difference appears to be the expected change in the customXML\item.xml file. If I re-zip the contents using 7zip, it seems to work OK (Word no longer complains that the document is corrupt).
The (simplified) code:
void CreateOrReplaceCustomXml(string filename, MyCustomData data)
{
using (var doc = WordProcessingDocument.Open(filename, true))
{
var part = GetCustomXmlParts(doc).SingleOrDefault();
if (part == null)
{
part = doc.MainDocumentPart.AddCustomXmlPart(CustomXmlPartType.CustomXml);
}
var serializer = new DataContractSerializer(typeof(MyCustomData));
using (var stream = new MemoryStream())
{
serializer.WriteObject(stream, data);
stream.Seek(0, SeekOrigin.Begin);
part.FeedData(stream);
}
}
}
IEnumerable<CustomXmlPart> GetCustomXmlParts(WordProcessingDocument doc)
{
return doc.MainDocumentPart.CustomXmlParts
.Where(part =>
{
using (var stream = doc.Package.GePart(c.Uri).GetStream())
using (var streamReader = new StreamReader(stream))
{
return streamReader.ReadToEnd().Contains("Some.Namespace");
}
});
}
Any suggestions?
Since re-zipping works, it seems the content is well-formed.
So it sounds like the zip process is at fault. So open the corrupted docx in 7-Zip, and take note of the values in the "method" column (especially for customXML\item.xml).
Compare that value to a working docx - is it the same or different? Method "Deflate" works.
I faced the same issue and it turned out it was due to encoding.
Do you already specify the same encoding when serializing/deserializing?
Couple of suggestion
a. Try doc.Package.Flush(); after you write the data back into the custom xml.
b. You may have to delete all custom part and add a new custom part. We are using the following code and it seems working fine.
public static void ReplaceCustomXML(WordprocessingDocument myDoc, string customXML)
{
MainDocumentPart mainPart = myDoc.MainDocumentPart;
mainPart.DeleteParts<CustomXmlPart>(mainPart.CustomXmlParts);
CustomXmlPart customXmlPart = mainPart.AddCustomXmlPart(CustomXmlPartType.CustomXml);
using (StreamWriter ts = new StreamWriter(customXmlPart.GetStream()))
{
ts.Write(customXML);
ts.Flush();
ts.Close();
}
}
public static MemoryStream GetCustomXmlPart(MainDocumentPart mainPart)
{
foreach (CustomXmlPart part in mainPart.CustomXmlParts)
{
using (XmlTextReader reader =
new XmlTextReader(part.GetStream(FileMode.Open, FileAccess.Read)))
{
reader.MoveToContent();
if (reader.Name.Equals("aaaa", StringComparison.OrdinalIgnoreCase))
{
string str = reader.ReadOuterXml();
byte[] byteArray = Encoding.ASCII.GetBytes(str);
MemoryStream stream = new MemoryStream(byteArray);
return stream;
}
}
}
return null; //result;
}
using (WordprocessingDocument myDoc = WordprocessingDocument.Open(ms, true))
{
StreamReader reader = new StreamReader(memStream);
string FullXML = reader.ReadToEnd();
ReplaceCustomXML(myDoc, FullXML);
myDoc.Package.Flush();
//Code to save file
}

Split large XML file after string found

What I have:
A large XML file # nearly 1 million lines worth of content. Example of content:
<etc35yh3 etc="numbers" etc234="a" etc345="date"><something><some more something></some more something></something></etc123>
<etc123 etc="numbers" etc234="a" etc345="date"><something><some more something></some more something></something></etc123>
<etc15y etc="numbers" etc234="a" etc345="date"><something><some more something></some more something></something></etc123>
^ repeat that by 900k or so lines (content changing of course)
What I need:
Search the XML file for "<etc123". Once found move (write) that line along with all lines below it to a separate XML file.
Would it be advisable to use a method such as File.ReadAllLines for the search portion? What would you all recommend for the writing portion. Line by line is not an option as far as I can tell as it would take much too long.
To quite literaly discard the content above your search string, I would not use File.ReadAllLines, as it would load the entire file into memory. Try File.Open and wrap it in a StreamReader. Loop on StreamReader.ReadLine, then start writing to a new StreamWriter, or do a byte copy on the underlying filestream.
An example of how to do so with StreamWriter/StreamReader alone is listed below.
//load the input file
//open with read and sharing
using (FileStream fsInput = new FileStream("input.txt",
FileMode.Open, FileAccess.Read, FileShare.Read))
{
//use streamreader to search for start
var srInput = new StreamReader(fsInput);
string searchString = "two";
string cSearch = null;
bool found = false;
while ((cSearch = srInput.ReadLine()) != null)
{
if (cSearch.StartsWith(searchString, StringComparison.CurrentCultureIgnoreCase)
{
found = true;
break;
}
}
if (!found)
throw new Exception("Searched string not found.");
//we have the data, write to a new file
using (StreamWriter sw = new StreamWriter(
new FileStream("out.txt", FileMode.OpenOrCreate, //create or overwrite
FileAccess.Write, FileShare.None))) // write only, no sharing
{
//write the line that we found in the search
sw.WriteLine(cSearch);
string cline = null;
while ((cline = srInput.ReadLine()) != null)
sw.WriteLine(cline);
}
}
//both files are closed and complete
You can copy with LINQ2XML
XElement doc=XElement.Load("yourXML.xml");
XDocument newDoc=new XDocument();
foreach(XElement elm in doc.DescendantsAndSelf("etc123"))
{
newDoc.Add(elm);
}
newDoc.Save("yourOutputXML.xml");
You could do one line at a time... Would not use read to end if checking contents of each line.
FileInfo file = new FileInfo("MyHugeXML.xml");
FileInfo outFile = new FileInfo("ResultFile.xml");
using(FileStream write = outFile.Create())
using(StreamReader sr = file.OpenRead())
{
bool foundit = false;
string line;
while((line = sr.ReadLine()) != null)
{
if(foundit)
{
write.WriteLine(line);
}
else if (line.Contains("<etc123"))
{
foundit = true;
}
}
}
Please note, this method may not produce valid XML, given your requirements.

"Root element is missing" error but I have a root element

If anyone can explain why I'm getting a "Root element is missing" error when my XML document (image attached) has a root element, they win a pony which fires lazers from its eyes.
Code:
if (ISF.FileExists("Players.xml"))
{
string xml;
using (IsolatedStorageFileStream rawStream = ISF.OpenFile("Players.xml", FileMode.Open))
{
StreamReader reader = new StreamReader(rawStream);
xml = reader.ReadToEnd();
XmlReaderSettings settings = new XmlReaderSettings { IgnoreComments = true, IgnoreWhitespace = true };
XmlReader xmlReader = XmlReader.Create(reader, settings);
while (xmlReader.Read())
{
switch (xmlReader.NodeType)
{
case XmlNodeType.Element:
switch (xmlReader.Name)
{
case "numberOfPlayers":
string nodeValue = xmlReader.ReadContentAsString();
int NODEVALUE = int.Parse(nodeValue);
MessageBox.Show(" " + NODEVALUE);
break;
}
break;
}
break;
}
reader.Close();
}
}
Your problem is due to this line:
xml = reader.ReadToEnd();
This positions the reader stream to the end so that when XmlReader.Create is executed, there is nothing left in the stream for it to read.
If you need the xml string to be populated, then you need to close and reopen the reader prior to XmlReader.Create. Otherwise, removing or commenting this line out will solve your problem.
Reset the base stream's position each time it is read if you want to read from the beginning as stated earlier, but you don't have to re-create the stream each time.
String xmlResource = Assembly.GetExecutingAssembly().GetName().Name + ".XML.IODeleter.xsd";
configXsd = new StreamReader(Assembly.GetExecutingAssembly().GetManifestResourceStream(xmlResource));
if (configXsd != null)
{
configXsd.BaseStream.Position = 0;
File.WriteAllText(apppath + #"\" + Assembly.GetExecutingAssembly().GetName().Name + ".XML.IODeleter.xsd", configXsd.ReadToEnd());
}
I ended up creating a quick little function to reference before each new XmlReader...
private void ResetStream()
{
/*
The point of this is simply to open the stream with a StreamReader object
and set the position of the stream to the beginning again.
*/
StreamReader reader = new StreamReader(m_stream);
if (reader != null)
{
reader.BaseStream.Position = 0;
}
}
So when I'm working in xml I call it right before I create my reader. I always have the same stream in memory and never recreate that.
ResetStream();
using (XmlReader reader = XmlReader.Create(m_stream)) { reader.Read(); }

Categories

Resources