How to parse .proto file into a FileDescriptor in C#? - c#

My goal is exactly the same as stated in this issue on github:
how to read an existing .proto file and get a FileDescriptor from it
I cannot use the suggested "workaround", for 2 reasons:
I have "plain" .proto files, i.e.:
they are text files, just like good old addressbook.proto
they are not self-describing
I do not want to invoke the protoc compiler as an external application.
According to Marc this is possible with protobuf-net library:
Without a compiled schema, you would need a runtime .proto parser. [...] protobuf-net includes one (protobuf-net.Reflection)
I found Parsers.cs
Thanks Marc, but how do I use/do this?
Is this the right entry point?
Is there a minimal working example somewhere?

var set = new FileDescriptorSet();
set.Add("my.proto", true);
set.Process();
That's all you need; note that if you want to provide the actual contents (rather than having the library do the file access), there is an optional TextReader parameter. If you need imports:
set.AddImportPath(...);
Once you've called Process, the .Files should be populated along with the .MessageTypes of each file, etc.
For a more complete example:
var http = new HttpClient();
var proto = await http.GetStringAsync(
"https://raw.githubusercontent.com/protocolbuffers/protobuf/master/examples/addressbook.proto");
var fds = new FileDescriptorSet();
fds.Add("addressbook.proto", true, new StringReader(proto));
fds.Process();
var errors = fds.GetErrors();
Console.WriteLine($"Errors: {errors.Length}");
foreach(var file in fds.Files)
{
Console.WriteLine();
Console.WriteLine(file.Name);
foreach (var topLevelMessage in file.MessageTypes)
{
Console.WriteLine($"{topLevelMessage.Name} has {topLevelMessage.Fields.Count} fields");
}
}
Which outputs:
addressbook.proto
Person has 5 fields
AddressBook has 1 fields
google/protobuf/timestamp.proto
Timestamp has 2 fields
Notice that you didn't have to provide timestamp.proto or an import path for it - the library embeds a number of the common imports, and makes them available automatically.
(each file is a FileDescriptorProto; the group of files in a logical parse operation is the FileDescriptorSet - which is the root object used from descriptor.proto; note that all of the objects in this graph are also protobuf serializable, if you need a compiled/binary schema)

Related

Adding and Removing Additional Documents with Roslyn

I have two code refactorings, one that amongst other things, generates a csv file and adds it to the Project in the solution, and another that is supposed to edit it.
I am successfully (seems like it, it shows in the solution explorer after it) generating and attaching the csv file to the solution Project through the code:
var doc = editor.GetChangedDocument();
SourceText st = SourceText.From(csv.GetStringDocument(), Encoding.Default);
var additionalDoc = doc.Project.AddAdditionalDocument(filename, st, new List<string> { "_TestData" });
return additionalDoc.Project.Solution;
Before returning, if I check additionalDoc.Project.AdditionalDocuments.Count(), its' value is 1.
And, in the other, when I try to access the additional files in the Project, the IEnumerable is empty.
How is it supposed to access the additional documents added with code refactorings/code fixers?

Using File.ReadAllLines from embedded text file

