Error serializing data in an xml file - c#

I have a problem with the highscore code for a game I am creating in school.
When the game ends it says that it cant access HighScore because of its protection level.
This is the code that is giving me an error :
public static HighScore LoadHighScores(string filename)
{
HighScore data;
// Get the path of the save game
string fullpath = "highscores.xml";
// Open the file
FileStream stream = File.Open(fullpath, FileMode.OpenOrCreate, FileAccess.Read);
try
{
// Read the data from the file
XmlSerializer serializer = new XmlSerializer(typeof(HighScore));
data = (HighScore)serializer.Deserialize(stream);
}
finally
{
// Close the file
stream.Close();
}
return (data);
}
When i run the code and die i call this code that saves the score i got:
public void SaveHighScore(int score)
{
// Create the data to saved
HighScore data = LoadHighScores(HighScoresFilename);
int scoreIndex = -1;
for (int i = data.Count--; i > -1; i--)
{
if (score >= data.Score[i]) //score.getScore();
{
scoreIndex = i;
}
}
if (scoreIndex > -1)
{
//New high score found ... do swaps
for (int i = data.Count--; i > scoreIndex; i--)
{
data.PlayerNames[i] = data.PlayerNames[i - 1];
data.Score[i] = data.Score[i - 1];
}
data.PlayerNames[scoreIndex] = PlayerName; //Retrieve User Name Here
data.Score[scoreIndex] = score; // Retrieve score here
SaveHighScores(data, HighScoresFilename);
}
}
And SaveHighScores looks like this:
public static void SaveHighScores(HighScore data, string filename)
{
// Get the path of the save game
string fullpath = "highscores.xml";
// Open the file, creating it if necessary
FileStream stream = File.Open(fullpath, FileMode.OpenOrCreate);
try
{
// Convert the object to XML data and put it in the stream
XmlSerializer serializer = new XmlSerializer(typeof(HighScore));
serializer.Serialize(stream, data);
}
finally
{
// Close the file
stream.Close();
}
}

Please check this link
Public Class - "is inaccessible due to its protection level. Only public types can be processed."
you can find your answer there....
As a summary mark your HighScore as public.

Related

How to serialize a list of objects with JSON

