I'm trying to edit an xml and then save it with the same name.
I have the following code:
public int ModifyFile(string xmlpath, string option, int returnCode)
{
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.Load(xmlpath);
XmlNode parentNode = xmlDoc.DocumentElement;
if (option.Equals("delete"))
{
returnCode = DeleteTag(parentNode, "identity", returnCode);
}
xmlDoc.Save(xmlpath);
return returnCode;
}
public int DeleteTag(XmlNode root, string deleteName, int returnCode)
{
foreach (XmlNode node in root.ChildNodes)
{
if (node.Name == deleteName)
{
root.RemoveChild(node);
returnCode = 1;
}
else
{
returnCode = DeleteTag(node, deleteName, returnCode);
}
}
return returnCode;
}
I'm getting "The process cannot access the file 'c:\temp\testfile.xml' because it is being used by another process" when it executes xmlDoc.Save(path).
How would I be able to save testfile.xml with the changes made? I need to keep the path and name the same.
public static bool hasIdentityTag(string path)
{
bool isTextPresent = false;
if (File.Exists(path))
{
XmlTextReader rdrXml = new XmlTextReader(path);
do
{
switch (rdrXml.NodeType)
{
case XmlNodeType.Element:
if (rdrXml.Name.Equals("identity"))
{
isTextPresent = true;
rdrXml.Close();
}
break;
}
} while (rdrXml.Read());
}
else
{
Console.WriteLine("The file {0} could not be located", path);
}
return isTextPresent;
}
One option would be to save the new XML to a temporary file, close the XmlDocument and dispose of the object, then move the temporary file back to the right place.
You could try this re-write using LinqToXml:
XElement root = XElement.Load(xmlpath);
bool modified = false;
try
{
switch(option)
{
case "delete":
var toDelete = root.Descendants("identity").ToArray();
foreach(XElement x in toDelete)
{
x.Remove();
modified = true;
returnCode = 1;
}
break;
}
}
finally
{
if(modified)
root.Save(xmlpath);
}
return returnCode;
How about loading the XmlDocument from a Stream instead of by file name? If you still encounter errors, this would indicate that something outside of your method/process is blocking the save.
Try rewriting theModifyFile method like this:
public int ModifyFile(string xmlpath, string option, int returnCode)
{
var fs = File.Open(xmlpath);
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.Load(fs); // use the stream, not file name
fs.Close(); // now close the stream... file should not be locked from this point
XmlNode parentNode = xmlDoc.DocumentElement;
if (option.Equals("delete"))
{
returnCode = DeleteTag(parentNode, "identity", returnCode);
}
xmlDoc.Save(path);
return returnCode;
}
Related
I have followed the code shown on this site: "https://www.veonconsulting.com/integrating-sap-using-nco/",
I'm trying to get data from the function BAPI_PRODORD_GET_DETAIL, I tested <bapi_prodord_get_detail><number>1000262</number><struct><order_objects><header>X</header><operations>X</operations><components>X</components></order_objects></struct></bapi_prodord_get_detail>, on SAP GUI, I can see data for Header, Components & Operations, but in the code, I 'm not getting the same response, could you please help.
Code:
public bool testConnection()
{
bool state = false;
string rfcRequest = "<RFC_READ_TABLE><QUERY_TABLE>MARD</QUERY_TABLE><DELIMITER>*"
+ "</DELIMITER><ROWSKIPS>0</ROWSKIPS><ROWCOUNT>0</ROWCOUNT><TABLE><OPTIONS><ROW>"
+ "<TEXT>MATNR IN (</TEXT></ROW><ROW><TEXT>'testConnection'</TEXT></ROW><ROW>"
+ "<TEXT>)</TEXT></ROW></OPTIONS></TABLE></RFC_READ_TABLE>";
Utils.RfcClient client = new Utils.RfcClient();
try
{
XElement response = client.PullRequestToSAPrfc(rfcRequest);
state = true;
}
catch (RfcLogonException ex)
{
Console.Write("Logon Failed");
}
catch (RfcInvalidStateException ex)
{
Console.Write("RFC Failed");
}
catch (RfcBaseException ex)
{
Console.WriteLine("communication error" + ex.Message);
}
catch (Exception ex)
{
Console.Write("Connection error");
}
finally
{
//client.disconnectDestination();
}
return state;
}
public bool testConnection()
{
bool state = false;
string rfcRequest = "<bapi_prodord_get_detail><number>1000262</number><struct>"
+ "<order_objects><header>X</header><operations>X</operations><components>X"
+ "</components></order_objects></struct></bapi_prodord_get_detail>";
Utils.RfcClient client = new Utils.RfcClient();
try
{
XElement response = client.PullRequestToSAPrfc(rfcRequest);
state = true;
}
catch (RfcLogonException ex)
{
Console.Write("Logon Failed");
}
catch (RfcInvalidStateException ex)
{
Console.Write("RFC Failed");
}
catch (RfcBaseException ex)
{
Console.WriteLine("communication error" + ex.Message);
}
catch (Exception ex)
{
Console.Write("Connection error");
}
finally
{
//client.disconnectDestination();
}
return state;
}
public XElement PullRequestToSAPrfc(string XMLRequest)
{
IRfcFunction requestFn;
requestFn = PrepareRfcFunctionFromXML(XElement.Parse(XMLRequest));
RfcSessionManager.BeginContext(_ECCsystem);
requestFn.Invoke(_ECCsystem);
RfcSessionManager.EndContext(_ECCsystem);
XElement XMLResponse = PrepareXMLFromrfc(requestFn);
return XMLResponse;
}
public IRfcFunction PrepareRfcFunctionFromXML(XElement xmlFunction)
{
RfcRepository repo = _ECCsystem.Repository;
IRfcFunction RfcFunction = repo.CreateFunction(xmlFunction.Name.ToString());
foreach (XElement xelement in xmlFunction.Elements())
{
if (xelement.Name.ToString().Equals("TABLE"))
{
if (NotProcessSpecialTable(xelement))
continue;
IRfcTable options = RfcFunction.GetTable(xelement.Descendants().First().Name.ToString());
foreach (XElement row in xelement.Elements().First().Elements())
{
options.Append();
foreach (XElement rowElement in row.Elements())
{
string elementName = rowElement.Name.ToString();
RfcElementMetadata elementMeta = options.GetElementMetadata(elementName);
var elementValue = getValueAsMetadata(ref elementMeta, rowElement.Value);
if (elementValue is string && string.IsNullOrEmpty((string)elementValue)) { continue; }
options.SetValue(elementName, elementValue);
}
}
}
else if (xelement.Name.ToString().Equals("STRUCT"))
{
IRfcStructure options = RfcFunction.GetStructure(xelement.Descendants().First().Name.ToString());
foreach (XElement structElement in xelement.Elements().First().Elements())
{
string elementName = structElement.Name.ToString();
RfcElementMetadata elementMeta = options.GetElementMetadata(elementName);
var elementValue = getValueAsMetadata(ref elementMeta, structElement.Value);
if (elementValue is string && string.IsNullOrEmpty((string)elementValue)) { continue; }
options.SetValue(elementName, elementValue);
}
}
else
{
string elementName = xelement.Name.ToString();
RfcElementMetadata elementMeta = RfcFunction.GetElementMetadata(elementName);
var elementValue = getValueAsMetadata(ref elementMeta, xelement.Value);
if (elementValue is string && string.IsNullOrEmpty((string)elementValue)) { continue; }
RfcFunction.SetValue(elementName, elementValue);
}
}
return RfcFunction;
}
public XElement PrepareXMLFromrfc(IRfcFunction rfcFunction)
{
var XMLRoot = new XElement(rfcFunction.Metadata.Name);
for (int functionIndex = 0; functionIndex < rfcFunction.ElementCount; functionIndex++)
{
var functionMatadata = rfcFunction.GetElementMetadata(functionIndex);
if (functionMatadata.DataType == RfcDataType.TABLE)
{
var rfcTable = rfcFunction.GetTable(functionMatadata.Name);
var XMLTable = new XElement(functionMatadata.Name);
foreach (IRfcStructure rfcStracture in rfcTable)
{
XElement XMLRow = new XElement("ROW");
for (int i = 0; i < rfcStracture.ElementCount; i++)
{
RfcElementMetadata rfcElementMetadata = rfcStracture.GetElementMetadata(i);
if (rfcElementMetadata.DataType == RfcDataType.BCD)
{ XMLRow.Add(new XElement(rfcElementMetadata.Name, rfcStracture.GetString(rfcElementMetadata.Name))); }
else
{
XMLRow.Add(new XElement(rfcElementMetadata.Name, rfcStracture.GetString(rfcElementMetadata.Name)));
}
}
XMLTable.Add(XMLRow);
}
XMLRoot.Add(XMLTable);
}
else if (functionMatadata.DataType == RfcDataType.STRUCTURE)
{
var rfcStructure = rfcFunction.GetStructure(functionMatadata.Name);
XElement XMLRow = new XElement(functionMatadata.Name);
for (int elementIndex = 0; elementIndex < rfcStructure.ElementCount; elementIndex++)
{
RfcElementMetadata eleMeta = rfcStructure.GetElementMetadata(elementIndex);
XMLRow.Add(new XElement(eleMeta.Name, rfcStructure.GetString(eleMeta.Name)));
}
XMLRoot.Add(XMLRow);
}
else
{
RfcElementMetadata rfcElement = rfcFunction.GetElementMetadata(functionIndex);
XMLRoot.Add(new XElement(rfcElement.Name, rfcFunction.GetString(rfcElement.Name)));
}
}
return XMLRoot;
}
# Below function is used for the data types.
private object getValueAsMetadata(ref RfcElementMetadata elementMeta, string value)
{
switch (elementMeta.DataType)
{
case RfcDataType.BCD:
return value;
case RfcDataType.NUM:
if (value.Contains("."))
{
int elementValue;
int.TryParse(value, out elementValue);
return elementValue;
}
else
{
return Convert.ToInt32(value);
}
case RfcDataType.INT1:
return Convert.ToInt32(value);
case RfcDataType.INT2:
return Convert.ToInt32(value);
case RfcDataType.INT4:
return Convert.ToInt32(value);
case RfcDataType.INT8:
return Convert.ToInt64(value);
case RfcDataType.CHAR:
return value;
case RfcDataType.DATE:
return DateTime.ParseExact(value, "yyyy-MM-dd", CultureInfo.InvariantCulture);
default:
return string.Empty;
}
}
You are confronted to the classic issue of external and internal values in SAP.
Your screenshot shows the ABAP Function Module Test screen in SAP system. When you enter a value in the screen, it may be transformed internally before calling the function module.
These are called the external and internal formats. "External" is what is shown in the User Interface (typed or displayed), "internal" is the value written to the database.
For instance, imagine a database object whose primary key is a GUID, this is the object key in internal format, but in the user interface this object is always referred or shown by its name (candidate key).
In your precise case, when a Production Order is a number, the internal format always contains leading zeroes on 12 digits, and the external format does not display them. In the Function Module Test screen, if you enter this external value:
1000262
it's converted to the following internal value and the BAPI is called with it:
000001000262
Generally speaking, when you call any function module from another program, you must indicate the internal value, because there's no user interface implied between the two.
i.e., use this XML in your method testConnection:
string rfcRequest = "<bapi_prodord_get_detail><number>000001000262</number><struct>"
+ "<order_objects><header>X</header><operations>X</operations><components>X"
+ "</components></order_objects></struct></bapi_prodord_get_detail>";
See also this answer about external and internal formats: Converting MATNR via conversion exit fails for custom table
If you would like to do the required field conversions programmatically, which are explained in Sandra Rossi's answer, you may use the RFMs BAPI_CONVERSION_EXT2INT, BAPI_CONVERSION_EXT2INT1, BAPI_CONVERSION_INT2EXT and BAPI_CONVERSION_INT2EXT1 for doing so.
However, every additional RFC call has of course a negative impact on the performance.
Besides, SAP Note 206068 is a good resource for an explanation of some RFC BAPI pitfalls which you also stepped in.
As the title says, is it possible to optimize the following code by using yield, is it worth and if so, how?
public static void LoadSettings(string fileName)
{
try
{
var xml = new XmlDocument();
xml.Load(fileName);
var userNodes = xml.SelectNodes("/settings");
foreach (XmlNode node in userNodes)
{
globals.username = node.SelectSingleNode("username").InnerText;
globals.password = node.SelectSingleNode("password").InnerText;
globals.rank = node.SelectSingleNode("rank").InnerText;
}
}
catch
{
Console.WriteLine("Oops, something is wrong.");
}
}
Edit: Thanks for answers guys!
No, you cannot. yield return can only implemented for methods actually returning something. You don't have a return value, so the answer is no.
As an example, this could be an application of yield return, but that would change the meaning of your method:
public static IEnumerable<Setting> LoadSettings(string fileName)
{
try
{
var xml = new XmlDocument();
xml.Load(fileName);
var userNodes = xml.SelectNodes("/settings");
foreach (XmlNode node in userNodes)
{
Setting globals = new Setting();
globals.username = node.SelectSingleNode("username").InnerText;
globals.password = node.SelectSingleNode("password").InnerText;
globals.rank = node.SelectSingleNode("rank").InnerText;
yield return globals;
}
}
catch
{
Console.WriteLine("Oops, something is wrong.");
}
}
I am trying to merge multiple XML files in into one using XmlReader and XmlWriter though my final file only contains the data from the last file.
I am using XmlReader and XmlWriter because the XML files to merge are large in size.
What am I doing wrong in the code below?
class Program
{
static void Main(string[] args)
{
string folder = #"C:\Temp\";
string output = folder + "_all.xml";
Encoding readEncoding = System.Text.Encoding.Default;
XmlWriterSettings writerSettings = new XmlWriterSettings();
writerSettings.Encoding = Encoding.UTF8;
writerSettings.ConformanceLevel = ConformanceLevel.Fragment;
XmlWriter writer = XmlWriter.Create(new StreamWriter(output, false), writerSettings);
bool firstFile = true;
foreach (FileInfo file in new DirectoryInfo(folder).GetFiles("*.xml").Where(f => f.Name != "_all.xml"))
{
XmlReader reader = XmlReader.Create(new StreamReader(file.FullName, readEncoding));
while(reader.Read())
{
switch (reader.NodeType)
{
case XmlNodeType.Element:
if (firstFile && reader.Name == "CYPHS:CYPHS")
{
writer.WriteStartElement(reader.Prefix, reader.LocalName, reader.NamespaceURI);
writer.WriteAttributes(reader, true);
}
else if (firstFile && reader.Name == "CYP000")
writer.WriteStartElement(reader.Name);
else if (firstFile && reader.Name.StartsWith("C000"))
writer.WriteNode(reader, false);
else if (!firstFile && reader.Name != "CYPHS:CYPHS" && reader.Name != "CYP000" && !reader.Name.StartsWith("C000"))
writer.WriteNode(reader, false);
break;
default:
break;
}
}
firstFile = false;
reader.Close();
}
writer.WriteEndElement();
writer.WriteEndElement();
writer.Close();
Console.WriteLine("Done!");
Console.ReadLine();
}
}
File 1
<CYPHS:CYPHS xsi:schemaLocation="http://www.datadictionary.nhs.uk/messages/CYPHS-v1-5 CYPHSCYPHS_XMLSchema-v1-5.xsd"
xmlns:CYPHS="http://www.datadictionary.nhs.uk/messages/CYPHS-v1-5"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<CYP000>
<C000010>File 1</C000010>
<CYP001>
<C001901>File 1</C001901>
<CYP101>
<C101902>File 1</C101902>
<CYP102>
<C102902>File 1</C102902>
</CYP102>
</CYP101>
<CYP002>
<C002901>File 1</C002901>
</CYP002>
</CYP001>
</CYP000>
</CYPHS:CYPHS>
File 2
<CYPHS:CYPHS xsi:schemaLocation="http://www.datadictionary.nhs.uk/messages/CYPHS-v1-5 CYPHSCYPHS_XMLSchema-v1-5.xsd"
xmlns:CYPHS="http://www.datadictionary.nhs.uk/messages/CYPHS-v1-5"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<CYP000>
<C000010>File 2</C000010>
<CYP001>
<C001901>File 2</C001901>
<CYP101>
<C101902>File 2</C101902>
<CYP102>
<C102902>File 2</C102902>
</CYP102>
</CYP101>
<CYP002>
<C002901>File 2</C002901>
</CYP002>
</CYP001>
</CYP000>
</CYPHS:CYPHS>
Should be merged into file as so:
<CYPHS:CYPHS xsi:schemaLocation="http://www.datadictionary.nhs.uk/messages/CYPHS-v1-5 CYPHSCYPHS_XMLSchema-v1-5.xsd"
xmlns:CYPHS="http://www.datadictionary.nhs.uk/messages/CYPHS-v1-5"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<CYP000>
<C000010>File 1</C000010>
<CYP001>
<C001901>File 1</C001901>
<CYP101>
<C101902>File 1</C101902>
<CYP102>
<C102902>File 1</C102902>
</CYP102>
</CYP101>
<CYP002>
<C002901>File 1</C002901>
</CYP002>
</CYP001>
<CYP001>
<C001901>File 2</C001901>
<CYP101>
<C101902>File 2</C101902>
<CYP102>
<C102902>File 2</C102902>
</CYP102>
</CYP101>
<CYP002>
<C002901>File 2</C002901>
</CYP002>
</CYP001>
</CYP000>
</CYPHS:CYPHS>
Like This
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Linq;
namespace ConsoleApplication53
{
class Program
{
static void Main(string[] args)
{
string file1 =
"<CYPHS:CYPHS xsi:schemaLocation=\"http://www.datadictionary.nhs.uk/messages/CYPHS-v1-5 CYPHSCYPHS_XMLSchema-v1-5.xsd\"" +
" xmlns:CYPHS=\"http://www.datadictionary.nhs.uk/messages/CYPHS-v1-5\"" +
" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">" +
"<CYP000>" +
"<C000010>File 1</C000010>" +
"<CYP001>" +
"<C001901>File 1</C001901>" +
"<CYP101>" +
"<C101902>File 1</C101902>" +
"<CYP102>" +
"<C102902>File 1</C102902>" +
"</CYP102>" +
"</CYP101>" +
"<CYP002>" +
"<C002901>File 1</C002901>" +
"</CYP002>" +
"</CYP001>" +
"</CYP000>" +
"</CYPHS:CYPHS>";
XDocument doc1 = XDocument.Parse(file1);
XElement doc1_CYP000 = doc1.Descendants("CYP000").FirstOrDefault();
string file2 =
"<CYPHS:CYPHS xsi:schemaLocation=\"http://www.datadictionary.nhs.uk/messages/CYPHS-v1-5 CYPHSCYPHS_XMLSchema-v1-5.xsd\"" +
" xmlns:CYPHS=\"http://www.datadictionary.nhs.uk/messages/CYPHS-v1-5\"" +
" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">" +
"<CYP000>" +
"<C000010>File 2</C000010>" +
"<CYP001>" +
"<C001901>File 2</C001901>" +
"<CYP101>" +
"<C101902>File 2</C101902>" +
"<CYP102>" +
"<C102902>File 2</C102902>" +
"</CYP102>" +
"</CYP101>" +
"<CYP002>" +
"<C002901>File 2</C002901>" +
"</CYP002>" +
"</CYP001>" +
"</CYP000>" +
"</CYPHS:CYPHS>";
XDocument doc2 = XDocument.Parse(file2);
XElement doc2_CYP000 = doc2.Descendants("CYP000").FirstOrDefault();
doc1_CYP000.Add(doc2_CYP000.Descendants());
}
}
}
I'm not entirely sure where you went wrong, but it seems most straightforward to check the Depth, LocalName and NamespaceURI properties of XmlReader when combining your XML files. I strongly recommend against hardcoding the namespace prefixes since the prefix can be replaced with any other prefix without changing the semantics of the XML file.
One thing to note: XmlWriter.WriteNode(XmlReader, bool) advances the reader to the beginning of the next node, so if you subsequently call Read() and there is no whitespace in the file you'll skip over the next element. With this in mind, when working directly with XmlReader, it's better to test both with and without spacing.
Thus:
public class XmlConcatenate
{
public static void ConcatenateAllFiles()
{
string folder = "C:\\Temp\\";
string output = folder + "_all.xml";
Encoding readEncoding = System.Text.Encoding.Default; // WHY NOT Encoding.UTF8 !?
var files = new DirectoryInfo(folder).GetFiles("*.xml").Where(f => f.Name != "_all.xml").Select(f => f.FullName).Select(n => (TextReader)new StreamReader(n, readEncoding));
using (var textWriter = new StreamWriter(output, false))
{
Concatenate(files, textWriter);
}
}
public static void Concatenate(IEnumerable<TextReader> inputs, TextWriter output)
{
var writerSettings = new XmlWriterSettings() { Encoding = Encoding.UTF8, ConformanceLevel = ConformanceLevel.Fragment };
var whiteSpace = new StringBuilder();
int indent = 0;
using (var writer = XmlWriter.Create(output, writerSettings))
{
var writeDepth = 0;
var first = true;
foreach (var input in inputs)
{
using (input)
using (var reader = XmlReader.Create(input))
{
bool alreadyRead = false;
while (!reader.EOF && (alreadyRead || reader.Read()))
{
alreadyRead = false;
switch (reader.NodeType)
{
case XmlNodeType.Element:
{
if (reader.Depth == 0 && reader.LocalName == "CYPHS" && reader.NamespaceURI == "http://www.datadictionary.nhs.uk/messages/CYPHS-v1-5")
{
if (writeDepth == 0)
{
writer.WriteWhitespace(whiteSpace.ToString());
writer.WriteStartElement(reader.Prefix, reader.LocalName, reader.NamespaceURI);
writer.WriteAttributes(reader, true);
writeDepth++;
}
}
else if (reader.Depth == 1 && reader.LocalName == "CYP000" && reader.NamespaceURI == "")
{
if (writeDepth == 1)
{
indent = whiteSpace.ToString().Replace("\n", "").Replace("\r", "").Length;
writer.WriteWhitespace(whiteSpace.ToString());
writer.WriteStartElement(reader.LocalName, reader.NamespaceURI);
writeDepth++;
}
}
else if (reader.Depth == 2)
{
if (reader.LocalName.StartsWith("C000") && reader.NamespaceURI == "")
{
if (first)
{
first = false;
writer.WriteWhitespace(whiteSpace.ToString());
writer.WriteNode(reader, false);
alreadyRead = true;
}
}
else
{
writer.WriteWhitespace(whiteSpace.ToString());
writer.WriteNode(reader, false);
alreadyRead = true;
}
}
whiteSpace.Length = 0; // Clear accumulated whitespace.
}
break;
case XmlNodeType.Whitespace:
{
whiteSpace.Append(reader.Value);
}
break;
default:
break;
}
}
}
}
while (writeDepth-- > 0)
{
if (indent > 0)
writer.WriteWhitespace("\n" + new string(' ', indent * writeDepth));
writer.WriteEndElement();
}
}
}
}
Bit of a nuisance getting the spacing to merge, if you don't care about preserving the spacing you can simplify the code substantially.
Working fiddle.
You might not want to use System.Text.Encoding.Default for reading your XML files. From the docs:
Because all Default encodings lose data, you might use UTF8 instead. UTF-8 is often identical in the U+00 to U+7F range, but can encode other characters without loss.
A different solution could be to use a custom XmlReader-implementation to concat the files while reading them.
Then use this custom reader along with an XmlWriter to create the merged file.
The custom XmlReader keeps internal XmlReaders for each file.
The intro/end is only read from the first file.
Only the relevant (to-be-appended) elements are read from the other files.
create an XmlReader for the first file
read up to the point where elements should be appended
for each subsequent file
create a new XmlReader
skip ahead to the first relevant element
read the relevant elements
dispose the reader
read the rest of the first file (resume the reader from step 1)
dispose the reader
Example implementation
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Xml;
public static class XmlConcatenator
{
// first: pause reading at the end of this element, will resume after subsequent streams are read
// subsequent: stop reading at the end of this element
private const string StopAtEndOf = "CYP000";
// first: (ignores this)
// subsequent: skip ahead to the first instance of this element
private const string ResumeAtFirst = "CYP001";
private static readonly XmlReaderSettings XmlReaderSettings = new XmlReaderSettings() { DtdProcessing = DtdProcessing.Ignore };
private static readonly XmlWriterSettings XmlWriterSettings = new XmlWriterSettings() { Encoding = Encoding.UTF8, Indent = true };
public static void Concat(Stream outStream, Stream[] fileStreams)
{
using var reader = XmlConcatReader.Create(fileStreams);
using var writer = XmlWriter.Create(outStream, XmlWriterSettings);
writer.WriteNode(reader, true);
}
private class XmlConcatReader : XmlReader
{
private readonly XmlReader _firstReader;
private readonly IEnumerator<Stream> _streams;
private XmlReader _currentReader;
private XmlConcatReader(Stream first, IEnumerable<Stream> streams)
{
_firstReader = XmlReader.Create(first, XmlReaderSettings);
_streams = streams.GetEnumerator();
_currentReader = _firstReader;
}
public static XmlReader Create(Stream[] inputStreams)
{
if (!(inputStreams?.Length > 1))
{
throw new InvalidOperationException($"{nameof(inputStreams)} must contain at least two streams");
}
return new XmlConcatReader(inputStreams[0], inputStreams.Skip(1));
}
public override bool Read()
{
var b = _currentReader.Read();
if (_currentReader.NodeType == XmlNodeType.EndElement && _currentReader.LocalName == StopAtEndOf)
{
// note: _firstReader is disposed at the end. See: Dispose(bool)
if (!ReferenceEquals(_currentReader, _firstReader))
{
_currentReader.Dispose();
}
if (_streams.MoveNext())
{
_currentReader = XmlReader.Create(_streams.Current, XmlReaderSettings);
while (_currentReader.Read())
{
if (_currentReader.LocalName == ResumeAtFirst)
{
return true;
}
}
}
else
{
_currentReader = _firstReader;
return true;
}
}
return b;
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
_firstReader?.Dispose();
}
base.Dispose(disposing);
}
public override XmlNodeType NodeType => _currentReader.NodeType;
public override string LocalName => _currentReader.LocalName;
public override string NamespaceURI => _currentReader.NamespaceURI;
public override string Prefix => _currentReader.Prefix;
public override string Value => _currentReader.Value;
public override int Depth => _currentReader.Depth;
public override string BaseURI => _currentReader.BaseURI;
public override bool IsEmptyElement => _currentReader.IsEmptyElement;
public override int AttributeCount => _currentReader.AttributeCount;
public override bool EOF => _currentReader.EOF;
public override ReadState ReadState => _currentReader.ReadState;
public override XmlNameTable NameTable => _currentReader.NameTable;
public override string GetAttribute(string name) => _currentReader.GetAttribute(name);
public override string GetAttribute(string name, string namespaceURI) => _currentReader.GetAttribute(name, namespaceURI);
public override string GetAttribute(int i) => _currentReader.GetAttribute(i);
public override string LookupNamespace(string prefix) => _currentReader.LookupNamespace(prefix);
public override bool MoveToAttribute(string name) => _currentReader.MoveToAttribute(name);
public override bool MoveToAttribute(string name, string ns) => _currentReader.MoveToAttribute(name, ns);
public override bool MoveToElement() => _currentReader.MoveToElement();
public override bool MoveToFirstAttribute() => _currentReader.MoveToFirstAttribute();
public override bool MoveToNextAttribute() => _currentReader.MoveToNextAttribute();
public override bool ReadAttributeValue() => _currentReader.ReadAttributeValue();
public override void ResolveEntity() => _currentReader.ResolveEntity();
}
}
Example of use
using System.IO;
using System.Linq;
internal static class Program
{
private static void Main()
{
var input = new[] { "in1.xml", "in2.xml" };
var output = "output.xml";
var inputStreams = input.Select(p => File.Open(p, FileMode.Open)).ToArray();
using var outputStream = File.Create(output);
XmlConcatenator.Concat(outputStream, inputStreams);
foreach (var stream in inputStreams)
{
stream.Dispose();
}
}
}
I am using a bool flag to check to make sure XML Elements exist before writing them to a variable.
The problem is that I seem to be getting a false negative. I know the element exits because I can see it in the XML sample. However, the flag is still being set to false...
Code Example:
bool flag;
flag = xmlReader.ReadToFollowing("statusCode");
if(flag)
{
statusCode = xmlReader.ReadElementContentAsInt();
}
else
{
statusCode = 333;
}
flag = xmlReader.ReadToFollowing("statusDesc");
if (flag)
{
statusDesc = xmlReader.ReadElementContentAsString();
}
else
{
statusDesc = "";
}
flag = xmlReader.ReadToFollowing("Guid");
if (flag)
{
guid = xmlReader.ReadElementContentAsString();
}
else
{
guid = "";
}
XML Example:
<statusCode>0</statusCode>
<statusDesc/>
<Status/>
<WSKey/>
<Priority/>
<Guid>3A336A97-BCA3-43F8-849C-A40D129B25AA</Guid>
statusCode resolves as true, statusDesc resolves as false, but Guid also resolves as false.
Any ideas?
You may want to consider using LINQ to XML.
The following code snippet worked in my test:
System.Xml.Linq.XDocument temp = System.Xml.Linq.XDocument.Parse("<root><statusCode>0</statusCode><statusDesc/><Status/><WSKey/><Priority/><Guid>3A336A97-BCA3-43F8-849C-A40D129B25AA</Guid></root>");
var t = temp.Descendants("Guid").Any();
Regards,
I got the same result with you. here is the code
class Program {
static void Main(string[] args) {
var s = "<?xml version=\"1.0\" encoding=\"utf-8\"?>" +
"<root>" +
"<statusCode>0</statusCode>" +
"<statusDesc/>" +
"<Status/>" +
"<WSKey/>" +
"<Priority/>" +
"<Guid>3A336A97-BCA3-43F8-849C-A40D129B25AA</Guid>" +
"</root>";
var xmlReader = new XmlTextReader(
new MemoryStream(
Encoding.ASCII.GetBytes(s), false));
bool flag;
Int32 statusCode;
String statusDesc;
String guid;
flag = xmlReader.ReadToFollowing("statusCode");
if (flag) {
statusCode = xmlReader.ReadElementContentAsInt();
} else {
statusCode = 333;
}
flag = xmlReader.ReadToFollowing("statusDesc");
if (flag) {
statusDesc = xmlReader.ReadElementContentAsString();
} else {
statusDesc = "";
}
flag = xmlReader.ReadToFollowing("Guid");
if (flag) {
guid = xmlReader.ReadElementContentAsString();
} else {
guid = "";
}
}
}
it seems that the method ReadToFollowing will return false if the element has empty value (e.g. statusDesc). Put a break point at line "flag = xmlReader.ReadToFollowing("statusDesc");" and run the program to that line, if you have a look at the variable xmlReader, its property 'EOF' has been set true. That means it has read to the end of xml. This will explain why it can't find the Guid value in the next call to ReadToFollowing.
Is there a way to fix the error "The process cannot access the file..etc". The flow is that the filesystemwatcher will watch for a xml file when I detects a xml file i need to read a specific node from the xml file.
How can I fix this? Any ideas or suggestions will be a big help. Thanks
Here is the filesystemwatcher code
private void fileSystemWatcher_Created(object sender, System.IO.FileSystemEventArgs e)
{
try
{
string type = GetType(e.FullPath).ToUpper();
if (type == "CC")
{
if (Global.pc_flag)
{
ProcessPC(e.FullPath);
}
else if (Global.mw_flag)
{
ProcessMW(e.FullPath);
}
else
{
ProcessXML(e.FullPath);
}
}
else if (type == "GC")
{
ProcessMW(e.FullPath);
}
//Process(e.FullPath);
}
catch(Exception ex)
{
error++;
lblErrors.Text = error.ToString();
MessageBox.Show(ex.Message);
}
}
Here what contains of GetType
private string GetType(string file)
{
string type = string.Empty;
using (var stream = File.Open(file, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
{
var request = XDocument.Load(stream);
var get_command = from r in request.Descendants("Transaction")
select new
{
Type = r.Element("Type").Value
};
foreach (var c in get_command)
{
type = c.Type;
}
}
return type;
}
You don't use your stream in code and while the stream is open you can not access the file in XDocument.Load(file)
private string GetType(string file)
{
string type = string.Empty;
var request = XDocument.Load(file);
var get_command = from r in request.Descendants("Transaction")
select new
{
Type = r.Element("Type").Value
};
foreach (var c in get_command)
{
type = c.Type;
}
return type;
}