I have been applying what I have learned so far in Bob Tabors absolute beginners series and I wrote a small console word game for my daughter that requires me to generate a random 5 letter word.
I was previously using File.ReadAllLines(path) to generate a string array from a text file (wordlist.txt) on my system and Random.next to generate the index I would pull from the array.
I learned from some posts here how to embed the file as a resource but now I am unable to find the syntax to point to it (path). Or do I have to access it differently now that it is embedded?
Thanks in advance
Without a good, minimal, complete code example it is impossible to offer specific advice.
However, the basic issue is this: when you embed a file as a resource, it is no longer a file. That is, the original file still exists, but the resource itself is not a file in any way. It is stored as some specific kind of data in your assembly; resources embedded from file sources generally wind up as binary data objects.
How to use this data depends on what you mean by "embed". There are actually two common ways to store resources in a C# program: you can use the "Resources" object in the project, which exposes the resource via the project's ...Properties.Resources class (which in turn uses the ResourceManager class in .NET). Or you can simply add the file to the project itself, and select the "Embedded Resource" build option.
If you are using the "Resources" designer, then there are a couple of different ways you might have added the file. One is to use the "New Text File..." option, which allows you to essentially copy/paste or type new text into a resource. This is exposed in code as a string property on the Properties.Resources object. The same thing will happen if you add the resource using the "Existing File..." option and select a file that Visual Studio recognizes as a text file.
Otherwise, the file will be included as a byte[] object exposed by a property in the Properties.Resources class.
If you have used the "Embedded Resource" build option instead of the "Resources" designer, then your data will be available by calling Assembly.GetManifestResourceStream(string) method, which returns a Stream object. This can be wrapped in StreamReader to allow it to be read line-by-line.
Direct replacements for the File.ReadAllLines(string) approach would look something like the following…
Using "Embedded Resource":
string[] ReadAllResourceLines(string resourceName)
{
using (Stream stream = Assembly.GetEntryAssembly()
.GetManifestResourceStream(resourceName))
using (StreamReader reader = new StreamReader(stream))
{
return EnumerateLines(reader).ToArray();
}
}
IEnumerable<string> EnumerateLines(TextReader reader)
{
string line;
while ((line = reader.ReadLine()) != null)
{
yield return line;
}
}
Using Properties.Resources:
You can do something similar when using the Properties.Resources class. It looks almost identical:
string[] ReadAllResourceLines(string resourceText)
{
using (StringReader reader = new StringReader(resourceText))
{
return EnumerateLines(reader).ToArray();
}
}
called like string[] allLines = ReadAllResourceLines(Properties.Resources.MyTextFile);, where MyTextFile is the property name for the resource you added in the designer (i.e. the string you pass in that second example is the text of the file itself, not the name of the resource).
If you added an existing file that Visual Studio didn't recognize as a text file, then the property type will be byte[] instead of string and you'll need yet another slightly different approach:
string[] ReadAllResourceLines(byte[] resourceData)
{
using (Stream stream = new MemoryStream(resourceData))
using (StreamReader reader = new StreamReader(stream))
{
return EnumerateLines(reader).ToArray();
}
}
Note that in all three examples, the key is that the data winds up wrapped in a TextReader implementation, which is then used to read each line individually, to populate an array. These all use the same EnumerateLines() helper method I show above.
Of course, now that you see how the data can be retrieved, you can adapt that to use the data in a variety of other ways, in case for example you don't really want or need the text represented as an array of string objects.
If you are using The Resource file and added a text file you could use
string text=Properties.Resources.<ResourceName>
here Resources is default Resource for your project .If you have added a custom Resource File you can use its name instead of Properties.Resources
if your content is a file then it is represented as a byte.In your case for simple Text it will be an string if you have included a Text File.
for any other file you can use the syntax for converting content to text(if it is text) as
string text=Encoding.ASCII.GetString(Properties.Resources.<ResourceName>);
if your file has any other encoding (as UTF Unicode ) you can use UTF8 or such classes for that under Encoding

How to start with clearcanvas in .NET

