Is there anything to consider when using XmlDocument function in unity3d?
I'm having this weird problem: When the function which uses XmlDocument is called from Awake() or OnGUI() the document is edited successfully. But when it's called from inside a button event, event tough I get a well edited string before saving the document, it's not able to modify the document itself.
Function that edits the file (sometimes):
public static void addTestProfile () {
string path = Application.dataPath + "/Documents/Profiles.xml";
Hashtable tempTable = new Hashtable();
tempTable.Add("user", "chuck II");
tempTable.Add("url", "funny");
tempTable.Add("passwrod", "1234asdf");
General.StoreProfile(tempTable, path);
}
public static void StoreProfile(Hashtable profile, string path) {
Debug.Log("profile to store name: " + profile["password"]);
XmlDocument doc = new XmlDocument();
doc.Load(profilesPath);
XmlElement element = doc.CreateElement("Profile");
XmlElement innerElement1 = doc.CreateElement("user");
innerElement1.InnerText = profile["user"] as string;
element.AppendChild(innerElement1);
XmlElement innerElement2 = doc.CreateElement("url");
innerElement2.InnerText = profile["url"] as string;
element.AppendChild(innerElement2);
XmlElement innerElement3 = doc.CreateElement("password");
innerElement3.InnerText = profile["password"] as string;
element.AppendChild(innerElement3);
doc.DocumentElement.AppendChild(element);
doc.Save(profilesPath);
Debug.Log(doc.InnerXml);
}
I created a new project just to test this problem, the file is not edited when called just before calling Application.loadLevel();
Here it works well and the file itself is edited:
void OnGUI () {
General.addTestProfile(); // General is the singleton class that contains the function implementation
}
But some how this is not working:
// GUI Save btn
if (GUI.Button(new Rect(255, 20, 60, 35), "Add")) {
General.addTestProfile(); // General is the singleton class that contains the function implementation
Application.LoadLevel(0);
}
When I print the resultant string right before the save() xmlDocument function, it shows the new item but somehow the xml file remains the same.
Am I missing something important maybe related to the execution order? Something like timeout?
this is just a wild guess, but could this work?
// PSEUDOCODE
bool pressed
function OnGUI()
if GUI.Button then
pressed = true
end if
if pressed then
addTestProfile();
end if
end function
I took help from this link: http://answers.unity3d.com/questions/9538/new-to-unity-3d-gui-button-help-needed-please
When it jumped back to scene 1, it was re-writing the original file.
"Debug.Log" sucks! , this instruction never printed the second call to createProfilesFile() function. So just one line was missing:
if (System.IO.File.Exists(profilesPath)) return;
Here the createProfilesFile() function:
public static void CreateProfilesFile (string path) {
Debug.Log("create init"); // This line wasn't called the second time ...
if (System.IO.File.Exists(path)) return;
// Create a new file specified path
XmlTextWriter textWriter = new XmlTextWriter(path,null);
// Opens the document
textWriter.WriteStartDocument();
// Write comments
textWriter.WriteComment("This document contains the profiles that have been created.");
textWriter.WriteStartElement("Profiles");
textWriter.WriteStartElement("Profile");
textWriter.WriteStartElement("user");
textWriter.WriteString("foo user");
textWriter.WriteEndElement();
textWriter.WriteStartElement("url");
textWriter.WriteString("foo url");
textWriter.WriteEndElement();
textWriter.WriteStartElement("password");
textWriter.WriteString("foo password");
textWriter.WriteEndElement();
textWriter.WriteEndElement();
textWriter.WriteEndElement();
// Ends the document.
textWriter.WriteEndDocument();
// close writer
textWriter.Close();
}
Related
I am writing a Windows Form Application for data post-processing. I have a panel where I allow for files to be dragged and dropped. The XML files will be quite large (enough to slow the UI down). Therefore I would like to read the file in asynchronously. So far for this part of the app I have two methods:
namespace myApp
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void DragDropPanel_DragEnter(object sender, DragEventArgs e)
{
// Trigger the Drag Drop Event
e.Effect = DragDropEffects.Copy;
}
private async void DragDropPanel_DragDrop(object sender, DarEventArgs e)
{
// Identifiers used are:
string[] filePaths = (string[])e.Data.GetData(DataFormats.FileDrop);
string filePath = filePaths[0],
fileName = System.IO.Path.GetFileName(filePath);
// Read in the file asynchronously
XmlReader reader = XmlReader.Create(filePath);
//reader.Settings.Async = true; // ** (SECOND ERROR) ** \\
while (await reader.ReadAsync()) // ** (FIRST ERROR) ** \\
{
Console.WriteLine("testing...");
}
// Do other things...
}
}
}
Now when I drag and drop the XML file I get the following error:
System.InvalidOperationException:
Set XmlReaderSettings.Async to true if you want to use Async Methods.
this error occurs because of the line I labeled with FIRST ERROR. I attempt to fix this by uncommenting the line above it which I have labeled with SECOND ERROR. Now when I drag and drop I get the error:
System.Xml.Xml.XmlException:
The XmlReaderSettings.Async property is read only and cannot be set
So I go to the MS Docs for the XmlReaderSettings.Async property and it says:
You must set this value to true when you create a new XmlReader instance if you want to use asynchronous XmlReader methods on that instance.
Which then gives the reason why the SECOND ERROR occurs. However, I cannot get this to work. Any tips?
You need to Create the XmlReader with the proper settings.
XmlReaderSettings settings = new XmlReaderSettings
{
Async = true
};
XmlReader reader = XmlReader.Create(filePath, settings);
References:
https://msdn.microsoft.com/en-us/library/ms162474(v=vs.110).aspx
https://msdn.microsoft.com/en-us/library/system.xml.xmlreadersettings(v=vs.110).aspx
I wrote a method to save an xml file from the web service. inside
that method first I check whether the file is exists.if exists,
delete it and save.if not save.
Other thing is I fire this methods using timer.(once a day automatically).
my question is when I save xml, if it exists what is happen.(Because when I run it locally visual studio asks "your xml has changed, do you want to reload it").does it reload automatically or completely delete the existing one or do we have to write a code to reload the xml file.
this is my code.
//method to save xml
public void sendValue()
{
string wbserviceUrl = "https://someurl.ashx";
WebClient clientOne = new WebClient();
string result = clientOne.DownloadString(wbserviceUrl);
XmlDocument cruisexmlDocument = new XmlDocument();
if (System.IO.File.Exists(#"D:/Anuprojects/Mppro/XmlFiles/Cruisedata/cruiseprodutsfour.xml"))
{
System.IO.File.Delete(#"D:/Anuprojects/Mppro/XmlFiles/Cruisedata/cruiseprodutsfour.xml");
cruisexmlDocument.LoadXml(result);
cruisexmlDocument.Save("D:/Anuprojects/Mppro/XmlFiles/Cruisedata/cruiseprodutsfour.xml");
}
else
{
cruisexmlDocument.LoadXml(result);
cruisexmlDocument.Save("D:/Anuprojects/Mppro/XmlFiles/Cruisedata/cruiseprodutsfour.xml");
}
}
//set daily time
public void setupTimer(TimeSpan savingTime)
{
DateTime current = DateTime.Now;
TimeSpan timeTogo = savingTime - current.TimeOfDay;
if(timeTogo < TimeSpan.Zero)
{
return;
}
this.timer = new Timer(x =>
{
this.sendValue();
},null,timeTogo,InfiniteTimeSpan);
}
//run it in the controller
public ActionResult Show()
{
setupTimer(new TimeSpan(11, 05,00));
return View();
}
If i understood your question, it is that if file already exist will it reload on execution?Answer to that would be that it depends on editor that you are using.
The reason why you are getting this error is like "the same file name is already exists in the directory.".Suggestion would be
Delete the exisitng file and save it [or]
Save the data with the date format like Products23102015.xml
This is driving me abit nuts so if anyone could hepl i'd be very grateful!!
I am trying to log my information to a log file so I used a Logger class.
** Logger.cs **
public class Logger : IDisposable
{
private readonly FileStream _file; //Only this instance have a right to own it
private readonly StreamWriter _writer;
private readonly object _mutex; //Mutex for synchronizing
/// <summary>
/// Call this function to use the text file
/// </summary>
/// <param name="logPath"></param>
public Logger(string logPath)
{
if (logPath != null) _file = new FileStream(logPath, FileMode.Append);
_writer = new StreamWriter(_file);
_mutex = new object();
}
// Log is thread safe, it can be called from many threads
public void Log(string message)
{
lock (_mutex)
{
//_writer.Write("\r\nLog Entry : ");
// _writer.WriteLine("{0} {1}", DateTime.Now.ToLongTimeString(),
//DateTime.Now.ToLongDateString());
_writer.WriteLine("{0} {1}", DateTime.Now.ToString("yyyy-MM-dd"),
DateTime.Now.ToLongTimeString());
_writer.WriteLine(message);
}
}
/// <summary>
/// Call this function when it says file is already been using somewhere
/// </summary>
public void Dispose()
{
_writer.Dispose(); //Will close underlying stream
}
}
** My Application Using Logger Class **
private void button1_Click(object sender, EventArgs e)
{
// This is the file path after d://dashboardLogfiles
String filePath = string.Format("{0:yyyy-MM-dd}", DateTime.Now);
// This is the text file created with time stamps
String txtFile = string.Format("DataSummarisation{0:yyyy-MM-dd hh-mm-ss-tt}", DateTime.Now);
// Given in config to read the path
var localhostizedLetter = #"d:/";
//Create directory
string pathString = Path.Combine(localhostizedLetter, "DataSummarisationLogfiles");
if (!Directory.Exists(pathString))
{
Directory.CreateDirectory(pathString);
}
// Create a folder inside directory
// If folder exists dont create it
pathString = Path.Combine(pathString, filePath);
if (!Directory.Exists(pathString))
{
Directory.CreateDirectory(pathString);
}
// create a file inside d://DataSummarisationDatetime.now//datetimewithtimestamp.txt
// if exists please dont create it.
pathString = Path.Combine(pathString, txtFile);
if (!Directory.Exists(pathString))
{
// here my file is created and opened.
// so I m doing a try catch to make sure if file is opened we are closing it so that nother process can use it
File.Create(pathString).Dispose();
var fileInfo = new FileInfo(pathString);
// IsFileLocked(fileInfo);
}
_logger = new Logger(pathString);
_logger.Log("Log File Created");
_logger.Dispose();
ThreadStart starter = () => MigrateProductStats(123, 0, pathString);
var thread = new Thread(starter);
thread.Start();
}
** My Functions Using Same Logger Path **
private void MigrateProductStats(object corporationIdObj, object brandIdObj, object logFilePath)
{
_logger = new Logger(logFilePath.ToString());
_logger.Log("Am I still writing to same file?");
_logger.Dispose();
for (int i = 0; i <= 10;i++ )
{
DoProductStatsForCorporation(123, logFilePath.ToString());
}
}
private void DoProductStatsForCorporation(int corporationId, string logFilePath)
{
_logger = new Logger(logFilePath);
_logger.Log("Am I still writing to same file second time?");
_logger.Dispose();
}
** Above Scenario is Working **
** But I am expecting to pass object instead of Path to avoid Reinstatiaitng **
ThreadStart starter = () => MigrateProductStats(123, 0, _logger);
var thread = new Thread(starter);
thread.Start();
In the above case in my Button click I am disposing logger and sending the path to the functions DoProductStatsForCorporation and MigrateProductStats instead of that if I try sending _logger object without disposing that and avoiding to Reiniate in my child functions I am getting error cannot write to the file as it is used by another process.
I hope that makes sense!
Any guidance on this would be much appreciated as i am pretty stuck on where to go with this.
I'm slightly confused. Why do you pass the file path to the Logger? It should probably control this itself. Personally, I would make my Logger class static and then synchronize the methods like so:
[MethodImpl(MethodImplOptions.Synchronized)]
public static void Log(string description)
Then, use and dispose of the writer inline to avoid these very issues.
using (var writer = new StreamWriter(Path + "\\" + LOG_FILE, true))
{
writer.WriteLine(log);
writer.Close();
}
The problem you have is that it's MT and most likely you are probably writing to a file that is already open (race conditions)
why dont you use a singleton for your logger/writer? why dont you lock the writer and only ever use the one instance instead of always creating a new instance?
also your path string looks really wrong. I mean why spit out every last milisecond/tick in the file name?
I suggest you use the singleton approach for logging or if you must, create a static instance of the logger and lock the file when you are writing and dispose when complete. you are writing to the file that maybe in use whilst other threads are accessing that file. Be sure that the filename is also unique - it may not appear to be what you think it is in the code.
For starters DoProductStatsForCorporation logger should not be parameter of this function.
Your logger should be created at start, and beeing a field in class.
You do not want to pass logger to functions because function don't really need this logger to do its job.
Move logger out of some button code, create it in constructor and use like from private field in class where logging takes place.
If you want to change log files in the same class and log to different files then you have to rewrite your logger class so it could use several files.
Also you can learn about Log4net it is really nice.
I have tried to figure this out now for about 30 minutes to no avail.
I got this code:
class BookMarkTest
{
Application word;
Document doc;
public BookMarkTest()
{
word = new Application();
doc = word.Documents.Open("C:\\Users\\Vipar\\Desktop\\TestSkabelon.docx");
Console.WriteLine("BEFORE:\n");
foreach (Bookmark b in doc.Bookmarks)
{
Console.WriteLine("Name: {0}\n", b.Name);
}
Console.WriteLine("Name: {0}, Text: {1}\n", doc.Bookmarks[2].Name, doc.Bookmarks[2].Range.Text);
test(ref doc);
Console.WriteLine("------------------\n");
Console.WriteLine("AFTER:\n");
foreach (Bookmark b in doc.Bookmarks)
{
Console.WriteLine("Name: {0}\n", b.Name);
}
Console.WriteLine("Name: {0}, Text: {1}\n", doc.Bookmarks[2].Name, doc.Bookmarks[2].Range.Text);
doc = null;
word = null;
}
// Fixed the code so it replaces bookmarks correctly rather than removing them.
public void test(ref Document doc)
{
Dictionary<string, Bookmark> bookmarks = new Dictionary<string, Bookmark>();
foreach(Bookmark b in doc.Bookmarks) {
bookmarks.Add(b.Name, b);
}
BookMarkReplaceNative(bookmarks["Titel"], "Min nye titel");
}
internal void BookMarkReplaceNative(Bookmark bookmark, string newText)
{
object rng = bookmark.Range;
string name = bookmark.Name;
bookmark.Range.Text = newText;
doc.Bookmarks.Add(name, rng);
}
}
First I check if all the bookmarks are there. There are 3 (I print them out to check this before I do anything else) so the array should be 0 = KontraktStart, 1 = Signatur, 2 = Titel , but when I call doc.Bookmarks[2].Name I get Signatur rather than Titel. I can't figure out why that is. I have tried to do doc.Bookmarks[3].Name but that tells me the element doesn't exist. For some reason I can't call doc.Bookmarks[0].Name name though. It's like an element disappears and is replaced with nothing.
Also my test() method removes Title from the Bookmarks array entirely. I knew this would happen, but how do I go about just replacing the bookmark rather than completely remove it? When I look in my document I can see that the text on the specific bookmark actually changes, but the bookmark is removed which is not desired. FIXED THIS. ADDED TO CODE SNIPPET
So my question is two-fold:
How come elements are disappearing from the Bookmarks collection before I even manipulate the collection?
How do I replace a Bookmark rather than completely remove it? FIXED THIS. ADDED TO CODE SNIPPET
Thanks in advance!
I have looked all over for this. It could be me just typing the wrong thing in search I'm not sure. So, if you know a good tutorial or example of this please share. I'm trying to learn.
I have a C# Windows Form app I'm working on. I have information (movies in this case) saved in an XML file. I saved the xml file like this.
//Now we add new movie.
XmlElement nodRoot = doc.DocumentElement;
string allMyChildren = nodRoot.InnerText;
string capitalized = CultureInfo.CurrentCulture.TextInfo.ToTitleCase(movieEditNameTextbox.Text);
int indexLookForNewMake = allMyChildren.IndexOf(capitalized);
if (indexLookForNewMake >= 0)
{
MessageBox.Show("Movie is already saved.", "Error");
}
else
{
XmlElement el = doc.CreateElement("Name");
el.InnerText = capitalized;
doc.DocumentElement.AppendChild(el);
//Check if Year is really a Number.
if (movieEditYearTextbox.Text.All(Char.IsDigit))
{
//Remove ' cause it gives errors.
string capitalizedFixed = capitalized.Replace("'", "");
string capitalizedFinalFixed = capitalizedFixed.Replace("\"", "");
//Assign Attribute to each New one.
el.SetAttribute("Name", capitalizedFinalFixed);
el.SetAttribute("Type", movieEditTypeDropdown.Text);
el.SetAttribute("Year", movieEditYearTextbox.Text);
//Reset all fields, they don't need data now.
movieEditNameTextbox.Text = "";
movieEditYearTextbox.Text = "";
movieEditTypeDropdown.SelectedIndex = -1;
removeMovieTextbox.Text = "";
doc.Save("movie.xml");
label4.Text = "Movie Has been Edited";
loadXml();
}
else
{
//Error out. Year not a Number
MessageBox.Show("Check movie year. Seems it isn't a number.", "Error");
}
}
That all works fine. Now what I'm trying to do is make it where you can choose a directory, and it search the directory and sub directories and get file names and save them into the XML file.
I used this to try to accomplish this. It does pull the list. But it doesn't save it. It don't save the new information.
I can't use LINQ as it cause a confliction for some reason with other code.
DirectoryInfo dirCustom = new DirectoryInfo(#"D:\Video");
FileInfo[] filCustom;
filCustom = dirCustom.GetFiles("*",SearchOption.AllDirectories);
//Open XML File.
XmlDocument doc = new XmlDocument();
doc.Load("movie.xml");
XmlElement el = doc.CreateElement("Name");
string fulCustoms = filCustom.ToString();
foreach (FileInfo filFile in filCustom)
{
string capitalized = CultureInfo.CurrentCulture.TextInfo.ToTitleCase(filFile.Name);
string capitalizedFixed = capitalized.Replace("\"", "");
el.SetAttribute("Name", capitalizedFixed);
el.SetAttribute("Type", "EDIT TYPE");
el.SetAttribute("Year", "EDIT YEAR");
richTextBox1.AppendText(capitalizedFixed + "\r\n");
}
doc.Save("movie.xml");
label4.Text = "Movie Has been Edited";
loadXml();
Now, the richTextBox does display the information correctly but it don't save it.
The loadXml() is just my noobish way to refresh the datagridview.
I'm completely lost and don't know where to turn to. I know my coding is probarely horrible, lol. I'm new to this. This is my first more complex application I have worked on.
I can't think of anymore information that would help you understand what I mean. I hope you do.
Thank you so much for your help.
Not sure exactly what your LoadXML() method does but my only piece of advise with your issue is to change the way you are implementing this functionality.
Create an object called Movie
public class Movie
{
public Movie() {}
public String Title { get; set; }
blah... blah...
}
Then create a MovieList
public class MovieList : List<Movie> { }
Then implement the following 2 methods inside the MovieList.
public static void Serialize(String path, MovieList movieList)
{
XmlSerializer serializer = new XmlSerializer(typeof(MovieList));
using (StreamWriter streamWriter = new StreamWriter(path))
{
serializer.Serialize(streamWriter, movieList);
}
}
public static MovieList Deserialize(String path)
{
XmlSerializer serializer = new XmlSerializer(typeof(MovieList));
using (StreamReader streamReader = new StreamReader(path))
{
return (MovieList) serializer.Deserialize(streamReader);
}
}
Thats it... You now have your object serialized and you can retrieve the data to populate through binding or whatever other methods you choose.