I'm developing a software to manage my collection of coins. I need to export the content of a list of objects in a JSON file but I encounter this error everytime I want to display the coins that are actually inside the database:
Additional text encountered after finished reading JSON content: [. Path '', line 1, position 109.
Here's where everything should happen:
List<Coin> coins = new List<Coin>();
public bool AddACoin (int ID, String coinName, String coinNation, String coinStatus, int coinYear, int quantity, float value)
{
var jsonSerializer = new JsonSerializer();
using (StreamWriter streamWriter = new StreamWriter(path, true))
using (JsonWriter jsonWriter = new JsonTextWriter(streamWriter))
{
coins.Add(new Coin(ID, coinName, coinNation, coinStatus, coinYear, quantity, value));
jsonSerializer.Serialize(jsonWriter, coins.ToList());
}
return true;
}
The output is stored inside different blocks of square brackets. I've a block for every object inserted. Instead I should have every object inside a unique block of square brackets. Thanks in advance.
EDIT: Here's the content of the JSON file
[{"ID":0,"coinName":"1 Euro","coinNation":"Ita","coinStatus":"FdC","coinYear":2005,"quantity":1,"value":4.7}][{"ID":0,"coinName":"1 Euro","coinNation":"Ita","coinStatus":"FdC","coinYear":2005,"quantity":1,"value":4.7},{"ID":1,"coinName":"2 Euro","coinNation":"Bel","coinStatus":"FdC","coinYear":2004,"quantity":1,"value":30.0}]
As I said, everything should be inside a unique block of square brackets.
I think that I've just found the solution to my problem and I'm going to share it with you. I've changed some lines and now I have:
public bool AddACoin (int ID, String coinName, String coinNation, String coinStatus, int coinYear, int quantity, float value)
{
var jsonSerializer = new JsonSerializer();
using (StreamReader streamReader = new StreamReader(path, true))
{
string json = streamReader.ReadToEnd();
coins = JsonConvert.DeserializeObject<List<Coin>>(json);
coins.Add(new Coin(ID, coinName, coinNation, coinStatus, coinYear, quantity, value));
string newJson = JsonConvert.SerializeObject(coins);
streamReader.Close();
File.WriteAllText(path, newJson);
}
return true;
}
If I'm thinking correctly, doing this causes the program to read until it reaches EOF and then, after serializing/deserializing the list, appends the new object. At the moment this seems to works fine.
I recommend you to use NewtonsoftJSON (you can install it via NuGet), clear json file every time you adding new coin, there are coins manager sample for you:
public class CoinsManager
{
public List<Coin> Coins { get; set; }
public string FilePath { get; set; }
public CoinsManager(string filePath)
{
FilePath = filePath;
Coins = new List<Coin>();
}
public void LoadCoins()
{
if (File.Exists(FilePath))
{
//If file exists, but empty, save empty settings to it
if (new FileInfo(FilePath).Length == 0)
{
SaveSettings();
}
else
{
//Read json from file
using (StreamReader r = new StreamReader(FilePath))
{
string json = r.ReadToEnd();
//Convert json to list
Coins = JsonConvert.DeserializeObject<List<Coin>>(json);
}
}
}
else
{
//Create file
File.Create(FilePath).Close();
//Wait for filesystem to create file
while (!File.Exists(FilePath))
{
System.Threading.Thread.Sleep(100);
}
//Save empty settings to file
SaveSettings();
}
}
public void SaveSettings()
{
string json = JsonConvert.SerializeObject(Coins);
File.WriteAllText(FilePath, json);
}
//Can save or update passed coin
public void SaveCoin(Coin coin)
{
//Select old coin
var oldCoin = Coins.Where(c => c.ID == coin.ID).FirstOrDefault();
//If there was no old coin, get last existing coin id, or zero if Coins list is empty
if (oldCoin == null)
{
int lastId;
if (Coins.Count != 0)
lastId = Coins.Count - 1;
else
lastId = 0;
coin.ID = lastId + 1;
Coins.Add(coin);
}
else
{
int index = Coins.IndexOf(oldCoin);
Coins[index] = coin;
}
}
public void DeleteCoin(Coin coin)
{
Coins.RemoveAll(c => c.ID == coin.Id);
}
}
and it's usage:
CoinsManager coinsManager = new CoinsManager("coinsStorage.json");
coinsManager.LoadCoins();
coinsManager.SaveCoin(new Coin {
...
});
coinsManager.SaveSettings();
if i understand correct you just need to change this row:
StreamWriter streamWriter = new StreamWriter(path, true);
to this one:
StreamWriter streamWriter = new StreamWriter(path, false);
your problem is that you always add to the file new json with all the list instead of just writing the list.
Because you work with file you need and you want to append your only option is to read the file then add elements and write it again.
You can read it when application start and menage it like it seems you do becouse your list is global.
Or you can read it right before you want to write the file.
In any one of this cases you need to add the fix I wrote.
You can use this for read the json o your list:
string myJsonString = File.ReadAllText(path);
coins = JsonConvert.DeserializeObject<List<Coin>>(myJsonString);
here is full function:
public bool AddACoin (int ID, String coinName, String coinNation, String coinStatus, int coinYear, int quantity, float value)
{
var jsonSerializer = new JsonSerializer();
using (StreamWriter streamWriter = new StreamWriter(path, false))
using (JsonWriter jsonWriter = new JsonTextWriter(streamWriter))
{
string myJsonString = File.ReadAllText(path);
coins = JsonConvert.DeserializeObject<List<Coin>>(myJsonString);
coins.Add(new Coin(ID, coinName, coinNation, coinStatus, coinYear, quantity, value));
jsonSerializer.Serialize(jsonWriter, coins.ToList());
}
return true;
}

XML Serialization No Longer Formatting Correctly

I was able to create an XML serializer in Unity which saves my game state and was working fine. I haven't touched it since then, but a few days later it started saving the XML files on one line instead.
This was how it looked before:
<GameData xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<gameFlags/>
<memories/>
<lastSaveTime>-8586819040232916923</lastSaveTime>
<health>50</health>
<speed>0.27</speed>
<playerPosition>
<x>10</x>
<y>-48.2583</y>
<z>0</z>
</playerPosition>
<lastSaveFile/>
<savedScene>Area 0-0</savedScene>
<playerName>Jon</playerName>
<PlayerPositionX>10</PlayerPositionX>
<PlayerPositionY>-48.2583</PlayerPositionY>
<PlayerPositionZ>0</PlayerPositionZ>
</GameData>
This is how it looks now
-8586812865931894767 50 0.27 10 -48.2583 0 Default Area 0-0 Jon 10 -48.2583 0
Everything is all on one line. I reverted to my previous commit as well when it was working but it still is saving on one line. Does anyone have any experience with this change?
Save Game Method
public void SaveGame(string saveFile)
{
CheckDirectory();
gameData.generateGameData (GameObject.FindGameObjectWithTag("Player").GetComponent<Player>());
// Update saveFile name
if (saveFile == null)
{
saveFile = GenerateNewSaveName();
}
this.saveFile = saveFile;
// FileStream fs = File.Create(GameDic.Instance.SavePath + saveFile);
UpdateSaveData(saveFile);
string fullSavePath = SavePath + saveFile + FILE_EXTENSION;
FileStream fs;
// Create a file or open an old one up for writing to
if (!File.Exists(fullSavePath))
{
fs = File.Create(fullSavePath);
}
else
{
fs = File.OpenWrite(fullSavePath);
}
XmlSerializer serializer = new XmlSerializer(typeof(GameData));
TextWriter textWriter = new StreamWriter(fs, System.Text.Encoding.UTF8);
serializer.Serialize(textWriter, gameData);
fs.Close();
Debug.Log("Game Saved to " + fullSavePath);
}
GameData Class
[Serializable]
public class GameData
{
#region Public Fields
public List<GameFlag> gameFlags;
public List<string> memories;
public List<string> conversations;
public List<QuestInstance> questInstances;
public long lastSaveTime;
public float health, speed;
// Needs properties to access
[NonSerialized]
public Vector3 playerPosition;
public string lastSaveFile;
public string savedScene;
public string playerName;
#endregion Public Fields
#region Public Constructors
public void generateGameData(Player player) {
health = player.getStat (Stat.HP);
speed = player.getStat (Stat.SPEED);
savedScene = SceneManager.GetActiveScene ().name;
playerPosition = player.respawnPoint;
playerName = "Jon";
memories = player.GetComponentInChildren<MemoryManager> ().getAccessedMemories ();
conversations = player.GetComponentInChildren<NPCManager> ().getConversations ();
questInstances = player.GetComponent<QuestManager> ().getQuestInstances ();
}
public string GenerateNewSaveName()
{
int attempt = 0;
string newSaveName = "";
while (newSaveName == "")
{
// Save Name is Player Name
string checkString = gameData.playerName;
// Add a number if original already taken
if (attempt != 0) checkString += attempt;
if (!File.Exists(SavePath + checkString))
{
// Make the check string the new file name
newSaveName = checkString;
}
attempt++;
}
return newSaveName;
}
}
For the QuestInstance class I suppose it just can just be any filler class that's empty. It's serialized correctly.
Your problem is most likely this line:
fs = File.OpenWrite(fullSavePath);
If the previous file was longer than the new one, you will still have fragments from the previous save file at the end - that's why you can't deserialize it. Get rid of everything in that if condition except for the File.Create and it should work.

How to avoid c# File.ReadLines First() locking file

I do not want to read the whole file at any point, I know there are answers on that question, I want t
o read the First or Last line.
I know that my code locks the file that it's reading for two reasons 1) The application that writes to the file crashes intermittently when I run my little app with this code but it never crashes when I am not running this code! 2) There are a few articles that will tell you that File.ReadLines locks the file.
There are some similar questions but that answer seems to involve reading the whole file which is slow for large files and therefore not what I want to do. My requirement to only read the last line most of the time is also unique from what I have read about.
I nead to know how to read the first line (Header row) and the last line (latest row). I do not want to read all lines at any point in my code because this file can become huge and reading the entire file will become slow.
I know that
line = File.ReadLines(fullFilename).First().Replace("\"", "");
... is the same as ...
FileStream fs = new FileStream(#fullFilename, FileMode.Open, FileAccess.Read, FileShare.Read);
My question is, how can I repeatedly read the first and last lines of a file which may be being written to by another application without locking it in any way. I have no control over the application that is writting to the file. It is a data log which can be appended to at any time. The reason I am listening in this way is that this log can be appended to for days on end. I want to see the latest data in this log in my own c# programme without waiting for the log to finish being written to.
My code to call the reading / listening function ...
//Start Listening to the "data log"
private void btnDeconstructCSVFile_Click(object sender, EventArgs e)
{
MySandbox.CopyCSVDataFromLogFile copyCSVDataFromLogFile = new MySandbox.CopyCSVDataFromLogFile();
copyCSVDataFromLogFile.checkForLogData();
}
My class which does the listening. For now it simply adds the data to 2 generics lists ...
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using MySandbox.Classes;
using System.IO;
namespace MySandbox
{
public class CopyCSVDataFromLogFile
{
static private List<LogRowData> listMSDataRows = new List<LogRowData>();
static String fullFilename = string.Empty;
static LogRowData previousLineLogRowList = new LogRowData();
static LogRowData logRowList = new LogRowData();
static LogRowData logHeaderRowList = new LogRowData();
static Boolean checking = false;
public void checkForLogData()
{
//Initialise
string[] logHeaderArray = new string[] { };
string[] badDataRowsArray = new string[] { };
//Get the latest full filename (file with new data)
//Assumption: only 1 file is written to at a time in this directory.
String directory = "C:\\TestDir\\";
string pattern = "*.csv";
var dirInfo = new DirectoryInfo(directory);
var file = (from f in dirInfo.GetFiles(pattern) orderby f.LastWriteTime descending select f).First();
fullFilename = directory + file.ToString(); //This is the full filepath and name of the latest file in the directory!
if (logHeaderArray.Length == 0)
{
//Populate the Header Row
logHeaderRowList = getRow(fullFilename, true);
}
LogRowData tempLogRowList = new LogRowData();
if (!checking)
{
//Read the latest data in an asynchronous loop
callDataProcess();
}
}
private async void callDataProcess()
{
checking = true; //Begin checking
await checkForNewDataAndSaveIfFound();
}
private static Task checkForNewDataAndSaveIfFound()
{
return Task.Run(() => //Call the async "Task"
{
while (checking) //Loop (asynchronously)
{
LogRowData tempLogRowList = new LogRowData();
if (logHeaderRowList.ValueList.Count == 0)
{
//Populate the Header row
logHeaderRowList = getRow(fullFilename, true);
}
else
{
//Populate Data row
tempLogRowList = getRow(fullFilename, false);
if ((!Enumerable.SequenceEqual(tempLogRowList.ValueList, previousLineLogRowList.ValueList)) &&
(!Enumerable.SequenceEqual(tempLogRowList.ValueList, logHeaderRowList.ValueList)))
{
logRowList = getRow(fullFilename, false);
listMSDataRows.Add(logRowList);
previousLineLogRowList = logRowList;
}
}
//System.Threading.Thread.Sleep(10); //Wait for next row.
}
});
}
private static LogRowData getRow(string fullFilename, bool isHeader)
{
string line;
string[] logDataArray = new string[] { };
LogRowData logRowListResult = new LogRowData();
try
{
if (isHeader)
{
//Asign first (header) row data.
//Works but seems to block writting to the file!!!!!!!!!!!!!!!!!!!!!!!!!!!
line = File.ReadLines(fullFilename).First().Replace("\"", "");
}
else
{
//Assign data as last row (default behaviour).
line = File.ReadLines(fullFilename).Last().Replace("\"", "");
}
logDataArray = line.Split(',');
//Copy Array to Generics List and remove last value if it's empty.
for (int i = 0; i < logDataArray.Length; i++)
{
if (i < logDataArray.Length)
{
if (i < logDataArray.Length - 1)
{
//Value is not at the end, from observation, these always have a value (even if it's zero) and so we'll store the value.
logRowListResult.ValueList.Add(logDataArray[i]);
}
else
{
//This is the last value
if (logDataArray[i].Replace("\"", "").Trim().Length > 0)
{
//In this case, the last value is not empty, store it as normal.
logRowListResult.ValueList.Add(logDataArray[i]);
}
else { /*The last value is empty, e.g. "123,456,"; the final comma denotes another field but this field is empty so we will ignore it now. */ }
}
}
}
}
catch (Exception ex)
{
if (ex.Message == "Sequence contains no elements")
{ /*Empty file, no problem. The code will safely loop and then will pick up the header when it appears.*/ }
else
{
//TODO: catch this error properly
Int32 problemID = 10; //Unknown ERROR.
}
}
return logRowListResult;
}
}
}
I found the answer in a combination of other questions. One answer explaining how to read from the end of a file, which I adapted so that it would read only 1 line from the end of the file. And another explaining how to read the entire file without locking it (I did not want to read the entire file but the not locking part was useful). So now you can read the last line of the file (if it contains end of line characters) without locking it. For other end of line delimeters, just replace my 10 and 13 with your end of line character bytes...
Add the method below to public class CopyCSVDataFromLogFile
private static string Reverse(string str)
{
char[] arr = new char[str.Length];
for (int i = 0; i < str.Length; i++)
arr[i] = str[str.Length - 1 - i];
return new string(arr);
}
and replace this line ...
line = File.ReadLines(fullFilename).Last().Replace("\"", "");
with this code block ...
Int32 endOfLineCharacterCount = 0;
Int32 previousCharByte = 0;
Int32 currentCharByte = 0;
//Read the file, from the end, for 1 line, allowing other programmes to access it for read and write!
using (FileStream reader = new FileStream(fullFilename, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, 0x1000, FileOptions.SequentialScan))
{
int i = 0;
StringBuilder lineBuffer = new StringBuilder();
int byteRead;
while ((-i < reader.Length) /*Belt and braces: if there were no end of line characters, reading beyond the file would give a catastrophic error here (to be avoided thus).*/
&& (endOfLineCharacterCount < 2)/*Exit Condition*/)
{
reader.Seek(--i, SeekOrigin.End);
byteRead = reader.ReadByte();
currentCharByte = byteRead;
//Exit condition: the first 2 characters we read (reading backwards remember) were end of line ().
//So when we read the second end of line, we have read 1 whole line (the last line in the file)
//and we must exit now.
if (currentCharByte == 13 && previousCharByte == 10)
{
endOfLineCharacterCount++;
}
if (byteRead == 10 && lineBuffer.Length > 0)
{
line += Reverse(lineBuffer.ToString());
lineBuffer.Remove(0, lineBuffer.Length);
}
lineBuffer.Append((char)byteRead);
previousCharByte = byteRead;
}
reader.Close();
}

File upload on Parse.com isn't working

I have been developing a game in Unity3D in C#. So I have set the game to upload the game save file
to Parse every 2 minutes. When I run the code in Unity it works fine, it saves the game save file locally, and every 2 minutes it uploads it to Parse, it also points to the user who's playing the game. Although when I run the game on a mobile device the file does not upload, I have been refreshing my parse data object in the last half an hour and I still haven't got anything.
Here is the code:
void Start ()
{
if (ParseUser.CurrentUser != null)
{
gapTest = true;
}
}
void Update ()
{
if(gapTest)
{
StartCoroutine(UploadFile());
}
if (uploadSaveFile)
{
OnZipComplete();
uploadSaveFile = false;
}
else if (createNewSaveFile)
{
CreateNewSaveFile();
createNewSaveFile = false;
}
}
IEnumerator UploadFile()
{
gapTest = false;
yield return new WaitForSeconds (120.0F);
if(ParseUser.CurrentUser != null)
{
OnZipComplete();
}
gapTest = true;
}
void OnZipComplete()
{
var query = ParseObject.GetQuery("GameSave").WhereEqualTo("UserObjectId", ParseUser.CurrentUser);
if (existingGameSave == null)
{
query.FindAsync().ContinueWith(t =>
{
IEnumerable<ParseObject> results = t.Result;
int resultCount = 0;
foreach (var result in results)
{
if (resultCount > 0)
{
Debug.LogError("Found more than one save file for user!");
}
else
{
resultCount++;
existingGameSave = result;
uploadSaveFile = true;
}
}
if (resultCount == 0)
{
createNewSaveFile = true;
}
});
}
else
{
UpdateExistingSaveFile();
}
}
private void CreateNewSaveFile()
{
//upload the file to parse
zipPath = Application.persistentDataPath + "/SaveGame.zip";
byte[] data = System.Text.Encoding.UTF8.GetBytes(zipPath);
ParseFile GameSave = new ParseFile("GameSave.zip", data);
var gameSave = new ParseObject("GameSave");
gameSave["UserObjectId"] = ParseUser.CurrentUser;
gameSave["GameSaveFile"] = GameSave;
Task saveTask = gameSave.SaveAsync();
Debug.Log("New Game save file has been uploaded");
}
void UpdateExistingSaveFile()
{
//upload the file to parse
UserIdFile = Application.persistentDataPath + "/UserId.txt";
zipPath = Application.persistentDataPath + "/SaveGame.zip";
byte[] data = System.Text.Encoding.UTF8.GetBytes(zipPath);
ParseFile GameSave = new ParseFile("GameSave.zip", data);
existingGameSave["GameSaveFile"] = GameSave;
Task saveTask = existingGameSave.SaveAsync();
Debug.Log("Existing Game save file has been uploaded");
}
This might be a problem related to permission on android device.
Go to Build Setting > Player Setting > Other Settings, and check "Write Access", by default, it is internal Only which means that the path is for development purpose only.
Try Setting the Write Access as External(SD Card) or add WRITE_ EXTERNAL_STORAGE permission into AndroidManifest.xml. And check Android/file/com.your.appid/files of your sd card if your data is actually getting written on device.
If data is getting written, then it will sure get uploaded on parse.com.

iterate through and infoPath form

I have a infopath form that may contain multiple attachments: By using a group of repeating elements, the user can click an “click add item” option he will be able to upload more attachments.
In Sharepoint I am using a workflow to extract the attachments and put them in a separate list. So far I manage only to extract the first one and the workflow finishes successfully.
can I put a loop or something to iterate trough the form?
I attach the code below:
public sealed partial class FileCopyFeature : SharePointSequentialWorkflowActivity
{
public FileCopyFeature()
{
InitializeComponent();
}
public Guid workflowId = default(System.Guid);
public Microsoft.SharePoint.Workflow.SPWorkflowActivationProperties workflowProperties = new Microsoft.SharePoint.Workflow.SPWorkflowActivationProperties();
private void CopyFile(object sender, EventArgs e)
{
// Retrieve the file associated with the item
// on which the workflow has been instantiated
SPFile file = workflowProperties.Item.File;
if (file == null)
return;
// Get the binary data of the file
byte[] xmlFormData = null;
xmlFormData = file.OpenBinary();
// Load the data into an XPathDocument object
XPathDocument ipForm = null;
if (xmlFormData != null)
{
using (MemoryStream ms = new MemoryStream(xmlFormData))
{
ipForm = new XPathDocument(ms);
ms.Close();
}
}
if (ipForm == null)
return;
// Create an XPathNavigator object to navigate the XML
XPathNavigator ipFormNav = ipForm.CreateNavigator();
ipFormNav.MoveToFollowing(XPathNodeType.Element);
XmlNamespaceManager nsManager =
new XmlNamespaceManager(new NameTable());
foreach (KeyValuePair<string, string> ns
in ipFormNav.GetNamespacesInScope(XmlNamespaceScope.All))
{
if (ns.Key == String.Empty)
{
nsManager.AddNamespace("def", ns.Value);
}
else
{
nsManager.AddNamespace(ns.Key, ns.Value);
}
}
do
{
XPathNavigator nodeNav = ipFormNav.SelectSingleNode("//my:field2", nsManager);
// Retrieve the value of the attachment in the InfoPath form
//XPathNavigator nodeNav = ipFormNav.SelectSingleNode(
//"//my:field2", nsManager);
string ipFieldValue = string.Empty;
if (nodeNav != null)
{
ipFieldValue = nodeNav.Value;
// Decode the InfoPath file attachment
InfoPathAttachmentDecoder dec =
new InfoPathAttachmentDecoder(ipFieldValue);
string fileName = dec.Filename;
byte[] data = dec.DecodedAttachment;
// Add the file to a document library
using (SPWeb web = workflowProperties.Web)
{
SPFolder docLib = web.Folders["Doc"];
docLib.Files.Add(fileName, data);
docLib.Update();
// workflowProperties.Item.CopyTo(data + "/Doc/" + fileName);
}
}
}
while (ipFormNav.MoveToNext());
}
}
/// <summary>
/// Decodes a file attachment and saves it to a specified path.
/// </summary>
public class InfoPathAttachmentDecoder
{
private const int SP1Header_Size = 20;
private const int FIXED_HEADER = 16;
private int fileSize;
private int attachmentNameLength;
private string attachmentName;
private byte[] decodedAttachment;
/// <summary>
/// Accepts the Base64 encoded string
/// that is the attachment.
/// </summary>
public InfoPathAttachmentDecoder(string theBase64EncodedString)
{
byte[] theData = Convert.FromBase64String(theBase64EncodedString);
using (MemoryStream ms = new MemoryStream(theData))
{
BinaryReader theReader = new BinaryReader(ms);
DecodeAttachment(theReader);
}
}
private void DecodeAttachment(BinaryReader theReader)
{
//Position the reader to get the file size.
byte[] headerData = new byte[FIXED_HEADER];
headerData = theReader.ReadBytes(headerData.Length);
fileSize = (int)theReader.ReadUInt32();
attachmentNameLength = (int)theReader.ReadUInt32() * 2;
byte[] fileNameBytes = theReader.ReadBytes(attachmentNameLength);
//InfoPath uses UTF8 encoding.
Encoding enc = Encoding.Unicode;
attachmentName = enc.GetString(fileNameBytes, 0, attachmentNameLength - 2);
decodedAttachment = theReader.ReadBytes(fileSize);
}
public void SaveAttachment(string saveLocation)
{
string fullFileName = saveLocation;
if (!fullFileName.EndsWith(Path.DirectorySeparatorChar.ToString()))
{
fullFileName += Path.DirectorySeparatorChar;
}
fullFileName += attachmentName;
if (File.Exists(fullFileName))
File.Delete(fullFileName);
FileStream fs = new FileStream(fullFileName, FileMode.CreateNew);
BinaryWriter bw = new BinaryWriter(fs);
bw.Write(decodedAttachment);
bw.Close();
fs.Close();
}
public string Filename
{
get { return attachmentName; }
}
public byte[] DecodedAttachment
{
get { return decodedAttachment; }
}
It appears that your issue has to do with your use of MoveToNext. Per the documentation, this function moves to the next sibling, and does not navigate to children elements. Your code appears to go to the first element it finds (presumably my:myFields), looks for the first child named my:field2 (it only pulls the first one since you are using SelectSingleNode, and then goes to the next sibling of my:myFields (not the next sibling of my:field2). One way to fix this might be to replace your current do-while loop with a call to SelectNodes like the following and then iterate over nodeList.
XmlNodeList nodelist = ipFormNav.SelectNodes("//my:field2", nsManager);

Categories

Resources