I am brand new to clearcanvas and to DICOM files in general so right now I am just trying to get a simple desktop application working (.NET and C#). I have added the clearcanvas dll's to my project but I'm just not sure how I would go about actually reading in a DICOM file. Ideally I would like to be able to access all of the tags separately after I have read in the file.
I would really appreciate just some initial code that can get me started.
Thanks,
You should be able to do something as simple as this:
string filename = "file.dcm";
DicomFile theFile = new DicomFile(filename);
theFile.Load(DicomReadOptions.Default);
foreach (DicomAttribute attribute in theFile.DataSet)
{
Console.WriteLine("Tag: {0}, Value: {1}", attribute.Tag.Name, attribute.ToString());
}
The DicomFile object is the core class for manipulating DICOM files. It has MetaInfo and DataSet properties which are DicomAttributeCollections contain the tags in the file. Each DicomAttribute within the DicomAttributeCollection has methods and properties to retrieve the attribute values.

How To Write To An Embedded Resource?

I keep getting the error "Stream was not writable" whenever I try to execute the following code. I understand that there's still a reference to the stream in memory, but I don't know how to solve the problem. The two blocks of code are called in sequential order. I think the second one might be a function call or two deeper in the call stack, but I don't think this should matter, since I have "using" statements in the first block that should clean up the streams automatically. I'm sure this is a common task in C#, I just have no idea how to do it...
string s = "";
using (Stream manifestResourceStream =
Assembly.GetExecutingAssembly().GetManifestResourceStream("Datafile.txt"))
{
using (StreamReader sr = new StreamReader(manifestResourceStream))
{
s = sr.ReadToEnd();
}
}
...
string s2 = "some text";
using (Stream manifestResourceStream =
Assembly.GetExecutingAssembly().GetManifestResourceStream("Datafile.txt"))
{
using (StreamWriter sw = new StreamWriter(manifestResourceStream))
{
sw.Write(s2);
}
}
Any help will be very much appreciated. Thanks!
Andrew
Embedded resources are compiled into your assembly, you can't edit them.
As stated above, embedded resources are read only. My recommendation, should this be applicable, (say for example your embedded resource was a database file, XML, CSV etc.) would be to extract a blank resource to the same location as the program, and read/write to the extracted resource.
Example Pseudo Code:
if(!Exists(new PhysicalResource())) //Check to see if a physical resource exists.
{
PhysicalResource.Create(); //Extract embedded resource to disk.
}
PhysicalResource pr = new PhysicalResource(); //Create physical resource instance.
pr.Read(); //Read from physical resource.
pr.Write(); //Write to physical resource.
Hope this helps.
Additional:
Your embedded resource may be entirely blank, contain data structure and / or default values.
A bit late, but for descendants=)
About embedded .txt:
Yep, on runtime you couldnt edit embedded because its embedded. You could play a bit with disassembler, but only with outter assemblies, which you gonna load in current context.
There is a hack if you wanna to write to a resource some actual information, before programm starts, and to not keep the data in a separate file.
I used to worked a bit with winCE and compact .Net, where you couldnt allow to store strings at runtime with ResourceManager. I needed some dynamic information, in order to catch dllNotFoundException before it actually throws on start.
So I made embedded txt file, which I filled at the pre-build event.
like this:
cd $(ProjectDir)
dir ..\bin\Debug /a-d /b> assemblylist.txt
here i get files in debug folder
and the reading:
using (var f = new StreamReader(Assembly.GetExecutingAssembly().GetManifestResourceStream("Market_invent.assemblylist.txt")))
{
str = f.ReadToEnd();
}
So you could proceed all your actions in pre-build event run some exes.
Enjoy! Its very usefull to store some important information and helps avoid redundant actions.

Tool that auto-generate a code for accesing a xml-file

