I am trying to import a file with multiple record definition in it. Each one can also have a header record so I thought I would define a definition interface like so.
public interface IRecordDefinition<T>
{
bool Matches(string row);
T MapRow(string row);
bool AreRecordsNested { get; }
GenericLoadClass ToGenericLoad(T input);
}
I then created a concrete implementation for a class.
public class TestDefinition : IRecordDefinition<Test>
{
public bool Matches(string row)
{
return row.Split('\t')[0] == "1";
}
public Test MapColumns(string[] columns)
{
return new Test {val = columns[0].parseDate("ddmmYYYY")};
}
public bool AreRecordsNested
{
get { return true; }
}
public GenericLoadClass ToGenericLoad(Test input)
{
return new GenericLoadClass {Value = input.val};
}
}
However for each File Definition I need to store a list of the record definitions so I can then loop through each line in the file and process it accordingly.
Firstly am I on the right track
or is there a better way to do it?
I would split this process into two pieces.
First, a specific process to split the file with multiple types into multiple files. If the files are fixed width, I have had a lot of luck with regular expressions. For example, assume the following is a text file with three different record types.
TE20110223 A 1
RE20110223 BB 2
CE20110223 CCC 3
You can see there is a pattern here, hopefully the person who decided to put all the record types in the same file gave you a way to identify those types. In the case above you would define three regular expressions.
string pattern1 = #"^TE(?<DATE>[0-9]{8})(?<NEXT1>.{2})(?<NEXT2>.{2})";
string pattern2 = #"^RE(?<DATE>[0-9]{8})(?<NEXT1>.{3})(?<NEXT2>.{2})";
string pattern3 = #"^CE(?<DATE>[0-9]{8})(?<NEXT1>.{4})(?<NEXT2>.{2})";
Regex Regex1 = new Regex(pattern1);
Regex Regex2 = new Regex(pattern2);
Regex Regex3 = new Regex(pattern3);
StringBuilder FirstStringBuilder = new StringBuilder();
StringBuilder SecondStringBuilder = new StringBuilder();
StringBuilder ThirdStringBuilder = new StringBuilder();
string Line = "";
Match LineMatch;
FileInfo myFile = new FileInfo("yourFile.txt");
using (StreamReader s = new StreamReader(f.FullName))
{
while (s.Peek() != -1)
{
Line = s.ReadLine();
LineMatch = Regex1.Match(Line);
if (LineMatch.Success)
{
//Write this line to a new file
}
LineMatch = Regex2.Match(Line);
if (LineMatch.Success)
{
//Write this line to a new file
}
LineMatch = Regex3.Match(Line);
if (LineMatch.Success)
{
//Write this line to a new file
}
}
}
Next, take the split files and run them through a generic process, that you most likely already have, to import them. This works well because when the process inevitably fails, you can narrow it to the single record type that is failing and not impact all the record types. Archive the main text file along with the split files and your life will be much easier as well.
Dealing with these kinds of transmitted files is hard, because someone else controls them and you never know when they are going to change. Logging the original file as well as a receipt of the import is very import and shouldn't be overlooked either. You can make that as simple or as complex as you want, but I tend to write a receipt to a db and copy the primary key from that table into a foreign key in the table I have imported the data into, then never change that data. I like to keep a unmolested copy of the import on the file system as well as on the DB server because there are inevitable conversion / transformation issues that you will need to track down.
Hope this helps, because this is not a trivial task. I think you are on the right track, but instead of processing/importing each line separately...write them to a separate file. I am assuming this is financial data, which is one of the reasons I think provability at every step is important.
I think the FileHelpers library solves a number of your problems:
Strong types
Delimited
Fixed-width
Record-by-Record operations
I'm sure you could consolidate this into a type hierarchy that could tie in custom binary formats as well.
Have you looked at something using Linq? This is a quick example of Linq to Text and Linq to Csv.
I think it would be much simpler to use "yield return" and IEnumerable to get what you want working. This way you could probably get away with only having 1 method on your interface.
Related
Well, I'm facing an issue on exporting my data to SQL Server as the subject says.
I have a semicolon delimited file, but also I have occurrences when I find semicolon inside the text, for example:
ID;DESCRIPTION;VALUE
1;TEXT1;35
2;TEXT;2;45
3;TE;XT3;50
So as you can see I have some garbage that I would like to remove, since this is shifting the columns.
I have some ideas, like make a standard count of semicolons, in this case it would be 2 semicolons by line and remove the extra ones.
In my case this is always happening in 1 column specifically the Address column and complement, so i know exactly what the number of the column is.
I cant ask people who dispose this file since the system is an old system and they can't put qualifiers like double quotes or simply change the delimiter.
I know I could do this via script task but I have few knowledge on programming, so I'm trying to look for another manner.
I'd like to say again that this problem is happening on source file so when I configure the flat file connection it already shift the column so I can't make any treatment like derived column or something else. I have to do the changes on the file itself before I load it in SSIS.
I've been looking for some days on any kind of forums and I don't see any similar questions and solutions for this problem, since most of the example files of people who asks, already have qualifiers or something like this, so I really appreciate if you can help me!
You mentioned you have little programming knowledge but a script is only solution that can handle delimiters in fields that are not enclosed. You are fortunate there is only a single problem field as it wouldn't be possible to parse ambiguous delimiters unless you have additional rules to determine where actual fields begin and end.
As long as you are certain there is only one field with embedded delimiters, one method is a data flow source Script component. Below are the steps to create one:
Add a script component to data flow and select Source for the type
Add the flat file connection manager to the script properties Connection Managers collection
Add each field as an output column under the script properties component Input and Outputs
Edit the script source and replace the template 'CreateOutputRows()' method code with the version code below.
See comments in the script indicating where customizations are needed for your actual file. This version will work with your sample file of 3 fields, with the second field having embedded delimiters.
public override void CreateNewOutputRows()
{
const char FIELD_DEMIMITER = ';';
////*** change this to the zero-based index of the problem field
const int BAD_FIELD_INDEX = 1;
////*** change this to the connection added to script componenent connection manager
var filePath = this.Connections.FlatFileSource.ConnectionString;
string record = "";
using (var inputFile = new System.IO.StreamReader(filePath))
{
record = inputFile.ReadLine();
if(record != null)
{
//count header record fields to get expected field count for data records
var headerFieldCount = record.Split(FIELD_DEMIMITER).Length;
while (record != null)
{
record = inputFile.ReadLine();
if(record == null)
{
break; //end of file
}
var fields = record.Split(FIELD_DEMIMITER);
var extraFieldCount = fields.Length - headerFieldCount;
if (extraFieldCount < 0)
{
//raise an error if fewer fields that we expect
throw new DataException(string.Format("Invalid record. {0} fields read, {1} fields in header.", fields.Length, headerFieldCount));
}
if (extraFieldCount > 0)
{
var newFields = new string[headerFieldCount];
//copy preceding good fields
for (var i = 0; i < BAD_FIELD_INDEX; ++i)
{
newFields[i] = fields[i];
}
//combine segments of bad field into single field
var sourceFieldIndex = BAD_FIELD_INDEX;
var combinedField = new System.Text.StringBuilder();
while (sourceFieldIndex <= extraFieldCount + BAD_FIELD_INDEX)
{
combinedField.Append(fields[sourceFieldIndex]);
if(sourceFieldIndex < extraFieldCount + BAD_FIELD_INDEX)
{
combinedField.Append(FIELD_DEMIMITER); //add delimiter back to field value
}
++sourceFieldIndex;
}
newFields[BAD_FIELD_INDEX] = combinedField.ToString();
//copy subsquent good fields
var targetFieldIndex = BAD_FIELD_INDEX + 1;
while (sourceFieldIndex < fields.Length)
{
newFields[targetFieldIndex] = fields[sourceFieldIndex];
++sourceFieldIndex;
++targetFieldIndex;
}
fields = newFields;
}
//create output record and copy fields
this.Output0Buffer.AddRow();
//*** change the code below to map source fields to the columns defined as script component output
Output0Buffer.ID = fields[0];
Output0Buffer.DESCRIPTION = fields[1];
Output0Buffer.VALUE = fields[2];
}
}
}
this.Output0Buffer.SetEndOfRowset();
}
Another thing you can do is import the text file into a single column (varchar(max)) staging table, and then use TSQL to parse the records and import them to your final destination table.
I have recently been tasked with writing a piece of software that will import Excel files.
The problem I am trying to solve is that my company has c100 clients and each supply a file in a different layout, in so much as the columns in a file will differ between clients but the pertinent information is there in each file.
This process is complicated due to the fact that certain operations need to be done to different files.
In 1 file, for example, a column needs to be inserted after a specifc column and then the result of a calculation needs to be placed into that column. In that same sheet an address is supplied across 9 columns, this address needs to be moved into the last 6 of the 9 columns and then have the first 3 columns removed.
What I don't want to do is write the processing logic for each file (c 100 as mentioned) and thereby get trapped into the drudge of having to maintain this code and be responsible for adding new customer files as they come in.
What I want to do is create a Rule or Processing engine of sorts whereby I can have basic rules like "Insert Column", "Remove Column", "Insert Calculation", "Format a, b, c, d, e & f Columns To Use d, e & f" - the reason being so that configuring the read and process of any new file can be done through a front-end piece of software by an end user (obviously with some training on what to do).
Is there a pattern or strategy that might fit this? I have read about Rules engines but the best examples of these are simple boolean comparisons like "Age = 15" or "Surname = 'Smith'" but can't find a decent example of doing something like "Insert Column after Column G" then "Put G - 125 in to Column H".
Any help here, or a pointer to a good approach, would be greatly appreciated.
Let me see if I can help you out here.
Correct me if I am wrong, but it seems like all your input and output files contain data in columns and columns only.
In that case, you should imagine your problem as a transformation of X input columns to Y output columns. For each client, you will need a configuration that will specify the transform. The configuration might look like below
Y1 = X1
Y2 = X1 + X2
Y3 = X3 + " some string"
As you can see, your configuration lines are simply C# expressions. You can use the LINQ Expression class to build an expression tree from your transformation formulas. You can learn about Expressions here. These expressions can then be compiled and used to do the actual transform. If you think in terms of C#, you will build a static transform method that takes a list as input and returns a list as output for each client. When you use Expressions, you will have to parse the configuration files yourself.
You can also use the Roslyn Compiler Services, which can support proper C# syntax. This way, you can literally have a static method which can do the transform. This also relieves you of the parsing duties.
In either case, you will still have to deal with things like: should I expect the columns to be a string (which means your support needs to know explicitly instruct the configuration GUI to parse needed columns into numbers) or should I automatically convert number like fields into numbers (now support doesn't have to do extra configuration, but they might hit issues when dealing with columns which have numbers, like ID, but should be treated as a string to avoid any improper handling), etc.
In Summary, my approach is:
Create config file per client.
Convert the config file into C# method dynamically using Expressions or Roslyn
Provide a GUI for generating this config - this way the support person can easily specify the transform without knowing your special syntax (Expressions) or C# syntax (Roslyn). When saving config, you can generate one method per client in a single assembly (or separate assembly per client) and persist it. Let's call it client library.
Your main application can do all the standard stuff of reading from excel, validating, etc and then call the client library method to generate the output in a standard format, which can be further processed in your main application.
Hope you got the gist.
Edit: Adding some code to demonstrate. The code is a bit long-winded, but commented for understanding.
// this data represents your excel data
var data = new string[][] {
new string [] { "col_1_1", "10", "09:30" },
new string [] { "col_2_1", "12", "09:40" }
};
// you should read this from your client specific config file/section
// Remember: you should provide a GUI tool to build this config
var config = #"
output.Add(input[0]);
int hours = int.Parse(input[1]);
DateTime date = DateTime.Parse(input[2]);
date = date.AddHours(hours);
output.Add(""Custom Text: "" + date);
";
// this template code should be picked up from a
// non client specific config file/section
var code = #"
using System;
using System.Collections.Generic;
using System.Linq;
namespace ClientLibrary {
static class ClientLibrary {
public static List<string> Client1(string[] input) {
var output = new List<string>();
<<code-from-config>>
return output;
}
}
}
";
// Inject client configuration into template to form full code
code = code.Replace(#"<<code-from-config>>", config);
// Compile your dynamic method and get a reference to it
var references = new MetadataReference[] {
MetadataReference.CreateFromFile(typeof(object).Assembly.Location),
MetadataReference.CreateFromFile(typeof(Enumerable).Assembly.Location)
};
CSharpCompilation compilation = CSharpCompilation.Create(
null,
syntaxTrees: new[] { CSharpSyntaxTree.ParseText(code) },
references: references,
options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));
MethodInfo clientMethod = null;
using (var ms = new MemoryStream()) {
EmitResult result = compilation.Emit(ms);
if (!result.Success) {
foreach (Diagnostic diagnostic in result.Diagnostics) {
Console.Error.WriteLine("{0}: {1}", diagnostic.Id, diagnostic.GetMessage());
}
} else {
ms.Seek(0, SeekOrigin.Begin);
Assembly assembly = Assembly.Load(ms.ToArray());
clientMethod = assembly.GetType("ClientLibrary.ClientLibrary").GetMethod("Client1");
}
}
if (clientMethod == null)
return;
// Do transformation
foreach (string[] row in data) {
var output = clientMethod.Invoke(null, new object[] { row }) as List<string>;
Console.WriteLine(string.Join("|", output));
}
You will need some nuget libraries to compile this, and their corresponding using clauses
nuget install Microsoft.Net.Compilers # Install C# and VB compilers
nuget install Microsoft.CodeAnalysis # Install Language APIs and Services
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.Emit;
As you notice, the only piece to worry about is the GUI to auto-generate the code for the transformation - which I have not provided here. If you want simple transforms, that should be very easy, but for a complex transform, it will be more involved
It sounds like you're expecting your end user to be technical-savvy enough to understand this configuration mechanism that you're going to write. If they can handle that level of technical detail, it might be simpler to give them an Excel book and an official excel template that contains all the columns that your import app needs and they can manually massage the data to the spec.
Otherwise, I would suggest some strategy design based pattern solution to build a library of "data massager" classes for known formats, and just add new classes as new formats are encountered. e.g.
public interface IClientDataImporter
{
List<MyCustomRowStructure> Import(string filename);
}
// client 1 importer
public class ClientOneImporter : IClientDataImporter
{
public List<MyCustomRowStructure> Import(string filename)
{
var result = new List<MyCustomRowStructure>();
// ..... insert custom logic here
return result;
}
}
// client 2 importer
public class ClientTwoImporter : IClientDataImporter
{
public List<MyCustomRowStructure> Import(string filename)
{
var result = new List<MyCustomRowStructure>();
// ..... insert custom logic here
return result;
}
}
// repeat up to however many formats you need
// then.....
public class ExcelToDatabaseImporter
{
public void ImportExcelFile(string filename, string clientName)
{
var myValidData = GetClientDataImporter(clientName).Import(filename);
StickMyDataToMyDatabase(myValidData); // this is where you would load the structure into the db... won't need to touch every time a new format is encountered
}
public IClientDataImporter GetClientDataImporter(string clientName)
{
switch (clientName):
case "ClientOne":
return new ClientOneImporter();
break;
case "ClientTwo":
return new ClientTwoImporter();
break;
default:
throw new ArgumentException("No importer for client");
break;
}
}
I would suggest you to maintain an xml configuration file for each excel file. The xml configuration has to read by a tool, may be a console application, and generate new CSV file, based on the xml configuration.
As XML configuration file can be easily edited by any text editor, users can update the same.
A little background. I am new to using C# in a professional setting. My experience is mainly in SQL. I have a file that I need to parse through to pull out certain pieces of information. I can figure out how to parse through each line, but have gotten stuck on searching for specific pieces of information. I am not interested in someone finishing this code for me. Instead, I am interested in pointers on where I can go from here.
Here is an example of the code I have written.
class Program
{
private static Dictionary<string, List<string>> _arrayLists = new Dictionary<string, List<string>>();
static void Main(string[] args)
{
string filePath = "c:\\test.txt";
StreamReader reader = new StreamReader(filePath);
string line;
while (null !=(line = reader.ReadLine()))
{
if (line.ToLower().Contains("disconnected"))
{
// needs to continue on search for Disconnected or Subscribed
}
else
{
if (line.ToLower().Contains("subscribed"))
{
// program needs to continue reading file
// looking for and assigning values to
// dvd, cls, jhd, dxv, hft
// records start at Subscribed and end at ;
}
}
}
}
}
A little bit of explanation of the file. I basically need to pull data existing between the word Subscribed and the first ; i come to. Specifically I need to take the values such as dvd = 234 and assign them to their same variables in the code. Not every record will have the same variables.
Here is an example of the text file that I need to parse through.
test information
annoying information
Subscribed more annoying info
more annoying info
dvd = 234,
cls = 453,
jhd = 567,
more annoying info
more annoying info
dxv = 456,
hft = 876;
more annoying info
test information
annoying information
Subscribed more annoying info
more annoying info
dvd = 234,
cls = 455,
more annoying info
more annoying info
dxv = 456,
hft = 876,
jjd = 768;
more annoying info
test information
annoying information
Disconnected more annoying info
more annoying info
more annoying info
Edit
My apologies on the vague question. I have to learn how to ask better questions.
My thought process was to make sure the program associated all the details between subscribed and the ; as one record. I think the part that I am confused on is in reading the lines. In my head I see the loop reading the line Subscribed, and then going into a method and reading the next line and assigning the value, and so on until it hits the ;. Once that was done I am trying to figure out how to tell the program to exit that method, but to continue reading from the line right after the semi-colon. Perhaps I am over thinking this.
I will take the advice I have been give and see what I can come up with to solve this. Thank you.
From you question as it is now it is not clear what specific problem you are struggling with. I'd suggest you edit your question providing specific challenges you'd like to overcome. currently you problem statement is "have gotten stuck on searching for specific pieces of information". This is as unspecific as it can get.
Having said that I'll try to help you.
First, you will never get into an if like that:
line.ToLower().Contains("Disconnected")
Here you convert all the characters to lower case, and then you are trying to find a substring with capital "D" in it. The expression above will (almost) always evaluate to false.
Secondly, in order for your application to do what you want to do it needs to track the current parsing state. I'm going to ignore the "Disconnected" bit now, as you have not shown what significance it has.
I'll be assuming that you are trying to find everything between Subscribed and first semicolon in the file. I'll also make a couple of other assumption regarding to what can constitute a string, which I won't list here. These can be wrong, but this is my best guess given the information you've provided.
You program will start in a state "looking for subscription". You already set up the read loop, which is good. In this loop you read lines of the file, and you find one that contains word Subscription.
Once you found such line your parser need to move to "parsing subscription" state. In this state, when you read lines you look for lines like jjd = 768, perhaps with a semicolon in the end. You can check if the line match a pattern by using Regular Expressions.
Regular Expressions also can divide match to capturing groups, so that you can extract the name (jjd) and the value (768) separately. Presences or absence of the semicolon could be another RegEx group.
Note that RegEx is not the only way to handle this, but this is the first that comes to mind.
You then keeping matching the lines to your regex and extracting names and values until you come across the semicolon, at which point you switch back to "looking for subscription" state.
You use the current state, to decide how to process the next read line.
You continue until the end of the file.
Generally you want to read up on parsing.
Hope this helps.
As with all code solutions to problems there are many possible ways to achieve what you are looking for. Some will work better then others. Below is one way that could help point you in the right direction.
You can check if the string starts with a keyword or value such as "dvd" (see MSDN String.StartsWith).
If it does then you can split the string into an array of parts (see MSDN String.Split).
You can then get the values of each part from the string array using the index of the value you want.
Do what you need to with the value retrieved.
Continue checking each line for your key business rules (ie. The semicolon that will end the section). Maybe you could check the last character of the string. (see String.EndsWith)
When processing text files containing semi-structured data, state variables can simplify the algorithm. In the code below, a boolean state variable isInRecord is used to track when a line is in a record.
using System;
using System.Collections.Generic;
using System.IO;
namespace ConsoleApplication19
{
public class Program
{
private readonly static String _testData = #"
test information
annoying information
Subscribed more annoying info
more annoying info
dvd = 234,
cls = 453,
jhd = 567,
more annoying info
more annoying info
dxv = 456,
hft = 876;
more annoying info
test information
annoying information
Subscribed more annoying info
more annoying info
dvd = 234,
cls = 455,
more annoying info
more annoying info
dxv = 456,
hft = 876,
jjd = 768;
more annoying info
test information
annoying information
Disconnected more annoying info
more annoying info
more annoying info";
public static void Main(String[] args)
{
/* Create a temporary file containing the test data. */
var testFile = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), Path.GetRandomFileName());
File.WriteAllText(testFile, _testData);
try
{
var p = new Program();
var records = p.GetRecords(testFile);
foreach (var kvp in records)
{
Console.WriteLine("Record #" + kvp.Key);
foreach (var entry in kvp.Value)
{
Console.WriteLine(" " + entry);
}
}
}
finally
{
File.Delete(testFile);
}
}
private Dictionary<String, List<String>> GetRecords(String path)
{
var results = new Dictionary<String, List<String>>();
var recordNumber = 0;
var isInRecord = false;
using (var reader = new StreamReader(path))
{
String line;
while ((line = reader.ReadLine()) != null)
{
line = line.Trim();
if (line.StartsWith("Disconnected"))
{
// needs to continue on search for Disconnected or Subscribed
isInRecord = false;
}
else if (line.StartsWith("Subscribed"))
{
// program needs to continue reading file
// looking for and assigning values to
// dvd, cls, jhd, dxv, hft
// records start at Subscribed and end at ;
isInRecord = true;
recordNumber++;
}
else if (isInRecord)
{
// Check if the line has a general format of "something = something".
var parts = line.Split("=".ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
if (parts.Length != 2)
continue;
// Update the relevant dictionary key, or add a new key.
List<String> entries;
if (results.TryGetValue(recordNumber.ToString(), out entries))
entries.Add(line);
else
results.Add(recordNumber.ToString(), new List<String>() { line });
// Determine if the isInRecord state variable should be toggled.
var lastCharacter = line[line.Length - 1];
if (lastCharacter == ';')
isInRecord = false;
}
}
}
return results;
}
}
}
This sort of ties back to a question I had earlier about a regex to search for a method containing a particular string, and someone suggested I use this MS tool called Roslyn but it's not available for VS2010 since 2012 came out.
So I'm writing this small utility to keep a list of every file in my solution that contains a particular method declaration (something like 3k of the 25k files overload this method). Then I simply want to filter that list of files to only ones that contain += inside the body of the method.
static void DirSearch(string dir)
{
string[] files = Directory.GetFiles(dir, "*.*", SearchOption.AllDirectories);
foreach (var file in files)
{
var contents = File.ReadAllText(file);
if (contents.Contains("void DetachEvents()"))
{
//IF DetachEvents CONTAINS += THEN...
WriteToFile(file);
}
}
}
This method iterates over all the folders and writes the file name to a text file if it contains the key method, but I have no idea how to extract just whatevers in the method body, since it's overloaded all 3K instances of the method are different.
Would the best approach to be get the index of the method name, then the index of each { and } until I encounter the next accessor modifier (signifying I've gotten to the end of DetachEvents)? Then I could just search between indexOfMethod and indexOfEndMethod for +=.
But it sounds really sloppy, I was hoping someone might have a better idea?
Do you have to do this in code? Is this a one time utility to identify the problem methods? Why not use something like Notepad++ and it's Find in Files capabilities. You can filter your find pretty easily and even apply regex (I think). From there you can copy the results which include the file name (i.e. someclassfile.cs) and get a list from there.
I wrote this really sloppy winform that lets the user type in the folder to the code base, the method name, and the flagrant text they're looking for. Then it loops over every file in the directory and calls this method on a string that contains all the text of the file. It returns true if the user-entered flagrant data is present, then the method that calls this adds the file its on to a list. Anyways, here's the major code:
private bool ContainsFlag(string contents)
{
int indexOfMethodDec = contents.IndexOf(_method);
int indexOfNextPublicMethod = contents.IndexOf("public", indexOfMethodDec);
if (indexOfNextPublicMethod == -1)
indexOfNextPublicMethod = int.MaxValue;
int indexOfNextPrivateMethod = contents.IndexOf("private", indexOfMethodDec);
if (indexOfNextPrivateMethod == -1)
indexOfNextPrivateMethod = int.MaxValue;
int indexOfNextProtectedMethod = contents.IndexOf("protected", indexOfMethodDec);
if (indexOfNextProtectedMethod == -1)
indexOfNextProtectedMethod = int.MaxValue;
int[] indeces = new int[3]{indexOfNextPrivateMethod,
indexOfNextProtectedMethod,
indexOfNextPublicMethod};
int closestToMethod = indeces.Min();
if (closestToMethod.Equals(Int32.MaxValue))
return false; //This should probably do something different.. This condition is true if the method you're reading is the last method in the class, basically
if (closestToMethod - indexOfMethodDec < 0)
return false;
string methodBody = contents.Substring(indexOfMethodDec, closestToMethod - indexOfMethodDec);
if (methodBody.Contains(_flag))
return true;
return false;
}
Plenty of room for improvement, this is mostly just a proof-of-concept thing that'll get used maybe twice per year internally. But for my purposes it worked. Should be a good starting-point for something more sophisticated if anyone needs it.
My project requires a file where I will store key/value pair data that should be able to be read and modified by the user. I want the program to just expect the keys to be there, and I want to parse them from the file as quickly as possible.
I could store them in XML, but XML is way to complex, and it would require traversing nodes, and child nodes and so on, all I want is some class that takes a file and generates key value pairs. I want as little error handling as possible, and I want it done with as little code as possible.
I could code a class like that myself, but I'd rather learn how it's don'e in the framework than inventing the wheel twice. Are there some built in magic class in .NET (3.5) that are able to do so?
MagicClass kv = new MagicClass("Settings.ini"); // It doesn't neccesarily have to be an INI file, it can be any simple key/value pair format.
string Value1 = kv.get("Key1");
...
If you're looking for a quick easy function and don't want to use .Net app\user config setting files or worry about serialization issues that sometimes occur of time.
The following static function can load a file formatted like KEY=VALUE.
public static Dictionary<string, string> LoadConfig(string settingfile)
{
var dic = new Dictionary<string, string>();
if (File.Exists(settingfile))
{
var settingdata = File.ReadAllLines(settingfile);
for (var i = 0; i < settingdata.Length; i++)
{
var setting = settingdata[i];
var sidx = setting.IndexOf("=");
if (sidx >= 0)
{
var skey = setting.Substring(0, sidx);
var svalue = setting.Substring(sidx+1);
if (!dic.ContainsKey(skey))
{
dic.Add(skey, svalue);
}
}
}
}
return dic;
}
Note: I'm using a Dictionary so keys must be unique, which is usually that case with setting.
USAGE:
var settingfile = AssemblyDirectory + "\\mycustom.setting";
var settingdata = LoadConfig(settingfile);
if (settingdata.ContainsKey("lastrundate"))
{
DateTime lout;
string svalue;
if (settingdata.TryGetValue("lastrundate", out svalue))
{
DateTime.TryParse(svalue, out lout);
lastrun = lout;
}
}
Use the KeyValuePair class for you Key and Value, then just serialize a List to disk with an XMLSerializer.
That would be the simplest approach I feel. You wouldn't have to worry about traversing nodes. Calling the Deserialize function will do that for you. The user could edit the values in the file if they wish also.
I don't know of any builtin class to parse ini file. I've used nini when needed to do so. It's licensed under the MIT/X11 license, so doesn't have any issue to be included in a closed source program.
It's very to use. So if you have a Settings.ini file formatted this way:
[Configuration]
Name = Jb Evain
Phone = +330101010101
Using it would be as simple as:
var source = new IniConfigSource ("Settings.ini");
var config = source.Configs ["Configuration"];
string name = config.Get ("Name");
string phone = config.Get ("Phone");
if you want the user to be able to read and modify the file, i suggest a comma-delimited pair, one per line
key1,value1
key2,value2
...
parsing is simple: read the file, split at newline or comma, then take the elements in pairs
Format the file this way:
key1=value1
key2=value2
Read the entire file into a string (there is a simple convenience function that does that, maybe in the File or string class), and call string.Split('='). Make sure you also call string.Trim() on each key and value as you traverse the list and pop each pair into a hashtable or dictionary.