I am Currently maintaining an application written in C#. There is a new feature I need to code and I have hit a wall.
I am pulling data from a database for a different application written in VB6. There is a field that holds data for a tree list. I need to recreate this tree in my application. The data looks like this:
{
{
table1~col_b^table1~colc^=
}|
{
{
table1~col_b^table2~col_b^=
}|{
table2~col_a^table3~cola^=
}|AND
}|OR
}
I don't even know where to start. What I need to accomplish is this. {} denote an expression, | separate expressions. basically this tree should look like this:
OR
-- table1~col_b^table1~colc^=
-- AND
---- table1~col_b^table2~col_b^=
---- table2~col_a^table3~cola^=
Any help or direction in solving this problem would be great!
Thanks,
Doug
When you would define a grammar I suggest you looking at Irony.net. It allows to write parsers very easily. Sample.
And this is rough grammar for your code:
[Language("ExpressionEvaluator", "1.0", "Multi-line expression evaluator")]
public class ExpressionEvaluatorGrammar : Grammar
{
public ExpressionEvaluatorGrammar()
{
// 1. Terminals
var identifier = new RegexBasedTerminal("identifier", "[a-z\\d_^~]+");
// 2. Non-terminals
var root = new NonTerminal("root");
var block = new NonTerminal("block");
var expression = new NonTerminal("expression");
var expressions = new NonTerminal("expressions");
var prop = new NonTerminal("prop");
var op = new NonTerminal("op");
// 3. BNF rules
op.Rule = ToTerm("OR") | "AND";
prop.Rule = identifier + "=" ;
expression.Rule = "{" + (prop | block) + "}" + "|" ;
expressions.Rule = MakeStarRule(expressions, expression);
block.Rule = expressions + op;
root.Rule = "{" + block +"}";
Root = root;
//automatically add NewLine before EOF so that our BNF rules work correctly when there's no final line break in source
this.LanguageFlags = LanguageFlags.NewLineBeforeEOF;
}
}
} //namespace
It parses ok, all you need to do is add AST tree and use it.
Without any additional libraries, it's not too hard to parse this into an expression tree in pure C# code:
class TreeNode
{
private enum ParseState
{
Operator,
Expression
}
public static TreeNode ParseTree(string treeData)
{
Stack<TreeNode> parsed = new Stack<TreeNode>();
StringBuilder nodeData = new StringBuilder();
ParseState state = ParseState.Operator;
for (int charIndex = 0; charIndex < treeData.Length; charIndex++)
{
switch (treeData[charIndex])
{
case '{':
nodeData.Clear();
state = ParseState.Expression;
break;
case '\t':
case ' ':
case '\r':
case '\n':
case '|':
// ignore whitespace and |
break;
case '}':
{
if (state == ParseState.Expression)
{
state = ParseState.Operator;
parsed.Push(new TreeNodeData(nodeData.ToString()));
}
else // Operator
{
TreeNodeOperators op = (TreeNodeOperators)(Enum.Parse(typeof(TreeNodeOperators), nodeData.ToString()));
TreeNodeExpression exp = new TreeNodeExpression();
exp.Operator = op;
exp.Right = parsed.Pop();
exp.Left = parsed.Pop();
parsed.Push(exp);
}
nodeData.Clear();
}
break;
default:
nodeData.Append(treeData[charIndex]);
break;
}
}
return parsed.Pop();
}
}
enum TreeNodeOperators
{
AND,
OR
}
class TreeNodeExpression : TreeNode
{
public TreeNodeOperators Operator {get; set;}
public TreeNode Left { get; set; }
public TreeNode Right { get; set; }
}
class TreeNodeData : TreeNode
{
public string Data {get; set;}
public TreeNodeData(string data)
{
Data = data;
}
}
You can use regexp to tokenize and a stack to parse recursively like this
internal class Node
{
public string Terminal { get; set; }
public List<Node> Operands { get; set; }
}
internal static readonly Regex TokensPattern = new Regex(#"(?<ws>\s+)|{\s*(?<value>[^\s}]+)\s*}|(?<token>OR|AND|.)", RegexOptions.Compiled);
static Node parseData(string str)
{
// init stack
var stack = new Stack<Node>();
stack.Push(new Node() { Operands = new List<Node>() });
// define parser
var parser = new Dictionary<string, Action<string>>();
parser.Add("{", _ => stack.Push(new Node() { Operands = new List<Node>() }));
parser.Add("}", _ => { var top = stack.Pop(); stack.Peek().Operands.Add(top); });
parser.Add("|", _ => { });
parser.Add("AND", _ => stack.Peek().Terminal = "AND");
parser.Add("OR", _ => stack.Peek().Terminal = "OR");
parser.Add("", value => stack.Peek().Operands.Add(new Node { Terminal = value }));
// execute parser
TokensPattern.Matches(str).Cast<Match>()
.Where(m => string.IsNullOrEmpty(m.Groups["ws"].Value))
.Count(m => { parser[m.Groups["token"].Value](m.Groups["value"].Value); return false; });
// return top of the tree
return stack.Peek().Operands[0];
}
static void Main(string[] args)
{
const string str = #"{
{
table1~col_b^table1~colc^=
}|
{
{
table1~col_b^table2~col_b^=
}|{
table2~col_a^table3~cola^=
}|{cccc}|AND
}|OR
}";
// print tree function
Action<int, Node> dump = null;
dump = new Action<int, Node>((level, node) =>
{
Console.WriteLine("{0}{1}", new string(' ', level * 2), node.Terminal);
if (node.Operands != null)
node.Operands.ForEach(el => dump(level + 1, el));
});
dump(0, parseData(str));
}
Related
I'm trying to figure out how to parse a string in this format into a tree like data structure of arbitrary depth.
and after that make random sentences.
"{{Hello,Hi,Hey} {world,earth},{Goodbye,farewell} {planet,rock,globe{.,!}}}"
where
, means or
{ means expand
} means collapse up to parent
for example, i want to get output like this:
1) hello world planet.
2) hi earth globe!
3) goodby planet.
and etc.
The input string must be parsed. Since it can contain nested braces, we need a recursive parser. But to begin with, we need a data model to represent the tree structure.
We can have three different types of items in this tree: text, a list representing a sequence and a list representing a choice. Let's derive three classes from this abstract base class:
abstract public class TreeItem
{
public abstract string GetRandomSentence();
}
The TextItem class simply returns its text as "random sentence":
public class TextItem : TreeItem
{
public TextItem(string text)
{
Text = text;
}
public string Text { get; }
public override string GetRandomSentence()
{
return Text;
}
}
The sequence concatenates the text of its items:
public class SequenceItem : TreeItem
{
public SequenceItem(List<TreeItem> items)
{
Items = items;
}
public List<TreeItem> Items { get; }
public override string GetRandomSentence()
{
var sb = new StringBuilder();
foreach (var item in Items) {
sb.Append(item.GetRandomSentence());
}
return sb.ToString();
}
}
The choice item is the only one using randomness to pick one random item from the list:
public class ChoiceItem : TreeItem
{
private static readonly Random _random = new();
public ChoiceItem(List<TreeItem> items)
{
Items = items;
}
public List<TreeItem> Items { get; }
public override string GetRandomSentence()
{
int index = _random.Next(Items.Count);
return Items[index].GetRandomSentence();
}
}
Note that the sequence and choice items both call GetRandomSentence() recursively on their items to descend the tree recursively.
This was the easy part. Now lets create a parser.
public class Parser
{
enum Token { Text, LeftBrace, RightBrace, Comma, EndOfString }
int _index;
string _definition;
Token _token;
string _text; // If token is Token.Text;
public TreeItem Parse(string definition)
{
_index = 0;
_definition = definition;
GetToken();
return Choice();
}
private void GetToken()
{
if (_index >= _definition.Length) {
_token = Token.EndOfString;
return;
}
switch (_definition[_index]) {
case '{':
_index++;
_token = Token.LeftBrace;
break;
case '}':
_index++;
_token = Token.RightBrace;
break;
case ',':
_index++;
_token = Token.Comma;
break;
default:
int startIndex = _index;
do {
_index++;
} while (_index < _definition.Length & !"{},".Contains(_definition[_index]));
_text = _definition[startIndex.._index];
_token = Token.Text;
break;
}
}
private TreeItem Choice()
{
var items = new List<TreeItem>();
while (_token != Token.EndOfString && _token != Token.RightBrace) {
items.Add(Sequence());
if (_token == Token.Comma) {
GetToken();
}
}
if (items.Count == 0) {
return new TextItem("");
}
if (items.Count == 1) {
return items[0];
}
return new ChoiceItem(items);
}
private TreeItem Sequence()
{
var items = new List<TreeItem>();
while (true) {
if (_token == Token.Text) {
items.Add(new TextItem(_text));
GetToken();
} else if (_token == Token.LeftBrace) {
GetToken();
items.Add(Choice());
if (_token == Token.RightBrace) {
GetToken();
}
} else {
break;
}
}
if (items.Count == 0) {
return new TextItem("");
}
if (items.Count == 1) {
return items[0];
}
return new SequenceItem(items);
}
}
It consists of a lexer, i.e., a low level mechanism to split the input text into tokens. We have have four kinds of tokens: text, "{", "}" and ",". We represent these tokens as
enum Token { Text, LeftBrace, RightBrace, Comma, EndOfString }
We also have added a EndOfString token to tell the parser that the end of the input string was reached. When the token is Text we store this text in the field _text. The lexer is implemented by the GetToken() method which has no return value and instead sets the _token field, to make the current token available in the two parsing methods Choice() and Sequence().
One difficulty is that when we encounter an item, we do not know whether it is a single item or whether it is part of a sequence or a choice. We assume that the whole sentence definition is a choice consisting of sequences, which gives sequences precedence over choices (like "*" has precedence over "+" in math).
Both Choice and Sequence gather items in a temporary list. If this list contains only one item, then this item will be returned instead of a choice list or a sequence list.
You can test this parser like this:
const string example = "{{Hello,Hi,Hey} {world,earth},{Goodbye,farewell} {planet,rock,globe{.,!}}}";
var parser = new Parser();
var tree = parser.Parse(example);
for (int i = 0; i < 20; i++) {
Console.WriteLine(tree.GetRandomSentence());
}
The output might look like this:
Goodbye rock
Hi earth
Goodbye globe.
Hey world
Goodbye rock
Hi earth
Hey earth
farewell planet
Goodbye globe.
Hey world
Goodbye planet
Hello world
Hello world
Goodbye planet
Hey earth
farewell globe!
Goodbye globe.
Goodbye globe.
Goodbye planet
farewell rock
I think that can be a complicated job, for that I used this tutorial, I strongly advice you to read the entire page to understand how this works.
First, you have to pass this "tree" as an array. You can parse the string, manually set the array or whatever. That's important because there isn't a good model for that tree model so it's better if you use a already available one. Also, it's important that if you want to set a correct grammar, you'll need to add "weight" to those words and tell the code how to correctly set and in what order.
Here is the code snippet:
using System;
using System.Text;
namespace App
{
class Program
{
static void Main(string[] args)
{
string tree = "{{Hello,Hi,Hey} {world,earth},{Goodbye,farewell} {planet,rock,globe{.,!}}}";
string[] words = { "Hello", "Hi", "Hey", "world", "earth", "Goodbye", "farewell", "planet", "rock", "globe" };
RandomText text = new RandomText(words);
text.AddContentParagraphs(12, 1, 3, 3, 3);
string content = text.Content;
Console.WriteLine(content);
}
}
public class RandomText
{
static Random _random = new Random();
StringBuilder _builder;
string[] _words;
public RandomText(string[] words)
{
_builder = new StringBuilder();
_words = words;
}
public void AddContentParagraphs(int numberParagraphs, int minSentences,
int maxSentences, int minWords, int maxWords)
{
for (int i = 0; i < numberParagraphs; i++)
{
AddParagraph(_random.Next(minSentences, maxSentences + 1),
minWords, maxWords);
_builder.Append("\n\n");
}
}
void AddParagraph(int numberSentences, int minWords, int maxWords)
{
for (int i = 0; i < numberSentences; i++)
{
int count = _random.Next(minWords, maxWords + 1);
AddSentence(count);
}
}
void AddSentence(int numberWords)
{
StringBuilder b = new StringBuilder();
// Add n words together.
for (int i = 0; i < numberWords; i++) // Number of words
{
b.Append(_words[_random.Next(_words.Length)]).Append(" ");
}
string sentence = b.ToString().Trim() + ". ";
// Uppercase sentence
sentence = char.ToUpper(sentence[0]) + sentence.Substring(1);
// Add this sentence to the class
_builder.Append(sentence);
}
public string Content
{
get
{
return _builder.ToString();
}
}
}
}
If the question is how to parse the text. I think maybe you can use the stack to parse it.
"{{Hello,Hi,Hey} {world,earth},{Goodbye,farewell} {planet,rock,globe{.,!}}}"
Basically, you push char in the stack when you read a char is not '}'. And when you get a '}', you pop from stack many time, until you reach a '{'.
But it has more details, because you have a rule ',' for OR.
The parsing is like do the calculation by stack. This is the way how you handle parenthesis for equation.
I have to create an indented navigation menu using below data from a .csv file:
ID;MenuName;ParentID;isHidden;LinkURL1;Company;NULL;False;/company2;About Us;1;False;/company/aboutus3;Mission;1;False;/company/mission4;Team;2;False;/company/aboutus/team5;Client 2;10;False;/references/client26;Client 1;10;False;/references/client17;Client 4;10;True;/references/client48;Client 5;10;True;/references/client510;References;NULL;False;/references
Using this data I have to develop an application that will parse the file and present the content in a console as the example below:
. Company.... About Us....... Team.... Mission. References.... Client 1.... Client 2
Menu items should be indented (depending on the parent), hidden items (isHidden==true) shouldn't be presented and items should be ordered alphabetically. So far I tried:
using (StreamReader sr = new StreamReader(#"file.csv"))
{
// Read the stream to a string, and write the string to the console.
string [] lines = sr.ReadToEnd().Split(/*';', */'\n');
for (int i = 1; i < lines.Length; i++)
{
Console.WriteLine($"String no {i} is : {lines[i-1]}");
}
}
With this i'm getting the lines but I'm stuck after that. I'm new in coding so any help will be appreciated :)
heres some code that should help you get off.
Working sample:
https://dotnetfiddle.net/L37Gjr
It first parses the data to a seperate object. This then gets used to build a m-ary tree, or a hierachical structure of connected nodes. (a node has a reference to 0 or more children).
https://en.wikipedia.org/wiki/M-ary_tree
Then tree traversal (use google if you need to know more) is used to insert and print the output, There is still something wrong however. it now uses level order traversal to print, this however comes up with an error:
Found root:1 - Company
Found root:10 - References
-------------------
1 - Company
2 - About Us
3 - Mission
4 - Team
10 - References
6 - Client 1
5 - Client 2
As you can see, it prints 4 - Team on the wrong level. I'll leave it to you to fix it (because i ran out of time), and if not i hope i gave you plenty ideas to go off and research on your own.
// sample for https://stackoverflow.com/questions/61395486/read-csv-file-and-return-indented-menu-c-sharp by sommmen
using System;
using System.Collections;
using System.Linq;
using System.Collections.Generic;
public class Program
{
public class Node<T>
{
public T Data {get;set;}
public List<Node<T>> Children { get; set;}
public Node()
{
Children = new List<Node<T>>();
}
// Tree traversal in level order
public List<Node<T>> LevelOrder()
{
List<Node<T>> list = new List<Node<T>>();
Queue<Node<T>> queue = new Queue<Node<T>>();
queue.Enqueue(this);
while(queue.Count != 0)
{
Node<T> temp = queue.Dequeue();
foreach (Node<T> child in temp.Children)
queue.Enqueue(child);
list.Add(temp);
}
return list;
}
public List<Node<T>> PreOrder()
{
List<Node<T>> list = new List<Node<T>>();
list.Add(this);
foreach (Node<T> child in Children)
list.AddRange(child.PreOrder());
return list;
}
public List<Node<T>> PostOrder()
{
List<Node<T>> list = new List<Node<T>>();
foreach (Node<T> child in Children)
list.AddRange(child.PreOrder());
list.Add(this);
return list;
}
}
public class Entity
{
public int id {get;set;}
public string menuName {get;set;}
public int? parentID {get;set;}
public bool isHidden {get;set;}
public string linkURL {get;set;}
}
public static void Main()
{
var data = #"ID;MenuName;ParentID;isHidden;LinkURL
1;Company;NULL;False;/company
2;About Us;1;False;/company/aboutus
3;Mission;1;False;/company/mission
4;Team;2;False;/company/aboutus/team
5;Client 2;10;False;/references/client2
6;Client 1;10;False;/references/client1
7;Client 4;10;True;/references/client4
8;Client 5;10;True;/references/client5
10;References;NULL;False;/references";
var lines = data.Split('\n');
var rootNodes = new List<Node<Entity>>();
var childItems = new List<Entity>();
// Parse the data to entities
// Items without a parent are used as rootnodes to build a tree
foreach(var row in lines.Skip(1))
{
var columns = row.Split(';');
var id = Convert.ToInt32(columns[0]);
var menuName = columns[1];
var parentID = ToNullableInt(columns[2]);
var isHidden = Convert.ToBoolean(columns[3]);
var linkURL = columns[4];
var entity = new Entity()
{
id = id,
menuName = menuName,
parentID = parentID,
isHidden = isHidden,
linkURL = linkURL
};
if(parentID == null)
{
Console.WriteLine("Found root:" + entity.id + " - " + entity.menuName);
rootNodes.Add(new Node<Entity>()
{
Data = entity
});
}
else
{
childItems.Add(entity);
}
}
// Add the childElements to their appropriate rootnode
foreach(var rootNode in rootNodes)
{
foreach(var childItem in childItems.OrderBy(a=>a.parentID).ThenBy(b=>b.menuName))
{
var newNode = new Node<Entity>()
{
Data = childItem
};
Insert(rootNode, newNode);
}
}
Console.WriteLine("-------------------");
foreach(var rootNode in rootNodes)
{
var indent = 0;
var previous = rootNode;
foreach(var node in rootNode.LevelOrder())
{
if(node.Data.isHidden) continue;
if(previous.Data.parentID != node.Data.parentID)
indent++;
for(var i = 0; i < indent; i++)
Console.Write("\t");
Console.WriteLine(node.Data.id + " - " + node.Data.menuName);
previous = node;
}
}
}
public static void Insert(Node<Entity> rootNode, Node<Entity> targetNode)
{
foreach(var current in rootNode.LevelOrder())
{
if(current.Data.id == targetNode.Data.parentID)
{
current.Children.Add(targetNode);
return;
}
}
}
public static int? ToNullableInt(string s)
{
int i;
if (int.TryParse(s, out i)) return i;
return null;
}
}
I've a text file and I've to read the text file and then I've to convert the file data in the form of a table.
The file is in this form
{KeyValuePair}
{
Key1 = Value1 {next}
Key2 = Value2 {next}
Key3 = Value3 {next}
Key4 = {KeyValuePair} {
KeyA = ValueA {next}
KeyB = ValueB {next}
KeyC = ValueC {next}
}
}
and I need a output like this
My logic code is Here
StreamReader reader = new StreamReader("C:\\Users\\Kaushik Kishore\\Documents\\TextToRead.txt");
string data = reader.ReadToEnd();
//string[] stringSeparater = new string[] { "{KeyValuePair}" };
//string[] getData = data.Split(stringSeparater, StringSplitOptions.None);
//string[] separater = new string[] { "{next}" };
//string[] nextSplit = data.Split(separater, StringSplitOptions.None);
string pattern = #"(=)|(next)|(KeyValuePair)|({)|(})";
string[] output = Regex.Split(data, pattern);
foreach (string one in output)
{
Response.Write(one);
}
so thing I'm facing the problem is how to write the actual logic to extract the desired string.
Next specifies that We have to change the row in the table. each time the next keyword will occur I've to post the data in new Row.
Thanks in Advance
EDIT
I've done some effort and write some code
This is printing the data fine now I want to know how to pass the data from controller to view. when the data is coming in loop parts.
public ActionResult Index()
{
StreamReader reader = new StreamReader("C:\\Users\\Kaushik Kishore\\Documents\\Text2.txt");
string data = reader.ReadToEnd();
// replacing all tabs white space new line and everything
string trimmedData = Regex.Replace(data, #"\s", "");
string pattern = #"({next})|({KeyValuePair}{)|(}{next})";
string[] output = Regex.Split(trimmedData, pattern);
int length = output.Length;
int count = 0;
foreach (string one in output)
{
count++;
if (one == "{KeyValuePair}{")
{
Response.Write("Table Create</br>");
}
else if (count == length)
{
string[] last = one.Split('=');
foreach (string lastVal in last)
{
Response.Write(lastVal.Substring(0,lastVal.Length-1));
Response.Write('|');
}
}
else
{
string[] keyVal = one.Split('=');
foreach (string val in keyVal)
{
if (val == "{next}")
{
Response.Write("</br>");
}
else if (val == "}{next}")
{
Response.Write("Subtable End</br>");
}
else if (val == "}")
{
Response.Write("");
}
else
{
Response.Write(val);
Response.Write("|");
}
}
}
}
reader.Close();
return View();
}
This will be your controller part
public ActionResult Index()
{
ViewBag.DisplayTable = GetKeyValueDisplayContent(#"YourFilePath.Txt");
return View();
}
private string GetKeyValueDisplayContent(string fileToRead)
{
// 01 Get Data
string DataToProcess = GetDataToProcess(fileToRead);
// 02 Cleaning Data (replacing all tabs white space new line and everything)
DataToProcess = CleanDataToProcess(DataToProcess);
// 03 Retrieve Array from Data format
string[] output = GetDataInArray(DataToProcess);
// 04 Displaying Result
string DrawTable = GetDisplayHTML(output);
return DrawTable;
}
private string GetDataToProcess(string fileToRead)
{
StreamReader reader = new StreamReader(fileToRead);
string data = reader.ReadToEnd();
reader.Close();
return data;
}
private string CleanDataToProcess(string dataToProcess)
{
return Regex.Replace(dataToProcess, #"\s", "");
}
private string[] GetDataInArray(string dataToProcess)
{
string pattern = #"({next})|({KeyValuePair}{)|(}{next})";
string[] output = Regex.Split(dataToProcess, pattern);
return output;
}
private string GetDisplayHTML(string[] output)
{
int length = output.Length;
int count = 0;
StringBuilder OutputToPrint = new StringBuilder();
foreach (string one in output)
{
if (one == "{KeyValuePair}{")
{
count++;
if (count >= 2)
{
OutputToPrint.Append("<td><table border = \"1\">");
}
else
{
OutputToPrint.Append("<table border = \"1\">");
}
}
else if (one.Contains("=") == true)
{
string[] keyVal = Regex.Split(one, #"=");
OutputToPrint.Append("<tr>");
foreach (string val in keyVal)
{
if (val != "")
{
OutputToPrint.Append("<td>");
OutputToPrint.Append(WebUtility.HtmlEncode(val));
OutputToPrint.Append("</td>");
}
}
}
else if (one.Equals("{next}"))
{
OutputToPrint.Append("</tr>");
}
else if (one.Contains("}{next}") == true)
{
OutputToPrint.Append("</table></td>");
}
else if (one == "}")
{
OutputToPrint.Append("</table>");
}
else { }
}
return OutputToPrint.ToString();
}
This will be the View
<div>
#Html.Raw(ViewBag.DisplayTable)
</div>
Hope You'll find this well
if you use this little pattern, and if you use it recursively on the value capturing group, I think you can obtain what you want:
string pattern = #"(?>\s*(?<key>[^\s=]+)\s*=\s*|^\s*)(?>{KeyValuePair}\s*{\s*(?<value>(?>(?<c>{)|(?<-c>})|[^{}]+)+(?(c)(?!)))\s*}|(?<value>[^\s{]+)\s*(?<next>{next})\s*)";
pattern details:
(?> # possible begin of the match
\s*(?<key>[^\s=]+)\s*=\s* # a keyname
| # OR
^\s* # the start of the string
)
(?>
# case KeyValuePair #
{KeyValuePair} \s* { \s*
(?<value>
(?>(?<c>{)|(?<-c>})|[^{}]+)+ (?(c)(?!)) # content inside balanced curly brackets*
)
\s* }
| OR
# case next #
(?<value>
[^\s{]+ # all that is not a white character or an opening curly bracket
)
\s*
(?<next> {next} )\s* # the goal of this capture is to know in which case you are
)
(*)You can find more explanations about balancing groups here: What are regular expression Balancing Groups?
The idea is to write a recursive method that will call itself when the pattern matches the "keyValuePair" case. In the "next" case, the method records only the key/value in an array (or this kind of structure). The method must return this kind of array.
I created a parser-based solution that outputs a dictionary containing the key value pairs. It can nest {KeyValuePair}s as deep as you want.
Use like this:
string data = File.ReadAllText("data.txt");
var p = new Parser(text);
Dictionary<string, Value> dictionary = p.Parse();
A value can be either a string or a dictionary:
public abstract class Value { }
public class StringValue : Value
{
public string Value { get; private set; }
public StringValue(string value)
{
this.Value = value;
}
}
public class DictionaryValue : Value
{
public Dictionary<string, Value> Values { get; private set; }
public DictionaryValue()
{
this.Values = new Dictionary<string, Value>();
}
}
This allows for error reporting:
public class ParseError : Exception
{
public ParseError(string message)
: base(message) { }
}
The parser consists of two things.
The tokenizer, which transforms the input text to a stream of tokens:
KeyValuePair, OpenBracket, KeyOrValue(Key1) , Assign,
KeyOrValue(Value1) , Next, KeyOrValue(Key2) , Assign,
KeyOrValue(Value2) , Next, KeyOrValue(Key3) , Assign,
KeyOrValue(Value3) , Next, KeyOrValue(Key4) , Assign, KeyValuePair,
OpenBracket, KeyOrValue(KeyA) , Assign, KeyOrValue(ValueA) , Next,
KeyOrValue(KeyB) , Assign, KeyOrValue(ValueB) , Next, KeyOrValue(KeyC)
, Assign, KeyOrValue(ValueC) , Next, CloseBracket, CloseBracket, End
And then the parser, which transforms the token stream into a dictionary.
Here is the complete code:
public class Parser
{
private Tokenizer tk;
public Parser(string text)
{
this.tk = new Tokenizer(text);
}
public Dictionary<string, Value> Parse()
{
Stack<Dictionary<string, Value>> dictionaries = new Stack<Dictionary<string, Value>>();
Token t;
while ((t = tk.ReadToken()) != Token.End)
{
switch (t)
{
case Token.KeyValuePair:
t = tk.ReadToken();
if (t != Token.OpenBracket)
throw new ParseError("{KeyValuePair} should be followed by a '{'");
dictionaries.Push(new Dictionary<string, Value>());
break;
case Token.CloseBracket:
if (dictionaries.Count > 1)
dictionaries.Pop();
break;
case Token.KeyOrValue:
string key = tk.TokenValue;
t = tk.ReadToken();
if (t != Token.Assign)
throw new ParseError("Key should be followed by a '='");
t = tk.ReadToken();
if (t == Token.KeyValuePair)
{
var value = new DictionaryValue();
dictionaries.Peek().Add(key, value);
dictionaries.Push(value.Values);
}
else if (t != Token.KeyOrValue)
throw new ParseError("Value expected after " + key + " =");
else
{
string value = tk.TokenValue;
dictionaries.Peek().Add(key, new StringValue(value));
t = tk.ReadToken();
if (t != Token.Next)
throw new ParseError("{next} expected after Key value pair (" + key + " = " + value + ")");
}
break;
case Token.Error:
break;
default:
break;
}
}
return dictionaries.Peek();
}
private class Tokenizer
{
private string _data;
private int currentIndex = 0;
private string tokenValue;
public string TokenValue
{
get { return tokenValue; }
}
public Tokenizer(string data)
{
this._data = data;
}
public Token ReadToken()
{
tokenValue = string.Empty;
if (currentIndex >= _data.Length) return Token.End;
char c = _data[currentIndex];
if (char.IsWhiteSpace(c))
{
currentIndex++;
return ReadToken();
}
else if (c == '{')
{
if (TryReadBracketedToken("KeyValuePair"))
{
currentIndex++;
return Token.KeyValuePair;
}
else if (TryReadBracketedToken("next"))
{
currentIndex++;
return Token.Next;
}
else
{
currentIndex++;
return Token.OpenBracket;
}
}
else if (c == '}')
{
currentIndex++;
return Token.CloseBracket;
}
else if (c == '=')
{
currentIndex++;
return Token.Assign;
}
else
{
StringBuilder valueBuilder = new StringBuilder();
while (currentIndex < _data.Length && !char.IsWhiteSpace(c))
{
valueBuilder.Append(c);
currentIndex++;
c = _data[currentIndex];
}
tokenValue = valueBuilder.ToString();
return Token.KeyOrValue;
}
}
private bool TryReadBracketedToken(string token)
{
bool result = _data.Length > currentIndex + token.Length + 2
&& _data.Substring(currentIndex + 1, token.Length + 1) == token + "}";
if (result)
{
currentIndex++;
currentIndex += token.Length;
}
return result;
}
}
private enum Token
{
KeyValuePair,
Next,
OpenBracket,
CloseBracket,
Assign,
KeyOrValue,
End,
Error
}
}
public abstract class Value { }
public class StringValue : Value
{
public string Value { get; private set; }
public StringValue(string value)
{
this.Value = value;
}
}
public class DictionaryValue : Value
{
public Dictionary<string, Value> Values { get; private set; }
public DictionaryValue()
{
this.Values = new Dictionary<string, Value>();
}
}
public class ParseError : Exception
{
public ParseError(string message)
: base(message) { }
}
Lets say I have a unit test similar to the below, is there a way to write one unit test rather than several but also avoid having a for loop in the unit test?
[Test]
public void RunTestWithMultipleOptions()
{
MyClass code = new MyClass();
code.Prefix = "{DS1}"; //Options are {DS1}, {DS2}, {DS3}, {DS4}
//Property could be set to
//code.Prefix = "{DS1}{DS2}";
//code.Prefix = "{DS1}{DS2}{DS3}";
//And so on
//Based on how many {DS} used a method needs calling
code.InputDataStore(1,"Data1");
//If used {DS1}{DS2} in Prefix then
//code.InputDataStore(1,"Data1");
//code.InputDataStore(2,"Data2");
//If used {DS1}{DS2}{DS3} in Prefix then
//code.InputDataStore(1,"Data1");
//code.InputDataStore(2,"Data2");
//code.InputDataStore(3,"Data3");
string OutputData = String.Empty;
code.Output += delegate(int Id, string Data)
{
if (Id == (int)OutputsEnum.OutputModified)
OutputData = Data;
};
//Call the input method which will raise the Output event which we can assert against
code.Input("hi there");
//Assert that output has replace the prefix {DS} with the data in the datastorecontent list
Assert.AreEqual("Data1hi there", OutputData);
}
I can pass in the property value to the unit test method and use test cases but based on what the property is MyMethod needs to be called x number of times. Without putting a loop in the test I can't think of a way without having all the permetations as separate unit tests.
UPDATE: Here is the main contents of the class:
public event Action<int, string> Output;
public string Prefix { get; set; }
public string Postfix { get; set; }
private List<string> DataStoreContents = new List<string>() { "", "", "", "" };
public void Input(string Data)
{
if (Output != null)
{
if (!String.IsNullOrEmpty(Prefix))
{
Prefix = Prefix.Replace("{DS1}", DataStoreContents[0]);
Prefix = Prefix.Replace("{DS2}", DataStoreContents[1]);
Prefix = Prefix.Replace("{DS3}", DataStoreContents[2]);
Prefix = Prefix.Replace("{DS4}", DataStoreContents[3]);
}
if (!String.IsNullOrEmpty(Postfix))
{
Postfix = Postfix.Replace("{DS1}", DataStoreContents[0]);
Postfix = Postfix.Replace("{DS2}", DataStoreContents[1]);
Postfix = Postfix.Replace("{DS3}", DataStoreContents[2]);
Postfix = Postfix.Replace("{DS4}", DataStoreContents[3]);
}
Output((int)OutputsEnum.OutputBeforeModified, Data);
Output((int)OutputsEnum.OutputModified, Prefix + Data + Postfix);
Output((int)OutputsEnum.OutputAfterModified, Data);
}
}
}
public void InputDataStore(int DataStore, string Data)
{
if (DataStore < 1 || DataStore > 4)
throw new ArgumentOutOfRangeException("Datastore number out of range");
DataStoreContents[DataStore - 1] = Data;
}
}
I want to test that when I call InputDataStore(1,"MyData1"); InputDataStore(2, "MyData"); that Output does in fact replace the relevant {DS1} values with the relevant string and also combine it with any other {DS} values
One options is to test each call apart and all of them together. If you test A&B&C, you can limit yourself to testing A,B,C apart and A&B&C together. One option is the next code(made some assumptions):
[TestFixture]
public class ToTestFixture
{
[SetUp]
public void SetUp()
{
_instance = new ToTest();
_instance.InputDataStore(1, "1");
_instance.InputDataStore(2, "2");
_instance.InputDataStore(3, "3");
_instance.InputDataStore(4, "4");
}
private ToTest _instance;
[TestCase("{DS1}","1")]
[TestCase("{DS2}", "2")]
[TestCase("{DS3}", "3")]
[TestCase("{DS4}", "4")]
[TestCase("{DS1}{DS2}{DS3}{DS4}", "1234")]
[Test]
public void TestPrefixReplacements(string input, string expectedResult)
{
_instance.Prefix = input;
//Call the input method which will raise the Output event which we can test
_instance.Input("Any string goes here as we test only prefix." );
Assert.AreEqual(expectedResult, _instance.Prefix);
}
}
internal enum OutputsEnum
{
OutputBeforeModified,
OutputModified,
OutputAfterModified
}
public class ToTest
{
public event Action<int, string> Output = (x, result) => Console.WriteLine(x.ToString() + result);
public string Prefix { get; set; }
public string Postfix { get; set; }
private List<string> DataStoreContents = new List<string>() {"1", "2", "3", "4"};
public void Input(string Data)
{
if (Output != null)
{
if (!String.IsNullOrEmpty(Prefix))
{
Prefix = Prefix.Replace("{DS1}", DataStoreContents[0]);
Prefix = Prefix.Replace("{DS2}", DataStoreContents[1]);
Prefix = Prefix.Replace("{DS3}", DataStoreContents[2]);
Prefix = Prefix.Replace("{DS4}", DataStoreContents[3]);
}
if (!String.IsNullOrEmpty(Postfix))
{
Postfix = Postfix.Replace("{DS1}", DataStoreContents[0]);
Postfix = Postfix.Replace("{DS2}", DataStoreContents[1]);
Postfix = Postfix.Replace("{DS3}", DataStoreContents[2]);
Postfix = Postfix.Replace("{DS4}", DataStoreContents[3]);
}
Output((int) OutputsEnum.OutputBeforeModified, Data);
Output((int) OutputsEnum.OutputModified, Prefix + Data + Postfix);
Output((int) OutputsEnum.OutputAfterModified, Data);
}
}
public void InputDataStore(int DataStore, string Data)
{
if (DataStore < 1 || DataStore > 4)
throw new ArgumentOutOfRangeException("Datastore number out of range");
DataStoreContents[DataStore - 1] = Data;
}
}
Anyhow I feel there is a bond between "DS1" and the index of the array. (1-0, 2-1). This means next refactoring is possible:
Prefix = Prefix.Replace("{DS"+index+"}", DataStoreContents[index-1]);
More than that I guess output action decision is strange and two if-s are duplicate code. This is what I mean:
public void Input(string Data)
{
if (Output != null)
{
Prefix = ApplyReplaceRules(Prefix);
Postfix = ApplyReplaceRules(Postfix);
Output((int) OutputsEnum.OutputBeforeModified, Data);
Output((int) OutputsEnum.OutputModified, Prefix + Data + Postfix);
Output((int) OutputsEnum.OutputAfterModified, Data);
}
}
private string ApplyReplaceRules(string patternString)
{
if (!String.IsNullOrEmpty(Postfix))
{
patternString = patternString.Replace("{DS1}", DataStoreContents[0]);
patternString = patternString.Replace("{DS2}", DataStoreContents[1]);
patternString = patternString.Replace("{DS3}", DataStoreContents[2]);
patternString = patternString.Replace("{DS4}", DataStoreContents[3]);
}
return patternString;
}
I have a set of enumeration values (fault codes to be precise). The code is a 16 bit unsigned integer. I am looking for a data structure that could represent such an enumeration. A similar question has been asked here: What's the best C# pattern for implementing a hierarchy with an enum?. But this hierarchy is deeper.
Sample enumeration values
Current = 0x2000,
Current_DeviceInputSide = 0x2100,
ShortToEarth = 0x2120,
ShortToEarthInPhase1 = 0x2121,
ShortToEarthInPhase2 = 0x2122,
ShortToEarthInPhase3 = 0x2123
Use case
When the user provides a code then the UI has to display the equivalent meaning of the code with the hierarchy.
For example, if the user provides a value 0x2121 then the UI has to display Short to earth in phase 1 in the current at device input side. The best way to represent this is by using a hierarchical notation: Current : DeviceInputSide : ShortToEarth : ShortToEarthInPhase1.
Competing approaches
I have three competing approaches to represent the enumeration:
Create an enumeration at each level of the hierarchy. Then use a controller class to resolve the name.
Store the enumeration values in an xml and use LINQ to generate the meaning of the code.
Store the enumeration values in an xml. During the application startup. Create a singleton instance to retrieve the meaning. The instance contains a dictionary populated with the values from the xml.
Approach 1
The enumerations:
enum WarnCodes
{
None= 0x000,
Current = 0x2000
}
enum WarnCodes_Current
{
DeviceInputSide = 0x2100,
DeviceOutputSide = 0x2200
}
enum WarnCodes_Current_DeviceInputSide
{
ShortToEarth = 0x2120,
ShortCircuit = 0x2130
}
enum WarnCodes_Current_DeviceInputSide_ShortToEarth
{
InPhase1 = 0x2121,
InPhase2 = 0x2122
}
The controller:
public string GetMeaning(int code)
{
int bitMask = 0xF000;
int maskedCode = bitMask & code;
StringBuilder meaning = new StringBuilder();
switch (maskedCode)
{
case WarnCodes.Current:
meaning.Append("Current : ");
bitMask = 0xFF00;
maskedCode = bitMask & code;
switch (maskedCode)
{
case WarnCodes_Current.DeviceInputSide:
meaning.Append("Current : Device Input Side :");
...
break;
}
break;
...
}
}
Approach 2
The xml to store the enumeration values looks like this
<WarnCodes>
<code hex="2000" meaning="Current">
<code hex="2100" meaning="Current, Device Input side">
<code hex="2120" meaning="Short to Earth">
<code hex="2121" meaning="Short to earth in Phase L1"/>
<code hex="2122" meaning="Short to earth in Phase L2"/>
</code>
</code>
</code>
</WarnCodes>
And the method used to query the codes is:
XElement rootElement = XElement.Load(settingsFilePath);
public string GetHierarchicalMeaning(int code)
{
XElement rootElement = XElement.Load(warnCodesFilePath);
List<string> meanings = new List();
StringBuilder stringBuilder = new StringBuilder();
IEnumerable<XElement> elements;
elements = from el in rootElement.Descendants("code")
where (string)el.Attribute("hex") == code.ToString("X")
select el;
XElement element = elements.First();
while (element.Parent != null)
{
meanings.Add(element.Attribute("meaning").Value);
element = element.Parent;
}
meanings.Reverse();
foreach (string meaning in meanings)
{
stringBuilder.AppendFormat("{0} : ", meaning);
}
return stringBuilder.ToString().Trim().TrimEnd(':').Trim();
}
Approach 3
The xml to store the enumeration values is same as in Approach 2. The dictionary is populated from the xml by GetChildren().
private Dictionary<int, WarnCodeValue> warnCodesDictionary;
public void Initialize()
{
XElement rootElement = XElement.Load(settingsFilePath);
warnCodesDictionary = GetChildren(rootElement);
}
private Dictionary<int, WarnCodeValue> GetChildren(XElement element)
{
if (element.Descendants().Count() > 0)
{
Dictionary<int, WarnCodeValue> childNodeDictionary = new Dictionary();
foreach (XElement childElement in element.Elements())
{
int hex = Convert.ToInt32(childElement.Attribute("hex").Value, 16);
string meaning = childElement.Attribute("meaning").Value;
Dictionary<int, WarnCodeValue> dictionary = GetChildren(childElement);
WarnCodeValue warnCodeValue;
if (dictionary == null)
{
warnCodeValue = new WarnCodeValue() {Meaning = meaning};
}
else
{
warnCodeValue = new WarnCodeValue() {Meaning = meaning, ChildNodes = dictionary};
}
childNodeDictionary.Add(hex, warnCodeValue);
}
return childNodeDictionary;
}
return null;
}
The meanings are retrieved using GetHierarchicalMeaning():
public string GetHierarchicalMeaning(int code)
{
StringBuilder stringBuilder = new StringBuilder();
int firstLevel = code & 0xF000;
int secondLevel = code & 0xFF00;
int thirdLevel = code & 0xFFF0;
if(warnCodesDictionary.ContainsKey(firstLevel))
{
stringBuilder.AppendFormat("{0} : ", warnCodesDictionary[firstLevel].Meaning);
if (warnCodesDictionary[firstLevel].ChildNodes != null &&
warnCodesDictionary[firstLevel].ChildNodes.ContainsKey(secondLevel))
{
stringBuilder.AppendFormat("{0} : ", warnCodesDictionary[firstLevel].ChildNodes[secondLevel].Meaning);
if (warnCodesDictionary[firstLevel].ChildNodes[secondLevel].ChildNodes != null &&
warnCodesDictionary[firstLevel].ChildNodes[secondLevel].ChildNodes.ContainsKey(thirdLevel))
{
stringBuilder.AppendFormat("{0} : ",
warnCodesDictionary[firstLevel].ChildNodes[secondLevel].ChildNodes[thirdLevel].Meaning);
if (warnCodesDictionary[firstLevel].ChildNodes[secondLevel].ChildNodes[thirdLevel].ChildNodes != null &&
warnCodesDictionary[firstLevel].ChildNodes[secondLevel].ChildNodes[thirdLevel].ChildNodes.ContainsKey(code))
{
stringBuilder.AppendFormat("{0} : ",
warnCodesDictionary[firstLevel].ChildNodes[secondLevel].ChildNodes[thirdLevel].ChildNodes[code].Meaning);
}
}
}
}
}
The WarnCodeValue class:
class WarnCodeValue
{
public string Meaning
{ get; set; }
public Dictionary<int, WarnCodeValue> ChildNodes { get; set; }
}
Questions
Which of the above 3 approaches is better from a performance point of view?
Are there any other approaches for representing the enumeration?
Any improvements to the code?
Consider using classes instead of enums, you then use a singleton for each value and can use the type system to build a tree, including virtual methods to produce error txt etc. (This can sometimes be a good option, but can also lead you into lots of problems if it does not fit well)
You could use FlagsAttribute.
For instance you could do something like this:
[FlagsAttribute]
enum WarnCodes
{
None= 0x0000,
Current = 0x2000,
// second level of hierarchy
DeviceInputSide = 0x0100,
DeviceOutputSide = 0x0200,
// third level of hierarchy
ShortToEarth = 0x0020,
ShortCircuit = 0x0030,
// fourth level of hierarchy
InPhase1 = 0x0001,
InPhase2 = 0x0002
}
You can test it like this:
int[] testVals = {0x0000, 0x2000, 0x2130, 0x2122, 0x2121, 0x2131};
foreach(var val in testVals)
{
Console.WriteLine( "{0,4:X} - {1}",
val, ( (WarnCodes)val ).ToString( ) );
}
Second attempt... You could implement your own tree structure where each node has a single-digit hexadecimal representation and a code like 0x2121 represents a branch of the tree:
>2 - (current)
/ \
(device input side)>1 2 (device output side)
/\ /\
>2 (short to earth)
/\
>1 (in phase 1)
So, to read what 0x2121 means, we follow the corresponding branch of the tree and (for each node) we read the message it contains.
Here's a quick and dirty implementation of the tree:
public class TreeNode
{
private List<TreeNode> _children;
public int hex {get; private set;}
public string meaning {get; private set;}
public IList<TreeNode> children {
get{
return _children.AsReadOnly();
}
}
public TreeNode(int hex, string meaning)
{
this.hex = hex;
this.meaning = meaning;
_children = new List<TreeNode>();
}
public TreeNode addChild(int hex, string meaning)
{
if(hex<=0 || hex >=16) throw new ArgumentOutOfRangeException("hex");
if(GetChildByCode(hex)!=null) throw new Exception("a child with code " +
hex.ToString() + " already exists");
var child = new TreeNode(hex,meaning);
_children.Add(child);
return child;
}
public TreeNode TryAddChild(int hex, string meaning)
{
if(hex<=0 || hex >=16) throw new ArgumentOutOfRangeException("hex");
var chd = GetChildByCode(hex);
if(chd==null) {
chd = new TreeNode(hex,meaning);
_children.Add(chd);
}
return chd;
}
public void AddBranch(int hexPath, string[] meanings)
{
var lst = intToList(hexPath,16,new LinkedList<int>()).ToList();
var curNode = this;
for(int i = 0; i<lst.Count; i++)
{
curNode = curNode.TryAddChild(lst[i], meanings[i]);
}
}
public TreeNode GetChildByCode(int hex)
{
return
(from c in _children
where c.hex == hex
select c).SingleOrDefault();
}
public string getMessagesByPath(int hexPath)
{
var lst = intToList(hexPath,16,new LinkedList<int>());
var msgs = getMessagesByPath(lst, new List<string>(),this);
return
(msgs == null || msgs.Count==0) ?
"None":
msgs.Aggregate((s1, s2) => s1 + ": " + s2);
}
// recursively follow the branch and read the node messages
protected IList<string> getMessagesByPath(LinkedList<int> hexPath, IList<string> accString, TreeNode curNode)
{
if(hexPath.Count == 0 || hexPath.First.Value == 0 || curNode==null)
return accString;
else
{
var chd = curNode.GetChildByCode(hexPath.First.Value);
string meaning = (chd==null)? "not found": chd.meaning;
accString.Add(meaning);
hexPath.RemoveFirst();
return getMessagesByPath(hexPath,accString,chd);
}
}
// convert the code to a list of digits in the given base (in this case 16)
// this could be an extension method for int
private LinkedList<int> intToList(int theInt, int theBase, LinkedList<int> acc)
{
if(theInt < theBase)
{
acc.AddFirst(theInt);
return acc;
}
else
{
acc.AddFirst(theInt % theBase);
return intToList(theInt/theBase, theBase, acc);
}
}
}
you can populate the tree this way:
var root = new TreeNode(0,"root");
root.AddBranch(0x2121, new string[] {"Current", "DeviceInputSide", "Short to Earth", "In phase I"});
root.AddBranch(0x2122, new string[] {"Current", "DeviceInputSide", "Short to Earth", "In phase II"});
root.AddBranch(0x2123, new string[] {"Current", "DeviceInputSide", "Short to Earth", "In phase III"});
root.AddBranch(0x2221, new string[] {"Current", "DeviceOutputSide", "Short to Earth", "In phase I"});
root.AddBranch(0x2222, new string[] {"Current", "DeviceOutputSide", "Short to Earth", "In phase II"});
root.AddBranch(0x2223, new string[] {"Current", "DeviceOutputSide", "Short to Earth", "In phase III"});
// ...
this way you get total control over the hierarchical structure of your codes and can implement checks so that the structure itself cannot be corrupted. Searching a message remains easy and (since it does not process a code after the first 0), a search for 0x2000 should be more efficient because only the 2 is actually processed.
//search meaning of path
root.getMessagesByPath(0x2122)
Found that a modified version of Approach 3 is most suitable. Thanks to #paolo for helping me come up with the answer.
Modified Approach 3
The xml containing the codes:
<?xml version="1.0" encoding="utf-8" ?>
<WarnCodes>
<code hex="2000" meaning="Current">
<code hex="2100" meaning="Current, Device Input side">
<code hex="2120" meaning="Short to Earth">
<code hex="2121" meaning="Short to earth in Phase L1"/>
<code hex="2122" meaning="Short to earth in Phase L2"/>
</code>
</code>
</code>
<code hex="3000" meaning="Voltage"/>
</WarnCodes>
The WarnCodeValue class:
class WarnCodeValue
{
public string Meaning
{ get; set; }
public string ConcatenatedMeaning
{ get; set; }
public Dictionary<int, WarnCodeValue> ChildNodes
{ get; set; }
}
The singleton processor class (to retrieve the meaning of a code):
sealed class WarnCodeProcessor
{
private static Dictionary<int, WarnCodeValue> warnCodesDictionary;
private static volatile WarnCodeProcessor _instance;
private static object instanceLockCheck = new object();
public static WarnCodeProcessor Instance
{
get
{
lock (instanceLockCheck)
{
if (_instance == null)
{
_instance = new WarnCodeProcessor();
}
}
return _instance;
}
}
private WarnCodeProcessor()
{
warnCodesDictionary = new Dictionary<int, WarnCodeValue>();
string currentDirectory = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
string settingsFilePath = Path.Combine(currentDirectory, "WarnCodes.xml");
XElement rootElement = XElement.Load(settingsFilePath);
warnCodesDictionary = GetChildren(rootElement, string.Empty);
}
public string GetConcatenatedMeaning(int code)
{
string concatenatedMeaning = string.Empty;
int firstLevel = code & 0xF000;
int secondLevel = code & 0xFF00;
int thirdLevel = code & 0xFFF0;
if (warnCodesDictionary.ContainsKey(firstLevel))
{
concatenatedMeaning = warnCodesDictionary[firstLevel].ConcatenatedMeaning;
if (warnCodesDictionary[firstLevel].ChildNodes != null &&
warnCodesDictionary[firstLevel].ChildNodes.ContainsKey(secondLevel))
{
concatenatedMeaning =
warnCodesDictionary[firstLevel].
ChildNodes[secondLevel].ConcatenatedMeaning;
if (warnCodesDictionary[firstLevel].ChildNodes[secondLevel].ChildNodes != null &&
warnCodesDictionary[firstLevel].ChildNodes[secondLevel].ChildNodes.ContainsKey(thirdLevel))
{
concatenatedMeaning =
warnCodesDictionary[firstLevel].
ChildNodes[secondLevel].
ChildNodes[thirdLevel].ConcatenatedMeaning;
if (warnCodesDictionary[firstLevel].ChildNodes[secondLevel].ChildNodes[thirdLevel].ChildNodes != null &&
warnCodesDictionary[firstLevel].ChildNodes[secondLevel].ChildNodes[thirdLevel].ChildNodes.ContainsKey(code))
{
concatenatedMeaning =
warnCodesDictionary[firstLevel].
ChildNodes[secondLevel].
ChildNodes[thirdLevel].
ChildNodes[code].ConcatenatedMeaning;
}
}
}
}
return concatenatedMeaning;
}
private static Dictionary<int, WarnCodeValue> GetChildren(XElement element, string concatenatedMeaning)
{
string elementMeaning = string.Empty;
XAttribute attribute = element.Attribute("meaning");
if (attribute != null)
{
elementMeaning = attribute.Value;
concatenatedMeaning =
string.IsNullOrEmpty(concatenatedMeaning) ? elementMeaning : string.Format("{0} : {1}", concatenatedMeaning, elementMeaning);
}
if (element.Descendants().Count() > 0)
{
Dictionary<int, WarnCodeValue> childNodeDictionary = new Dictionary<int, WarnCodeValue>();
foreach (XElement childElement in element.Elements())
{
int hex = Convert.ToInt32(childElement.Attribute("hex").Value, 16);
string meaning = childElement.Attribute("meaning").Value;
Dictionary<int, WarnCodeValue> dictionary = GetChildren(childElement, concatenatedMeaning);
WarnCodeValue warnCodeValue = new WarnCodeValue();
warnCodeValue.ChildNodes = dictionary;
warnCodeValue.Meaning = meaning;
warnCodeValue.ConcatenatedMeaning =
string.IsNullOrEmpty(concatenatedMeaning) ? meaning : string.Format("{0} : {1}", concatenatedMeaning, meaning);
childNodeDictionary.Add(hex, warnCodeValue);
}
return childNodeDictionary;
}
return null;
}
}
Usage
string concatenatedMeaning = WarnCodeProcessor.Instance.GetConcatenatedMeaning(0x2121);
Output
Current : Current, Device Input side : Short to Earth : Short to earth in Phase L1
Possible modifications include a GetMeaning(code) to retrieve the original meaning of the code, rather than the concatenated meaning.