I've written a small utility that allows me to change a simple AppSetting for another application's App.config file, and then save the changes:
//save a backup copy first.
var cfg = ConfigurationManager.OpenExeConfiguration(pathToExeFile);
cfg.SaveAs(cfg.FilePath + "." + DateTime.Now.ToFileTime() + ".bak");
//reopen the original config again and update it.
cfg = ConfigurationManager.OpenExeConfiguration(pathToExeFile);
var setting = cfg.AppSettings.Settings[keyName];
setting.Value = newValue;
//save the changed configuration.
cfg.Save(ConfigurationSaveMode.Full);
This works well, except for one side effect. The newly saved .config file loses all the original XML comments, but only within the AppSettings area. Is it possible to to retain XML comments from the original configuration file AppSettings area?
Here's a pastebin of the full source if you'd like to quickly compile and run it.
I jumped into Reflector.Net and looked at the decompiled source for this class. The short answer is no, it will not retain the comments. The way Microsoft wrote the class is to generate an XML document from the properties on the configuration class. Since the comments don't show up in the configuration class, they don't make it back into the XML.
And what makes this worse is that Microsoft sealed all of these classes so you can't derive a new class and insert your own implementation. Your only option is to move the comments outside of the AppSettings section or use XmlDocument or XDocument classes to parse the config files instead.
Sorry. This is an edge case that Microsoft just didn't plan for.
Here is a sample function that you could use to save the comments. It allows you to edit one key/value pair at a time. I've also added some stuff to format the file nicely based on the way I commonly use the files (You could easily remove that if you want). I hope this might help someone else in the future.
public static bool setConfigValue(Configuration config, string key, string val, out string errorMsg) {
try {
errorMsg = null;
string filename = config.FilePath;
//Load the config file as an XDocument
XDocument document = XDocument.Load(filename, LoadOptions.PreserveWhitespace);
if(document.Root == null) {
errorMsg = "Document was null for XDocument load.";
return false;
}
XElement appSettings = document.Root.Element("appSettings");
if(appSettings == null) {
appSettings = new XElement("appSettings");
document.Root.Add(appSettings);
}
XElement appSetting = appSettings.Elements("add").FirstOrDefault(x => x.Attribute("key").Value == key);
if (appSetting == null) {
//Create the new appSetting
appSettings.Add(new XElement("add", new XAttribute("key", key), new XAttribute("value", val)));
}
else {
//Update the current appSetting
appSetting.Attribute("value").Value = val;
}
//Format the appSetting section
XNode lastElement = null;
foreach(var elm in appSettings.DescendantNodes()) {
if(elm.NodeType == System.Xml.XmlNodeType.Text) {
if(lastElement?.NodeType == System.Xml.XmlNodeType.Element && elm.NextNode?.NodeType == System.Xml.XmlNodeType.Comment) {
//Any time the last node was an element and the next is a comment add two new lines.
((XText)elm).Value = "\n\n\t\t";
}
else {
((XText)elm).Value = "\n\t\t";
}
}
lastElement = elm;
}
//Make sure the end tag for appSettings is on a new line.
var lastNode = appSettings.DescendantNodes().Last();
if (lastNode.NodeType == System.Xml.XmlNodeType.Text) {
((XText)lastNode).Value = "\n\t";
}
else {
appSettings.Add(new XText("\n\t"));
}
//Save the changes to the config file.
document.Save(filename, SaveOptions.DisableFormatting);
return true;
}
catch (Exception ex) {
errorMsg = "There was an exception while trying to update the config value for '" + key + "' with value '" + val + "' : " + ex.ToString();
return false;
}
}
If comments are critical, it might just be that your only option is to read & save the file manually (via XmlDocument or the new Linq-related API). If however those comments are not critical, I would either let them go or maybe consider embedding them as (albeit redundant) data elements.
Related
I add dicom files using the AddFile(dicomFile,name) method but the number of frames tag does not appear.
var sourcePath = Path.Combine(tempDirectory, "DICOM", $"PATIENT{i + 1}", $"STUDY{j + 1}", $"SERIES{k + 1}", $"SUBSERIES{l + 1}");
var dicomDir = new DicomDirectory { AutoValidate = false };
foreach (var file in new DirectoryInfo(tempDirectory).GetFiles("*.*", SearchOption.AllDirectories))
{
try
{
var dicomFile = DicomFile.Open(file.FullName);
if (dicomFile != null)
{
var referenceField = file.FullName.Replace(tempDirectory, string.Empty).Trim('\\');
dicomDir.AddFile(dicomFile, referenceField);
}
}
catch (Exception ex)
{
Log.Error(ex, ex.Message);
}
}
var dicomDirPath = Path.Combine(tempDirectory, "DICOMDIR");
dicomDir.Save(dicomDirPath);
resultDirectories.Add(dicomDirPath);
I also tried the addorupdate method but it doesn't work.
I use the fo-dicom library 4.0.7
When building a DICOMDIR with fo-dicom by iterative calling AddFile for each file, then you will get a DICOMDIR with all the required DicomTags. But of course there are a lot of tags that are optional and you can add them yourself.
The method AddFile returns an instance of type DicomDirectoryEntry, which gives you a reference to the patient record entry, the study record entry, the series record entry and the instance record entry. There you can add as many additional optional data that you wish. In your case it would look like
[...]
if (dicomFile != null)
{
var referenceField = file.FullName.Replace(tempDirectory, string.Empty).Trim('\\');
var entries = dicomDir.AddFile(dicomFile, referenceField);
// now you can add some additional data.
// but before adding values, make sure that those values are available
// in your original DicomFile to avoid NullReferenceExceptions.
if (dicomFile.Dataset.Contains(DicomTag.NumberOfFrames))
{
entries.InstanceRecord.AddOrUpdate(DicomTag.NumberOfFrames, dicomFile.Dataset.GetSingleValue<int>(DicomTag.NumberOfFrames));
}
}
What my goal with my piece of code is not to save duplicate domains to a .txt file if a checkbox is ticked.
Code:
// save to file here
if (footprints.Any(externalUrl.Contains))
{
// Load all URLs into an array ...
var hash = new List<string>(File.ReadAllLines(#"Links\" + lblFootprintsUsed.Text));
// Find the domain root url e.g. site.com ...
var root = Helpers.GetRootUrl(externalUrl);
if (chkBoxDoNotSaveDuplicateDomains.Checked == true)
{
if (!hash.Contains(Helpers.GetRootUrl(externalUrl)))
{
using (var sr = new StreamWriter(#"Links\" + lblFootprintsUsed.Text, true))
{
// before saving make & to & and get rid of #038; altogether ...
var newURL = externalUrl.Replace("&", "&").Replace("#038;", " ");
sr.WriteLine(newURL);
footprintsCount++;
}
}
}
if (chkBoxDoNotSaveDuplicateDomains.Checked == false)
{
if (!hash.Contains(externalUrl))
{
using (var sr = new StreamWriter(#"Links\" + lblFootprintsUsed.Text, true))
{
// before saving make & to & and get rid of #038; altogether ...
var newURL = externalUrl.Replace("&", "&").Replace("#038;", " ");
sr.WriteLine(newURL);
footprintsCount++;
}
}
}
}
The code above starts off by checking if a certain footprint pattern is found in a URL structure, if it does we load all URLs into a List the way !hash.Contains(externalUrl) should work is NOT to add duplicate URLs to the .txt file, but i can see from testing it does add complete duplicate URLs (the first issue) i never noticed this before, then i tried to add !hash.Contains(Helpers.GetRootUrl(externalUrl)) which should not add duplicate domains to the .txt file.
So unchecked, the code should not add duplicate URLs to file.
And checked the code should not add duplicate domains to file.
Both seem to fail, i cannot see any issue in the code as such, is there anyhting i am missing or could do better? any help is appreciated.
Here you are adding the full URL to the file, but while checking you are comparing only with the root URL
Modify the condition
if (!hash.Contains(Helpers.GetRootUrl(externalUrl)))
to
if (!hash.Any(x => x.Contains(Helpers.GetRootUrl(externalUrl))))
I am using the PdfSharp reference library to attempt to add functionality to my program that adds metadata tags. I am able to successfully add metadata tags to a document, but I am having an issue with updating the tags on existing custom properties. Whenever I attempt to use my method to update the custom properties, I receive the following exception:
"'System.Collections.Generic.KeyValuePair' does not contain a definition for 'Name'."
Could you guys tell me if I am coding the if statement in the foreach loop below to correctly loop through all of the custom elements in the PDF document to see if it exists and needs to be updated? Thanks.
public void AddMetaDataPDF(string property, string propertyValue, string
path)
{
PdfDocument document = PdfReader.Open(path);
bool propertyFound = false;
try {
dynamic properties = document.Info.Elements;
foreach(dynamic p in properties)
{
//Check to see if the property exists. If it does, update
value.
if(string.Equals(p.Name, property,
StringComparison.InvariantCultureIgnoreCase))
{
document.Info.Elements.SetValue("/" + property, new
PdfString(propertyValue));
}
}
// the property doesn't exist so add it
if(!propertyFound)
{
document.Info.Elements.Add(new KeyValuePair<String, PdfItem>
("/"+ property, new PdfString(propertyValue)));
}
}
catch (Exception ex)
{
MessageBox.Show(path + "\n" + ex.Message);
document.Close();
}
finally
{
if(document != null)
{
document.Save(path);
document.Close();
}
}
}
I didn't try your code but a common issue when working with this library is that you need to add a slash before the name of the property for it to be found. The code below will make the trick.
PdfDocument document = PdfReader.Open(path);
var properties = document.Info.Elements;
if (properties.ContainsKey("/" + propertyName))
{
properties.SetValue("/" + propertyName, new PdfString(propertyValue));
}
else
{
properties.Add(new KeyValuePair<String, PdfItem>("/" + propertyName, new PdfString(propertyValue)));
}
document.Save(path);
document.Close();
Also the PDF file shouldn't be write protected. Otherwise you need to use a tool for unlocking the file before calling PdfSharp.
I am using XDocument.Validate (it seems to function the same as XmlDocument.Validate) to validate an XML document against an XSD - this works well and I am informed of validation errors.
However, only some information seems to be exposed [reliably] in the ValidationEventHandler (and XmlSchemaException), e.g.:
the error message (i.e. "The 'X' attribute is invalid - The value 'Y' is invalid according to its datatype 'Z' - The Pattern constraint failed"),
the severity
What I would like is to get the "failing XPath" for the validation failure (where it makes sense): that is, I would like to get the failure in relation to the XML document (as opposed to the XML text).
Is there a way to obtain the "failing XPath" information from XDocument.Validate? If not, can the "failing XPath" be obtained through another XML validation method such as an XmlValidatingReader1?
Background:
The XML will be sent as data to my Web Service with an automatic conversion (via JSON.NET) from JSON to XML. Because of this I begin processing the XDocument data1 and not text, which has no guaranteed order due to the original JSON data. The REST client is, for reasons I care not to get into, basically a wrapper for HTML form fields over an XML document and validation on the server occurs in two parts - XML schema validation and Business Rule validation.
In the Business Rule validation it's easy to return the "XPath" for the fields which fail conformance that can be used to indicate the failing field(s) on the client. I would like to extend this to the XSD schema validation which takes care of the basic structure validation and, more importantly, the basic "data type" and "existence" of attributes. However, due to the desired automatic process (i.e. highlight the appropriate failing field) and source conversions, the raw text message and the source line/column numbers are not very useful by themselves.
Here is a snippet of the validation code:
// Start with an XDocument object - created from JSON.NET conversion
XDocument doc = GetDocumentFromWebServiceRequest();
// Load XSD
var reader = new StringReader(EmbeddedResourceAccess.ReadResource(xsdName));
var xsd = XmlReader.Create(reader, new XmlReaderSettings());
var schemas = new XmlSchemaSet();
schemas.Add("", xsd);
// Validate
doc.Validate(schemas, (sender, args) => {
// Process validation (not parsing!) error,
// but how to get the "failing XPath"?
});
Update: I found Capture Schema Information when validating XDocument which links to "Accessing XML Schema Information During Document Validation" (cached) from which I determined two things:
XmlSchemaException can be specialized into XmlSchemaValidationException which has a SourceObject property - however, this always returns null during validation: "When an XmlSchemaValidationException is thrown during validation by a validating XmlReader object, the value of the SourceObject property is null".
I can read through the document (via XmlReader.Read) and "remember" the path prior to the validation callback. While this "seems like it works" in initial tests (without a ValidationCallback), it feels quite inelegant to me - but I've been able to find little else.
Sender of validation event is a source of event. So, you can search over the network for code which gets XPath for node (e.g. Generating an XPath expression) and generate XPath for source of event:
doc.Validate(schemas, (sender, args) => {
if (sender is XObject)
{
xpath = ((XObject)sender).GetXPath();
}
});
Take it :-)
var xpath = new Stack<string>();
var settings = new XmlReaderSettings
{
ValidationType = ValidationType.Schema,
ValidationFlags = XmlSchemaValidationFlags.ReportValidationWarnings,
};
MyXmlValidationError error = null;
settings.ValidationEventHandler += (sender, args) => error = ValidationCallback(sender, args);
foreach (var schema in schemas)
{
settings.Schemas.Add(schema);
}
using (var reader = XmlReader.Create(xmlDocumentStream, settings))
{
// validation
while (reader.Read())
{
if (reader.NodeType == XmlNodeType.Element)
{
xpath.Push(reader.Name);
}
if (error != null)
{
// set "failing XPath"
error.XPath = xpath.Reverse().Aggregate(string.Empty, (x, y) => x + "/" + y);
// your error with XPath now
error = null;
}
if (reader.NodeType == XmlNodeType.EndElement ||
(reader.NodeType == XmlNodeType.Element && reader.IsEmptyElement))
{
xpath.Pop();
}
}
}
I don't know the API but my guess is no, you can't get the xpath because validation may be implemented as a finite state machine. A state may not translate to an xpath or in the case when it is valid for more than one element to follow and the element found is not in the expected set, the xpath doesn't exist.
Finally successed in this way!
When I use XmlReader.Create(xmlStream, settings) and xmlRdr.Read() to Validate a XML, I captured the sender of the ValidationEventHandler and find it is an object of {System.Xml.XsdValidatingReader},so I transfer sender to a xmlreader object, there are some functions in XMLReader class to help you find the parent node of the error attributes.
There is one thing to watch out that when i use XMLReader.MoveToElement(), the Validation function will be stuck into a loop in the error attribute,so i used MoveToAtrribute(AttributeName) and then MoveToNextAttribute to avoid stuck into the loop, maybe there is more elegant way to handle this.
Without further ado,below is my code.
public string XMLValidation(string XMLString, string SchemaPath)
{
string error = string.Empty;
MemoryStream xmlStream = new MemoryStream(Encoding.UTF8.GetBytes(XMLString));
XmlSchemaSet schemas = new XmlSchemaSet();
schemas.Add(null, SchemaPath);
XmlReaderSettings settings = new XmlReaderSettings();
settings.ValidationType = ValidationType.Schema;
settings.Schemas.Add(schemas);
settings.ValidationEventHandler += new ValidationEventHandler(delegate(object sender, ValidationEventArgs e)
{
switch (e.Severity)
{
case XmlSeverityType.Error:
XmlReader senRder = (XmlReader)sender;
if (senRder.NodeType == XmlNodeType.Attribute)
{//when error occurs in an attribute,get its parent element name
string attrName = senRder.Name;
senRder.MoveToElement();
error += string.Format("ERROR:ElementName'{0}':{1}{2}", senRder.Name, e.Message, Environment.NewLine);
senRder.MoveToAttribute(attrName);
}
else
{
error += string.Format("ERROR:ElementName'{0}':{1}{2}", senRder.Name, e.Message, Environment.NewLine);
}
break;
case XmlSeverityType.Warning:
break;
}
});
XmlReader xmlRdr = XmlReader.Create(xmlStream, settings);
while (xmlRdr.Read()) ;
return error;
}
Alternatively you could use the code at How to find an XML node from a line and column number in C#? to get the failing node by using the args.Exception.LineNumber and args.Exception.LinePosition and then navigate the XML document as required to provide more information about what data caused the validation to fail.
If, like me, you are using the "XmlDocument.Validate(ValidationEventHandler validationEventHandler)" method to validate your XML:
// Errors and alerts collection
private ICollection<string> errors = new List<String>();
// Open xml and validate
...
{
// Create XMLFile for validation
XmlDocument XMLFile = new XmlDocument();
// Validate the XML file
XMLFile.Validate(ValidationCallBack);
}
// Manipulator of errors occurred during validation
private void ValidationCallBack(object sender, ValidationEventArgs args)
{
if (args.Severity == XmlSeverityType.Warning)
{
errors.Add("Alert: " + args.Message + " (Path: " + GetPath(args) + ")");
}
else if (args.Severity == XmlSeverityType.Error)
{
errors.Add("Error: " + args.Message + " (Path: " + GetPath(args) + ")");
}
}
The secret is to get the "Exception" property data of the "args" parameter. Do like this:
// Return this parent node
private string GetPath(ValidationEventArgs args)
{
var tagProblem =((XmlElement)((XmlSchemaValidationException)args.Exception).SourceObject);
return iterateParentNode(tagProblem.ParentNode) + "/" +tagProblem.Name;
}
private string iterateParentNode(XmlNode args)
{
var node = args.ParentNode;
if (args.ParentNode.NodeType == XmlNodeType.Element)
{
return interateParentNode(node) + #"/" + args.Name;
}
return "";
}
I have downloaded a dictionary file from http://code.google.com/p/quickdic-dictionary/
But the file extension is .quickdic and is not plain text.
How can I load the quickdic dictionaries (.quickdic) into c# to make simple word queries?
I browsed through the git code, and a few things stuck out.
First, in the DictionaryActivity.java file, there is the following in onCreate():
final String name = application.getDictionaryName(dictFile.getName());
this.setTitle("QuickDic: " + name);
dictRaf = new RandomAccessFile(dictFile, "r");
dictionary = new Dictionary(dictRaf);
That Dictionary Class is not the built in class with Java, but is here according to the imports:
import com.hughes.android.dictionary.engine.Dictionary;
When I look there, it shows a constructor for a Dictionary taking a RandomAccessFile as the parameter. Here's that source code:
public Dictionary(final RandomAccessFile raf) throws IOException {
dictFileVersion = raf.readInt();
if (dictFileVersion < 0 || dictFileVersion > CURRENT_DICT_VERSION) {
throw new IOException("Invalid dictionary version: " + dictFileVersion);
}
creationMillis = raf.readLong();
dictInfo = raf.readUTF();
// Load the sources, then seek past them, because reading them later disrupts the offset.
try {
final RAFList<EntrySource> rafSources = RAFList.create(raf, new EntrySource.Serializer(this), raf.getFilePointer());
sources = new ArrayList<EntrySource>(rafSources);
raf.seek(rafSources.getEndOffset());
pairEntries = CachingList.create(RAFList.create(raf, new PairEntry.Serializer(this), raf.getFilePointer()), CACHE_SIZE);
textEntries = CachingList.create(RAFList.create(raf, new TextEntry.Serializer(this), raf.getFilePointer()), CACHE_SIZE);
if (dictFileVersion >= 5) {
htmlEntries = CachingList.create(RAFList.create(raf, new HtmlEntry.Serializer(this), raf.getFilePointer()), CACHE_SIZE);
} else {
htmlEntries = Collections.emptyList();
}
indices = CachingList.createFullyCached(RAFList.create(raf, indexSerializer, raf.getFilePointer()));
} catch (RuntimeException e) {
final IOException ioe = new IOException("RuntimeException loading dictionary");
ioe.initCause(e);
throw ioe;
}
final String end = raf.readUTF();
if (!end.equals(END_OF_DICTIONARY)) {
throw new IOException("Dictionary seems corrupt: " + end);
}
So, anyway, this is how his java code reads the file in.
Hopefully, this helps you simulate this in C#.
From here you would probably want to see how he is serializing the EntrySource, PairEntry, TextEntry, and HtmlEntry, as well as the indexSerializer.
Next look to see how RAFList.create() works.
Then see how that result is incorporated in creating a CachingList using CachingList.create()
Disclaimer: I'm not sure if the built in serializer in C# uses the same format as Java's, so you may need to simulate that too :)