My application have a configuration xml-file. That file contains more than 50 program settings. At the present time I read and save each program setting separately. I guess It is not effiсiently for such tasks.
I need something that can auto-generate a code for load and save my program settings using predefined xml-schema.
I found a dataset in Add New Item dialog. Unfortunately, i cannot add new code to dataset1 such as events in set-accessors of properties because of this
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
Maybe, there is a tool that allows a user to generate a wrapper for accesing a xml-file ? Such as DataSet1, but with availability to add events.
Edit: I didn't mark a useful answer because i read an articles (link) which you give me. I will mark useful answer later.
If you are not willing to use app.config/web.config or the properties file (which Oded and Bruno recommend and I recommend as well), I highly recommend this utility:
Web Services Contract First (WSCF) Blue for VS2008 and VS2010
If you're on VS2005, you'll want this version of the tool: http://www.thinktecture.com/resourcearchive/tools-and-software/wscf (don't use the VS2008 version on this site. I could never get it to work right.)
Once you have the plugin installed into Visual Studio, you'll need an XSD schema of your XML file. (Google for an online XSD Generator.) Following the instructions found on the WSCF website, you can generate a wrapper class that will deserialize and reserialize your XML and give you an abstracted view of your XML.
I figure it is impossible (or at least very hard) to add new node/element TYPES, but adding new instances of existing node/element types, accessing to your data, editing the data, reordering nodes, and then saving back out are all easy.
Deserialization code looks like this:
private MyGeneratedXMLconfigClass config;
using (StreamReader sr = new StreamReader(filename))
{
XmlSerializer cXml = new XmlSerializer(typeof(MyGeneratedXMLconfigClass));
config = (MyGeneratedXMLconfigClass)cXml.Deserialize(sr);
}
Now your XML has been de-serialized into the "config" instance of your custom class. Then you can access the whole class as a series of nested values and Lists.
For example:
string errorFile = config.errorsFile;
List<string> actions = config.actionList;
var specialActions = from action in config.actionList
where action.contains("special")
select action;
Etc., etc. Then once you're done manipulating your data, you can re-serialize with this code:
using (StreamWriter wr = new StreamWriter(filename, false))
{
XmlSerializer cXml = new XmlSerializer(typeof(MyGeneratedXMLconfigClass));
cXml.Serialize(wr, config);
}
One of the very nice things about this tool is that it auto-generates all classes as "partial" classes, so that you can feel free to extend each class on your own without fear of your code getting stomped on in case you ever need to re-generate because the XSD/XML was changed.
I imagine this might sound like a lot, but the learning curve is actually pretty easy and once you get it installed and working, you'll realize how stupidly easy it is. It's worth it. I swear. :-)
If you have an appropriate xsd schema for your xml file microsoft provides xsd.exe, a small tool which auto-generates c# classes for this schema.
For details see: http://msdn.microsoft.com/en-us/library/x6c1kb0s%28VS.71%29.aspx
Why are you using hand rolled XML for configuration? What is wrong with the existing app.config and web.config schemas?
Why not use a .Settings file?
You can follow these steps:
1) generate an XSD file from your XML file. For There used to be a tool to infer schema from an XML file, I forgot what it's called. Currently I use my own utility, which basically runs this core routine to read an xml file and generate the corresponding xsd:
static void InferSchema(string fileName)
{
XmlWriter writer = null;
XmlSchemaInference infer = new XmlSchemaInference();
XmlSchemaSet sc = new XmlSchemaSet();
string outputXsd = fileName.Replace(".xml", ".xsd");
sc = infer.InferSchema(new XmlTextReader(fileName));
using (writer = XmlWriter.Create(new StreamWriter(outputXsd)))
{
foreach(XmlSchema schema in sc.Schemas())
{
schema.Write(writer);
Console.WriteLine(">> found schema - generated to {0}",
outputXsd);
}
}
}
2) run xsd.exe to generate a serializable class from the XSD file.
xsd.exe /c /n:MyNameSpaceHere MyGenerated.xsd
Next, you can read the XML file into the serializable class using XmlSerializer.Serialize() method. Something like this:
public static void Serialize<T>(T data, TextWriter writer)
{
try
{
XmlSerializer xs = new XmlSerializer(typeof(T));
xs.Serialize(writer, data);
}
catch (Exception e)
{
throw;
}
}
Finally, you can write back to the XML file from the class using the XmlSerializer.Deserialize() method, like this for instance:
public static void Deserialize<T>(out T data, XmlReader reader)
{
try
{
XmlSerializer xs = new XmlSerializer(typeof(T));
data = (T)xs.Deserialize(reader);
}
catch (Exception e)
{
reader.Close();
throw;
}
}
This is called a properties file. C# should have something similar to Java's Properties class where you can load all properties without hard-coding their names.
EDIT:
There's apparently no built-in properties parsing solution for C#. But you can easily implement your own. See here.
If you have an XSD file, you can generate classes from that. Besides the already mentioned xsd.exe from Microsoft (which hasn't been updated for quite some time), there are other tools for this. I am using XSD2Code, which allows generating strongly typed collections, lazy initialization, etc.
If you do not have an XSD, you can point the xsd.exe at your xml-file, and it will generate an XSD from that. The schema usually needs some work, but generally is a good starting point.
xsd.exe (instance).xml
You can use System.Xml.Serialization - it's very easy and you can serialize even class objects directly, like MyCustomClass (it even saves MyCustomClass public fields).
Deserializing the XML file will get a new instance of MyCustomClass, so such a feature is priceless.
Note one thing: you should add EVERY SINGLE TYPE you use in the class, but that's easy though.
I attached a complete project that does the thing you want. just change classes and objects and that will be all.
source code
for example (i'm shortening the code):
using System.Xml;
using System.Xml.Serialization;
using System.IO;
[XmlRootAttribute("Vendor")]
class Vendor{
[XmlAttribute]
Product prod;
}
[XmlRootAttribute("Product")]
class Product{
[XmlAttribute]
public string name="";
}
class Test{
Vendor v=new Vendor();
Product p=new Product();
p.name="a cake";
v.prod=p;
//add EVERY SINGLE TYPE you use in the serialized class.
Type[] type_list = { typeof(Product) };
XmlSerializer packer = new XmlSerializer(v.GetType(),type_list);
XmlWriter flusher = XmlWriter.Create(#"c:\bak.xml");
packer.Serialize(flusher, v);
flusher.Close();
XmlReader restorer = XmlReader.Create(#"c:\bak.xml");
Vendor v2 = (Vendor)packer.Deserialize(restorer);
//v2.prod.name is now "cake"
//COOL was my first impression :P
}

Categories