Click here to Skip to main content
15,895,557 members
Articles / General Programming / String

Enhanced String Handling

Rate me:
Please Sign up or sign in to vote.
4.78/5 (16 votes)
16 Dec 2010CPOL28 min read 45.6K   338   38  
Allow for constructs within a string to be programmatically evaluated for a final string value
using System;
using System.Collections.Generic;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using EnhancedStringEvaluate;
using TestEvaluation.ProcessEvaluate;
using System.Text.RegularExpressions;
using System.IO;


namespace EvaluateSampleTest
{
	/// <summary>
	/// Summary description for EnhancedStringEvaluateTest
	/// </summary>
	[TestClass]
	public class EnhancedStringEvaluateTest
	{
		public EnhancedStringEvaluateTest()
		{
			//
			// TODO: Add constructor logic here
			//
		}

		private TestContext testContextInstance;

		/// <summary>
		///Gets or sets the test context which provides
		///information about and functionality for the current test run.
		///</summary>
		public TestContext TestContext
		{
			get
			{
				return testContextInstance;
			}
			set
			{
				testContextInstance = value;
			}
		}

		#region Additional test attributes
		//
		// You can use the following additional attributes as you write your tests:
		//
		// Use ClassInitialize to run code before running the first test in the class
		// [ClassInitialize()]
		// public static void MyClassInitialize(TestContext testContext) { }
		//
		// Use ClassCleanup to run code after all tests in a class have run
		// [ClassCleanup()]
		// public static void MyClassCleanup() { }
		//
		// Use TestInitialize to run code before running each test 
		// [TestInitialize()]
		// public void MyTestInitialize() { }
		//
		// Use TestCleanup to run code after each test has run
		// [TestCleanup()]
		// public void MyTestCleanup() { }
		//
		#endregion

		[TestMethod]
		public void CounterTest()
		{
			var context = new List<IProcessEvaluate>();
			context.Add(new ProcessCounter());

			var eval = new EnhancedStringEval(context);
			string counterN1 = eval.EvaluateString("Counter1: {Counter::CounterName1::}");
			Assert.AreEqual(@"Counter1: 0", counterN1);

			counterN1 = eval.EvaluateString("Counter1: {Counter::CounterName1}");
			Assert.AreEqual(@"Counter1: 1", counterN1);

			counterN1 = eval.EvaluateString("Counter1: {Counter::CounterName1::Init}");
			Assert.AreEqual(@"Counter1: 0", counterN1);

			counterN1 = eval.EvaluateString("Counter1: {Counter::CounterName1::Next}");
			Assert.AreEqual(@"Counter1: 1", counterN1);

			counterN1 = eval.EvaluateString("Counter1: {Counter::CounterName1::Previous}");
			Assert.AreEqual(@"Counter1: 0", counterN1);

			counterN1 = eval.EvaluateString("Counter1: {Counter::CounterName1::=10}");
			Assert.AreEqual("Counter1: 10", counterN1);

			counterN1 = eval.EvaluateString("Counter1: {Counter::CounterName1::+10}");
			Assert.AreEqual("Counter1: 20", counterN1);

			counterN1 = eval.EvaluateString("Counter1: {Counter::CounterName1::-1}");
			Assert.AreEqual("Counter1: 19", counterN1);

			try
			{
				string counterN2 = eval.EvaluateString("Counter2: {Counter::CounterN2::Next}");
				Assert.Fail();
			}
			catch (Exception ex)
			{
				// Passing through the catch leg of the try/catch is good
			}

			try
			{
				string counterN2 = eval.EvaluateString("Counter2: {Counter::CounterN2::Previous}");
				Assert.Fail();
			}
			catch (Exception ex)
			{
				// Passing through the catch leg of the try/catch is good
			}

			//
			// Complex delimiters
			//

			IDelimitersAndSeparator delim = new DelimitersAndSeparator("<Delim>", "</Delim>");
			var contextCmplx = new List<IProcessEvaluate>();
			contextCmplx.Add(new ProcessCounter(delim));
			var evalCmplx = new EnhancedStringEval(contextCmplx, delim);
			string counterCmplx = evalCmplx.EvaluateString("Complex delim counter: <Delim>Counter::Complex Delim::</Delim>");
			Assert.AreEqual(@"Complex delim counter: 0", counterCmplx);
			counterCmplx = evalCmplx.EvaluateString("Counter1: <Delim>Counter::CounterName1</Delim>");
			Assert.AreEqual(@"Counter1: 0", counterCmplx);
			counterN1 = eval.EvaluateString("Counter1: {Counter::CounterName1::Init}");	// Still using old ProcessXxx!!!
			Assert.AreEqual(@"Counter1: 0", counterN1);

			counterCmplx = evalCmplx.EvaluateString("Complex delim counter: <Delim>Counter::Complex Delim</Delim>");
			Assert.AreEqual(@"Complex delim counter: 1", counterCmplx);
			counterCmplx = evalCmplx.EvaluateString("Counter1: <Delim>Counter::CounterName1</Delim>");
			Assert.AreEqual(@"Counter1: 1", counterCmplx);
			counterN1 = eval.EvaluateString("Counter1: {Counter::CounterName1}");
			Assert.AreEqual(@"Counter1: 1", counterN1);

			counterCmplx = evalCmplx.EvaluateString("Complex delim counter: <Delim>Counter::Complex Delim</Delim>");
			Assert.AreEqual(@"Complex delim counter: 2", counterCmplx);
			counterCmplx = evalCmplx.EvaluateString("Counter1: <Delim>Counter::CounterName1</Delim>");
			Assert.AreEqual(@"Counter1: 2", counterCmplx);
			counterN1 = eval.EvaluateString("Counter1: {Counter::CounterName1}");
			Assert.AreEqual(@"Counter1: 2", counterN1);

			counterCmplx = evalCmplx.EvaluateString("Complex delim counter: <Delim>Counter::Complex Delim::Init</Delim>");
			Assert.AreEqual(@"Complex delim counter: 0", counterCmplx);
			counterCmplx = evalCmplx.EvaluateString("Counter1: <Delim>Counter::CounterName1::Init</Delim>");
			Assert.AreEqual(@"Counter1: 0", counterCmplx);
			counterN1 = eval.EvaluateString("Counter1: {Counter::CounterName1::Init}");
			Assert.AreEqual(@"Counter1: 0", counterN1);

			counterCmplx = evalCmplx.EvaluateString("Complex delim counter: <Delim>Counter::Complex Delim::Next</Delim>");
			Assert.AreEqual(@"Complex delim counter: 1", counterCmplx);
			counterCmplx = evalCmplx.EvaluateString("Counter1: <Delim>Counter::CounterName1::Next</Delim>");
			Assert.AreEqual(@"Counter1: 1", counterCmplx);
			counterN1 = eval.EvaluateString("Counter1: {Counter::CounterName1::Next}");
			Assert.AreEqual(@"Counter1: 1", counterN1);

			counterCmplx = evalCmplx.EvaluateString("Complex delim counter: <Delim>Counter::Complex Delim::Previous</Delim>");
			Assert.AreEqual(@"Complex delim counter: 0", counterCmplx);
			counterCmplx = evalCmplx.EvaluateString("Counter1: <Delim>Counter::CounterName1::Previous</Delim>");
			Assert.AreEqual(@"Counter1: 0", counterCmplx);
			counterN1 = eval.EvaluateString("Counter1: {Counter::CounterName1::Previous}");
			Assert.AreEqual(@"Counter1: 0", counterN1);

			counterCmplx = evalCmplx.EvaluateString("Complex delim counter: <Delim>Counter::Complex Delim:: Next </Delim>");
			Assert.AreEqual(@"Complex delim counter: 1", counterCmplx);
			counterCmplx = evalCmplx.EvaluateString("Counter1: <Delim>Counter::CounterName1:: Next </Delim>");
			Assert.AreEqual(@"Counter1: 1", counterCmplx);
			counterN1 = eval.EvaluateString("Counter1: {Counter::CounterName1::Next}");
			Assert.AreEqual(@"Counter1: 1", counterN1);

			counterCmplx = evalCmplx.EvaluateString("Complex delim counter: <Delim>Counter::Complex Delim:: = 10 </Delim>");
			Assert.AreEqual(@"Complex delim counter: 10", counterCmplx);
			counterCmplx = evalCmplx.EvaluateString("Counter1: <Delim>Counter::CounterName1:: = 10</Delim>");
			Assert.AreEqual("Counter1: 10", counterCmplx);
			counterN1 = eval.EvaluateString("Counter1: {Counter::CounterName1::=10}");
			Assert.AreEqual("Counter1: 10", counterN1);

			counterCmplx = evalCmplx.EvaluateString("Complex delim counter: <Delim>Counter::Complex Delim:: + 10</Delim>");
			Assert.AreEqual(@"Complex delim counter: 20", counterCmplx);
			counterCmplx = evalCmplx.EvaluateString("Counter1: <Delim>Counter::CounterName1::+10</Delim>");
			Assert.AreEqual("Counter1: 20", counterCmplx);
			counterN1 = eval.EvaluateString("Counter1: {Counter::CounterName1::+ 10}");
			Assert.AreEqual("Counter1: 20", counterN1);

			counterCmplx = evalCmplx.EvaluateString("Complex delim counter: <Delim>Counter::Complex Delim:: - 1</Delim>");
			Assert.AreEqual(@"Complex delim counter: 19", counterCmplx);
			counterCmplx = evalCmplx.EvaluateString("Counter1: <Delim>Counter::CounterName1::-1</Delim>");
			Assert.AreEqual("Counter1: 19", counterCmplx);
			counterN1 = eval.EvaluateString("Counter1: {Counter::CounterName1::-1}");
			Assert.AreEqual("Counter1: 19", counterN1);

			counterCmplx = evalCmplx.EvaluateString("Complex delim counter: <Delim>Counter::Complex Delim:: = -10 </Delim>");
			Assert.AreEqual(@"Complex delim counter: -10", counterCmplx);
			counterCmplx = evalCmplx.EvaluateString("Counter1: <Delim>Counter::CounterName1:: = - 10 </Delim>");
			Assert.AreEqual("Counter1: -10", counterCmplx);
			counterN1 = eval.EvaluateString("Counter1: {Counter::CounterName1:: = -10 }");
			Assert.AreEqual("Counter1: -10", counterN1);

			counterCmplx = evalCmplx.EvaluateString("Complex delim counter: <Delim>Counter::Complex Delim:: + -10 </Delim>");
			Assert.AreEqual(@"Complex delim counter: -20", counterCmplx);
			counterCmplx = evalCmplx.EvaluateString("Counter1: <Delim>Counter::CounterName1::+-10</Delim>");
			Assert.AreEqual("Counter1: -20", counterCmplx);
			counterN1 = eval.EvaluateString("Counter1: {Counter::CounterName1::+ -10}");
			Assert.AreEqual("Counter1: -20", counterN1);

			counterCmplx = evalCmplx.EvaluateString("Complex delim counter: <Delim>Counter::Complex Delim:: - - 1 </Delim>");
			Assert.AreEqual(@"Complex delim counter: -19", counterCmplx);
			counterCmplx = evalCmplx.EvaluateString("Counter1: <Delim>Counter::CounterName1::--1 </Delim>");
			Assert.AreEqual("Counter1: -19", counterCmplx);
			counterN1 = eval.EvaluateString("Counter1: {Counter::CounterName1::- -1 }");
			Assert.AreEqual("Counter1: -19", counterN1);
		}

		/// <summary>
		/// Will work only if Option1 compiler-directive in the TestEvaluation project is set
		/// </summary>
		[TestMethod()]
		public void TestCounter2()
		{
			// Possibility 1: Know how to handle trailing colon internally
			var cxt1 = new List<IProcessEvaluate>();
			cxt1.Add(new ProcessCounter());
			var eval1 = new EnhancedStringEval(cxt1);
			string test1 = eval1.EvaluateString("c1: {Counter::Name:::Init}");
			Assert.AreEqual("c1: 0", test1);

			test1 = eval1.EvaluateString("c1: {Counter::Name:}");
			Assert.AreEqual("c1: 1", test1);

			test1 = eval1.EvaluateString("Name without colon trailing is a different name: {Counter::Name}");
			Assert.AreEqual("Name without colon trailing is a different name: 0", test1);

			test1 = eval1.EvaluateString("Name ending with a space: {Counter::Name: }");
			Assert.AreEqual("Name ending with a space: 0", test1);

			// Possibility 2: handle colon specially in an inherited class
			var cxt2 = new List<IProcessEvaluate>();
			var counter = new ProcessCounter { PosibilityOption = Possibility.Substitution };
			cxt2.Add(counter);
			var eval2 = new EnhStringTrailingColon(cxt2);
			string test2 = eval2.EvaluateString("c2: {Counter::Name:::Init}");
			Assert.AreEqual("c2: 0", test2);

			test2 = eval2.EvaluateString("c2: {Counter::Name:}");
			Assert.AreEqual("c2: 1", test2);

			test2 = eval2.EvaluateString("Name without colon trailing is a different name: {Counter::Name}");
			Assert.AreEqual("Name without colon trailing is a different name: 0", test2);

			test2 = eval2.EvaluateString("Name ending with a space: {Counter::Name: }");
			Assert.AreEqual("Name ending with a space: 0", test2);
		}

		[TestMethod]
		public void TestCurrentDir()
		{
			var context = new List<IProcessEvaluate>();
			var currDir = new ProcessCurrentDir();
			context.Add(currDir);
			context.Add(new ProcessCounter());

			currDir.CurrentDir = @"C:\Accounting";
			var eval = new EnhancedStringEval(context);
			string dir1 = eval.EvaluateString("CurrDir: {CurrentDir::}");
			Assert.AreEqual(@"CurrDir: C:\Accounting", dir1);

			dir1 = eval.EvaluateString("CurrDir: {CurrentDir::-0}");
			Assert.AreEqual(@"CurrDir: C:\Accounting", dir1);

			dir1 = eval.EvaluateString("CurrDir: {CurrentDir::-1}");
			Assert.AreEqual(@"CurrDir: C:\", dir1);

			dir1 = eval.EvaluateString("CurrDir: {CurrentDir::-2}");
			Assert.AreEqual(@"CurrDir: --\\no path at given count: -2\\--", dir1);

			currDir.CurrentDir = Path.Combine(currDir.CurrentDir, "Book1");
			string dir2 = eval.EvaluateString("CurrDir: {CurrentDir::}");
			Assert.AreEqual(@"CurrDir: C:\Accounting\Book1", dir2);

			currDir.CurrentDir = Path.Combine(currDir.CurrentDir, "{Counter::Dir}");
			string dirR = eval.EvaluateString("CurrDir: {CurrentDir::}");
			Assert.AreEqual(@"CurrDir: C:\Accounting\Book1\0", dirR);

			dirR = eval.EvaluateString("CurrDir: {CurrentDir::}");
			Assert.AreEqual(@"CurrDir: C:\Accounting\Book1\1", dirR);

			dirR = eval.EvaluateString("CurrDir: {CurrentDir::}");
			Assert.AreEqual(@"CurrDir: C:\Accounting\Book1\2", dirR);

			//
			/////   Different delimiters
			/////   C suffix for Complex-delimiter
			//

			IDelimitersAndSeparator delim = new DelimitersAndSeparator("#{", "}");
			var contextC = new List<IProcessEvaluate>();
			var currDirC = new ProcessCurrentDir(delim);
			contextC.Add(currDirC);
			contextC.Add(new ProcessCounter(delim));

			currDirC.CurrentDir = @"C:\Desk";
			var evalC = new EnhancedStringEval(contextC, delim);
			string dirC1 = evalC.EvaluateString("CurrDir: #{CurrentDir::}");
			Assert.AreEqual(@"CurrDir: C:\Desk", dirC1);

			dirC1 = evalC.EvaluateString("CurrDir: #{CurrentDir::-0}");
			Assert.AreEqual(@"CurrDir: C:\Desk", dirC1);

			dirC1 = evalC.EvaluateString("CurrDir: #{CurrentDir::-1}");
			Assert.AreEqual(@"CurrDir: C:\", dirC1);

			dirC1 = evalC.EvaluateString("CurrDir: #{CurrentDir::-2}");
			Assert.AreEqual(@"CurrDir: --\\no path at given count: -2\\--", dirC1);

			currDirC.CurrentDir = Path.Combine(currDirC.CurrentDir, "Book1");
			string dirC2 = evalC.EvaluateString("CurrDir: #{CurrentDir::}");
			Assert.AreEqual(@"CurrDir: C:\Desk\Book1", dirC2);

			currDirC.CurrentDir = Path.Combine(currDirC.CurrentDir, "#{Counter::Dir}");
			string dirCR = evalC.EvaluateString("CurrDir: #{CurrentDir::}");
			Assert.AreEqual(@"CurrDir: C:\Desk\Book1\0", dirCR);

			dirCR = evalC.EvaluateString("CurrDir: #{CurrentDir::}");
			Assert.AreEqual(@"CurrDir: C:\Desk\Book1\1", dirCR);

			dirCR = evalC.EvaluateString("CurrDir: #{CurrentDir::}");
			Assert.AreEqual(@"CurrDir: C:\Desk\Book1\2", dirCR);
		}

		[TestMethod]
		public void TestCurrentTime()
		{
			var context = new List<IProcessEvaluate>();
			context.Add(new ProcessCurrentTime());
			var eval = new EnhancedStringEval(context);
			string ct = eval.EvaluateString("{CurrentTime::yyyyMMddHHmmssfff}");
			DateTime dtNow = DateTime.Now;
			int yr = int.Parse(ct.Substring(0, 4));
			int mo = int.Parse(ct.Substring(4, 2));
			int dy = int.Parse(ct.Substring(6, 2));
			int hr = int.Parse(ct.Substring(8, 2));
			int mi = int.Parse(ct.Substring(10, 2));
			int sc = int.Parse(ct.Substring(12, 2));
			int ms = int.Parse(ct.Substring(14, 3));
			DateTime dtEval = new DateTime(yr, mo, dy, hr, mi, sc, ms);
			TimeSpan span = dtNow - dtEval;
			Assert.AreEqual(true, span.TotalMilliseconds < 50);

			ct = eval.EvaluateString("{CurrentTime::yyyyMMddHHmmssfff::}");
			dtNow = DateTime.Now;
			yr = int.Parse(ct.Substring(0, 4));
			mo = int.Parse(ct.Substring(4, 2));
			dy = int.Parse(ct.Substring(6, 2));
			hr = int.Parse(ct.Substring(8, 2));
			mi = int.Parse(ct.Substring(10, 2));
			sc = int.Parse(ct.Substring(12, 2));
			ms = int.Parse(ct.Substring(14, 3));
			dtEval = new DateTime(yr, mo, dy, hr, mi, sc, ms);
			span = dtNow - dtEval;
			Assert.AreEqual(true, span.TotalMilliseconds < 50);

			ct = eval.EvaluateString("{CurrentTime::yyyy/MM/dd HH:mm:ss.fff:: +2 y}");
			dtNow = DateTime.Now;
			yr = dtNow.Year;
			mo = dtNow.Month;
			dy = dtNow.Day;
			hr = dtNow.Hour;
			mi = dtNow.Minute;
			sc = dtNow.Second;
			ms = dtNow.Millisecond;
			DateTime next2Yr = new DateTime(yr + 2, mo, dy, hr, mi, sc, ms);
			yr = int.Parse(ct.Substring(0, 4));		// yyyy -- /MM/dd HH:mm:ss.fff
			mo = int.Parse(ct.Substring(5, 2));		// MM   -- /dd HH:mm:ss.fff
			dy = int.Parse(ct.Substring(8, 2));		// dd   --  HH:mm:ss.fff
			hr = int.Parse(ct.Substring(11, 2));	// HH   -- :mm:ss.fff
			mi = int.Parse(ct.Substring(14, 2));	// mm   -- :ss.fff
			sc = int.Parse(ct.Substring(17, 2));	// ss   -- .fff
			ms = int.Parse(ct.Substring(20, 3));	// fff  -- 
			dtEval = new DateTime(yr, mo, dy, hr, mi, sc, ms);
			span = next2Yr - dtEval;
			Assert.AreEqual(true, span.TotalMilliseconds < 50);

			ct = eval.EvaluateString("{CurrentTime::yyyyMMddHHmmssfff:: +2 M}");
			dtNow = DateTime.Now;
			yr = dtNow.Year;
			mo = dtNow.Month;
			if (mo == 11) { mo = 1; ++yr; }
			else if (mo == 12) { mo = 2; ++yr; }
			else mo += 2;
			dy = dtNow.Day;
			hr = dtNow.Hour;
			mi = dtNow.Minute;
			sc = dtNow.Second;
			ms = dtNow.Millisecond;
			DateTime next2mos = new DateTime(yr, mo, dy, hr, mi, sc, ms);
			yr = int.Parse(ct.Substring(0, 4));
			mo = int.Parse(ct.Substring(4, 2));
			dy = int.Parse(ct.Substring(6, 2));
			hr = int.Parse(ct.Substring(8, 2));
			mi = int.Parse(ct.Substring(10, 2));
			sc = int.Parse(ct.Substring(12, 2));
			ms = int.Parse(ct.Substring(14, 3));
			dtEval = new DateTime(yr, mo, dy, hr, mi, sc, ms);
			span = next2mos - dtEval;
			Assert.AreEqual(true, span.TotalMilliseconds < 50);

			ct = eval.EvaluateString("{CurrentTime::yyyyMMddHHmmssfff::+3w}");
			DateTime next3wks = DateTime.Now + TimeSpan.FromDays(21.0);
			dtNow = DateTime.Now;
			yr = int.Parse(ct.Substring(0, 4));
			mo = int.Parse(ct.Substring(4, 2));
			dy = int.Parse(ct.Substring(6, 2));
			hr = int.Parse(ct.Substring(8, 2));
			mi = int.Parse(ct.Substring(10, 2));
			sc = int.Parse(ct.Substring(12, 2));
			ms = int.Parse(ct.Substring(14, 3));
			dtEval = new DateTime(yr, mo, dy, hr, mi, sc, ms);
			span = next3wks - dtEval;
			Assert.AreEqual(true, span.TotalMilliseconds < 50);

			ct = eval.EvaluateString("{CurrentTime::yyyy/MM/dd HH:mm:ss.fff:: +1 d}");
			DateTime tomorrow = DateTime.Now + TimeSpan.FromDays(1.0);
			yr = int.Parse(ct.Substring(0, 4));		// yyyy -- /MM/dd HH:mm:ss.fff
			mo = int.Parse(ct.Substring(5, 2));		// MM   -- /dd HH:mm:ss.fff
			dy = int.Parse(ct.Substring(8, 2));		// dd   --  HH:mm:ss.fff
			hr = int.Parse(ct.Substring(11, 2));	// HH   -- :mm:ss.fff
			mi = int.Parse(ct.Substring(14, 2));	// mm   -- :ss.fff
			sc = int.Parse(ct.Substring(17, 2));	// ss   -- .fff
			ms = int.Parse(ct.Substring(20, 3));	// fff  -- 
			dtEval = new DateTime(yr, mo, dy, hr, mi, sc, ms);
			span = tomorrow - dtEval;
			Assert.AreEqual(true, span.TotalMilliseconds < 50);

			ct = eval.EvaluateString("{CurrentTime::yyyyMMddHHmmssfff:: +5 h}");
			DateTime next5hrs = DateTime.Now + TimeSpan.FromHours(5.0);
			dtNow = DateTime.Now;
			yr = int.Parse(ct.Substring(0, 4));
			mo = int.Parse(ct.Substring(4, 2));
			dy = int.Parse(ct.Substring(6, 2));
			hr = int.Parse(ct.Substring(8, 2));
			mi = int.Parse(ct.Substring(10, 2));
			sc = int.Parse(ct.Substring(12, 2));
			ms = int.Parse(ct.Substring(14, 3));
			dtEval = new DateTime(yr, mo, dy, hr, mi, sc, ms);
			span = next5hrs - dtEval;
			Assert.AreEqual(true, span.TotalMilliseconds < 50);

			ct = eval.EvaluateString("{CurrentTime::yyyyMMddHHmmssfff:: +6 m}");
			DateTime next6mins = DateTime.Now + TimeSpan.FromMinutes(6.0);
			dtNow = DateTime.Now;
			yr = int.Parse(ct.Substring(0, 4));
			mo = int.Parse(ct.Substring(4, 2));
			dy = int.Parse(ct.Substring(6, 2));
			hr = int.Parse(ct.Substring(8, 2));
			mi = int.Parse(ct.Substring(10, 2));
			sc = int.Parse(ct.Substring(12, 2));
			ms = int.Parse(ct.Substring(14, 3));
			dtEval = new DateTime(yr, mo, dy, hr, mi, sc, ms);
			span = next6mins - dtEval;
			Assert.AreEqual(true, span.TotalMilliseconds < 50);

			ct = eval.EvaluateString("{CurrentTime::yyyyMMddHHmmssfff:: +7 s}");
			DateTime next7secs = DateTime.Now + TimeSpan.FromSeconds(7.0);
			dtNow = DateTime.Now;
			yr = int.Parse(ct.Substring(0, 4));
			mo = int.Parse(ct.Substring(4, 2));
			dy = int.Parse(ct.Substring(6, 2));
			hr = int.Parse(ct.Substring(8, 2));
			mi = int.Parse(ct.Substring(10, 2));
			sc = int.Parse(ct.Substring(12, 2));
			ms = int.Parse(ct.Substring(14, 3));
			dtEval = new DateTime(yr, mo, dy, hr, mi, sc, ms);
			span = next7secs - dtEval;
			Assert.AreEqual(true, span.TotalMilliseconds < 50);

			//
			/////   Different delimiters
			/////   C suffix for Complex-delimiter
			//

			IDelimitersAndSeparator delim = new DelimitersAndSeparator("%<", ">%");
			var contextC = new List<IProcessEvaluate>();
			contextC.Add(new ProcessCurrentTime(delim));
			var evalC = new EnhancedStringEval(contextC, delim);
			ct = evalC.EvaluateString("%<CurrentTime::yyyyMMddHHmmssfff>%");
			dtNow = DateTime.Now;
			yr = int.Parse(ct.Substring(0, 4));
			mo = int.Parse(ct.Substring(4, 2));
			dy = int.Parse(ct.Substring(6, 2));
			hr = int.Parse(ct.Substring(8, 2));
			mi = int.Parse(ct.Substring(10, 2));
			sc = int.Parse(ct.Substring(12, 2));
			ms = int.Parse(ct.Substring(14, 3));
			dtEval = new DateTime(yr, mo, dy, hr, mi, sc, ms);
			span = dtNow - dtEval;
			Assert.AreEqual(true, span.TotalMilliseconds < 50);

			ct = evalC.EvaluateString("%<CurrentTime::dd/MM/yyyy HH:mm:ss.fff::-1y>%");
			dtNow = DateTime.Now;
			yr = dtNow.Year;
			mo = dtNow.Month;
			dy = dtNow.Day;
			hr = dtNow.Hour;
			mi = dtNow.Minute;
			sc = dtNow.Second;
			ms = dtNow.Millisecond;
			DateTime lastYr = new DateTime(yr - 1, mo, dy, hr, mi, sc, ms);
			dy = int.Parse(ct.Substring(0, 2));
			mo = int.Parse(ct.Substring(3, 2));
			yr = int.Parse(ct.Substring(6, 4));
			hr = int.Parse(ct.Substring(11, 2));
			mi = int.Parse(ct.Substring(14, 2));
			sc = int.Parse(ct.Substring(17, 2));
			ms = int.Parse(ct.Substring(20, 3));
			dtEval = new DateTime(yr, mo, dy, hr, mi, sc, ms);
			span = lastYr - dtEval;
			Assert.AreEqual(true, span.TotalMilliseconds < 50);

			ct = evalC.EvaluateString("%<CurrentTime::yyyyMMddHHmmssfff::-1m>%");
			dtNow = DateTime.Now;
			yr = dtNow.Year;
			mo = dtNow.Month;
			dy = dtNow.Day;
			hr = dtNow.Hour;
			mi = dtNow.Minute;
			sc = dtNow.Second;
			ms = dtNow.Millisecond;
			if (mo == 1) { mo = 12; --yr; }
			else --mo;
			DateTime lastMo = new DateTime(yr, mo, dy, hr, mi, sc, ms);
			yr = int.Parse(ct.Substring(0, 4));
			mo = int.Parse(ct.Substring(4, 2));
			dy = int.Parse(ct.Substring(6, 2));
			hr = int.Parse(ct.Substring(8, 2));
			mi = int.Parse(ct.Substring(10, 2));
			sc = int.Parse(ct.Substring(12, 2));
			ms = int.Parse(ct.Substring(14, 3));
			dtEval = new DateTime(yr, mo, dy, hr, mi, sc, ms);
			span = lastMo - dtEval;
			Assert.AreEqual(true, span.TotalMilliseconds < 50);

			ct = evalC.EvaluateString("%<CurrentTime::yyyy/MM/dd HH:mm:ss.fff::-1d>%");
			DateTime yesterday = DateTime.Now - TimeSpan.FromDays(1.0);
			yr = int.Parse(ct.Substring(0, 4));		// yyyy -- /MM/dd HH:mm:ss.fff
			mo = int.Parse(ct.Substring(5, 2));		// MM   -- /dd HH:mm:ss.fff
			dy = int.Parse(ct.Substring(8, 2));		// dd   --  HH:mm:ss.fff
			hr = int.Parse(ct.Substring(11, 2));	// HH   -- :mm:ss.fff
			mi = int.Parse(ct.Substring(14, 2));	// mm   -- :ss.fff
			sc = int.Parse(ct.Substring(17, 2));	// ss   -- .fff
			ms = int.Parse(ct.Substring(20, 3));	// fff  -- 
			dtEval = new DateTime(yr, mo, dy, hr, mi, sc, ms);
			span = yesterday - dtEval;
			Assert.AreEqual(true, span.TotalMilliseconds < 50);

			ct = evalC.EvaluateString("%<CurrentTime::yyyyMMddHHmmssfff::-2w>%");
			DateTime TwoWksAgo = DateTime.Now - TimeSpan.FromDays(14.0);
			yr = int.Parse(ct.Substring(0, 4));
			mo = int.Parse(ct.Substring(4, 2));
			dy = int.Parse(ct.Substring(6, 2));
			hr = int.Parse(ct.Substring(8, 2));
			mi = int.Parse(ct.Substring(10, 2));
			sc = int.Parse(ct.Substring(12, 2));
			ms = int.Parse(ct.Substring(14, 3));
			dtEval = new DateTime(yr, mo, dy, hr, mi, sc, ms);
			span = TwoWksAgo - dtEval;
			Assert.AreEqual(true, span.TotalMilliseconds < 50);

			ct = evalC.EvaluateString("%<CurrentTime::yyyyMMddHHmmssfff::-3h>%");
			DateTime ThreeHrsAgo = DateTime.Now - TimeSpan.FromHours(3.0);
			yr = int.Parse(ct.Substring(0, 4));
			mo = int.Parse(ct.Substring(4, 2));
			dy = int.Parse(ct.Substring(6, 2));
			hr = int.Parse(ct.Substring(8, 2));
			mi = int.Parse(ct.Substring(10, 2));
			sc = int.Parse(ct.Substring(12, 2));
			ms = int.Parse(ct.Substring(14, 3));
			dtEval = new DateTime(yr, mo, dy, hr, mi, sc, ms);
			span = ThreeHrsAgo - dtEval;
			Assert.AreEqual(true, span.TotalMilliseconds < 50);

			ct = evalC.EvaluateString("%<CurrentTime::yyyyMMddHHmmssfff:: - 4 m >%");
			DateTime FourMinsAgo = DateTime.Now - TimeSpan.FromMinutes(4.0);
			yr = int.Parse(ct.Substring(0, 4));
			mo = int.Parse(ct.Substring(4, 2));
			dy = int.Parse(ct.Substring(6, 2));
			hr = int.Parse(ct.Substring(8, 2));
			mi = int.Parse(ct.Substring(10, 2));
			sc = int.Parse(ct.Substring(12, 2));
			ms = int.Parse(ct.Substring(14, 3));
			dtEval = new DateTime(yr, mo, dy, hr, mi, sc, ms);
			span = FourMinsAgo - dtEval;
			Assert.AreEqual(true, span.TotalMilliseconds < 50);

			ct = evalC.EvaluateString("%<CurrentTime::yyyyMMddHHmmssfff:: -5 s >%");
			DateTime FiveSecsAgo = DateTime.Now - TimeSpan.FromSeconds(5.0);
			yr = int.Parse(ct.Substring(0, 4));
			mo = int.Parse(ct.Substring(4, 2));
			dy = int.Parse(ct.Substring(6, 2));
			hr = int.Parse(ct.Substring(8, 2));
			mi = int.Parse(ct.Substring(10, 2));
			sc = int.Parse(ct.Substring(12, 2));
			ms = int.Parse(ct.Substring(14, 3));
			dtEval = new DateTime(yr, mo, dy, hr, mi, sc, ms);
			span = FiveSecsAgo - dtEval;
			Assert.AreEqual(true, span.TotalMilliseconds < 50);
		}

		[TestMethod]
		public void TestCurrentDate()
		{
			var context = new List<IProcessEvaluate>();
			context.Add(new ProcessDate());
			DateTime today = DateTime.Today;
			var eval = new EnhancedStringEval(context);
			string sDt = eval.EvaluateString("{Date::yyyy-MM-dd}");
			int yr = int.Parse(sDt.Substring(0, 4));
			int mo = int.Parse(sDt.Substring(5, 2));
			int dy = int.Parse(sDt.Substring(8, 2));
			var dEval = new DateTime(yr, mo, dy);
			TimeSpan span = today - dEval;
			Assert.AreEqual(true, span.TotalDays <= 1);

			//
			/////   Different delimiters
			/////   C suffix for Complex-delimiter
			//

			IDelimitersAndSeparator delim = new DelimitersAndSeparator("<begin>", "<end>");
			var contextC = new List<IProcessEvaluate>();
			contextC.Add(new ProcessDate(delim));
			today = DateTime.Today;
			var evalC = new EnhancedStringEval(contextC, delim);
			sDt = evalC.EvaluateString("<begin>Date::yyyy-MM-dd<end>");
			yr = int.Parse(sDt.Substring(0, 4));
			mo = int.Parse(sDt.Substring(5, 2));
			dy = int.Parse(sDt.Substring(8, 2));
			dEval = new DateTime(yr, mo, dy);
			span = today - dEval;
			Assert.AreEqual(true, span.TotalDays <= 1);
		}

		[TestMethod]
		public void TestForeignKey()
		{
			string tmpPath = Path.GetTempPath();
			string fileNm = System.Reflection.MethodBase.GetCurrentMethod().Name;
			string filePath = Path.Combine(tmpPath, fileNm + ".txt");
			using (var fs = new FileStream(filePath, FileMode.Create))
			using (var sw = new StreamWriter(fs))
			{
				sw.WriteLine("Key1=First Simple Key");
				sw.WriteLine("Key2=Second Key: {Counter::Simple Key}");
				sw.WriteLine("Key3=Third key on {CurrentTime::dd/MM/yyyy hh:mm:ss.fff tt}, {Counter::Simple Key}");
			}

			var context = new List<IProcessEvaluate>();
			context.Add(new ProcessCounter());
			context.Add(new ProcessCurrentTime());
			context.Add(new ProcessForeignKey());
			var eval = new EnhancedStringEval(context);
			
			string key1 = string.Format("Key1:  {{ForeignKey::{0}::Key1}}", filePath);
			string key1eval = eval.EvaluateString(key1);
			Assert.AreEqual("Key1:  First Simple Key", key1eval);

			string key2 = string.Format("Key2:  {{ForeignKey::{0}::Key2}}", filePath);
			string key2eval = eval.EvaluateString(key2);
			Assert.AreEqual("Key2:  Second Key: 0", key2eval);

			string key3 = string.Format("Key3:  {{ForeignKey::{0}::Key3}}", filePath);
			string key3eval = eval.EvaluateString(key3);
			Assert.AreEqual(true, key3eval.StartsWith("Key3:  Third key on "));
			Assert.AreEqual(true, key3eval.EndsWith(", 1"));

			File.Delete(filePath);

			//
			/////   Different delimiters
			/////   C suffix for keeping up with tradition
			//

			using (var fs = new FileStream(filePath, FileMode.Create))
			using (var sw = new StreamWriter(fs))
			{
				sw.WriteLine("Key1=First Simple Key");
				sw.WriteLine("Key2=Second Key: <Counter::Simple Key>");
				sw.WriteLine("Key3=Third key on <CurrentTime::dd/MM/yyyy hh:mm:ss.fff tt>, <Counter::Simple Key>");
			}

			IDelimitersAndSeparator delim = new DelimitersAndSeparator("<", ">");
			var contextC = new List<IProcessEvaluate>();
			contextC.Add(new ProcessCounter(delim));
			contextC.Add(new ProcessCurrentTime(delim));
			contextC.Add(new ProcessForeignKey(delim));
			var evalC = new EnhancedStringEval(contextC, delim);

			key1 = string.Format("Key1:  <ForeignKey::{0}::Key1>", filePath);
			key1eval = evalC.EvaluateString(key1);
			Assert.AreEqual("Key1:  First Simple Key", key1eval);

			key2 = string.Format("Key2:  <ForeignKey::{0}::Key2>", filePath);
			key2eval = evalC.EvaluateString(key2);
			Assert.AreEqual("Key2:  Second Key: 0", key2eval);

			key3 = string.Format("Key3:  <ForeignKey::{0}::Key3>", filePath);
			key3eval = evalC.EvaluateString(key3);
			Assert.AreEqual(true, key3eval.StartsWith("Key3:  Third key on "));
			Assert.AreEqual(true, key3eval.EndsWith(", 1"));

			File.Delete(filePath);
		}

		[TestMethod]
		public void TestIf()
		{
			var context = new List<IProcessEvaluate>();
			context.Add(new ProcessCounter());
			var currDir = new ProcessCurrentDir();
			context.Add(currDir);
			context.Add(new ProcessCurrentTime());
			context.Add(new ProcessForeignKey());
			context.Add(new ProcessIf());
			var eval = new EnhancedStringEval(context);

			string tmpPath = Path.GetTempPath();
			string fileNm = System.Reflection.MethodBase.GetCurrentMethod().Name;
			string filePath = Path.Combine(tmpPath, fileNm + ".txt");
			using (var fs = new FileStream(filePath, FileMode.Create))
			using (var sw = new StreamWriter(fs))
			{
				sw.WriteLine("Key1=First Simple Key");
				sw.WriteLine("Key2=Second Key: {Counter::Simple Key}");
				sw.WriteLine("Key3=Third key on {CurrentTime::dd/MM/yyyy hh:mm:ss.fff tt}, {Counter::Simple Key}");
				sw.WriteLine("Key4={if::{CurrentTime::dd/MM/yyyy hh:mm:ss.fff tt} = {CurrentTime::dd/MM/yyyy hh:mm:ss.fff tt::-3d}::Yes::No}");
				sw.WriteLine("Key5={if::{Counter::Simple key} = 2::2-{CurrentDir::}::No-{CurrentDir::}}");
			}

			string key = string.Format("{{ForeignKey::{0}::Key4}}", filePath);
			string e1 = eval.EvaluateString(key);
			Assert.AreEqual("No", e1);

			currDir.CurrentDir = @"C:\";
			key = string.Format("{{ForeignKey::{0}::Key5}}", filePath);
			e1 = eval.EvaluateString(key);
			Assert.AreEqual(@"No-C:\", e1);

			currDir.CurrentDir = @"C:\abc\";
			e1 = eval.EvaluateString(key);
			Assert.AreEqual(@"No-C:\abc\", e1);

			currDir.CurrentDir = @"C:\abc\def";
			e1 = eval.EvaluateString(key);
			Assert.AreEqual(@"2-C:\abc\def", e1);

			key = string.Format("{{if::DirectoryExists({0})::Yes::No}}", tmpPath);
			string e2 = eval.EvaluateString(key);
			Assert.AreEqual("Yes", e2);

			key = string.Format("{{if::FileExists({0})::Yes::No}}", filePath);
			string e3 = eval.EvaluateString(key);
			Assert.AreEqual("Yes", e3);

			File.Delete(filePath);

			//
			/////   Different delimiters
			/////   C suffix for Complex-delimiter
			//

			IDelimitersAndSeparator delim = new DelimitersAndSeparator("$<", ">$");
			var contextC = new List<IProcessEvaluate>();
			contextC.Add(new ProcessCounter(delim));
			var currDirC = new ProcessCurrentDir(delim);
			contextC.Add(currDirC);
			contextC.Add(new ProcessCurrentTime(delim));
			contextC.Add(new ProcessForeignKey(delim));
			contextC.Add(new ProcessIf(delim));
			var evalC = new EnhancedStringEval(contextC, delim);

			if (tmpPath.EndsWith("\\")) tmpPath = tmpPath.Substring(0, tmpPath.Length - 1);
			using (var fs = new FileStream(filePath, FileMode.Create))
			using (var sw = new StreamWriter(fs))
			{
				sw.WriteLine("Key1=First Simple Key");
				sw.WriteLine("Key2=Second Key: $<Counter::Simple Key>$");
				sw.WriteLine("Key3=Third key on $<CurrentTime::dd/MM/yyyy hh:mm:ss.fff tt>$, $<Counter::Simple Key>$");
				sw.WriteLine("Key4=$<if::$<CurrentTime::dd/MM/yyyy>$ = $<CurrentTime::dd/MM/yyyy::-3d>$::Yes::No>$");
				sw.WriteLine("Key5=$<if::$<Counter::Simple key>$ = 2::2-$<CurrentDir::>$::No-$<CurrentDir::>$>$");
			}

			key = string.Format("$<ForeignKey::{0}::Key4>$", filePath);
			e1 = evalC.EvaluateString(key);
			Assert.AreEqual("No", e1);

			currDirC.CurrentDir = @"D:\";
			key = string.Format("$<ForeignKey::{0}::Key5>$", filePath);
			e1 = evalC.EvaluateString(key);
			Assert.AreEqual(@"No-D:\", e1);

			currDirC.CurrentDir = @"D:\abc\";
			e1 = evalC.EvaluateString(key);
			Assert.AreEqual(@"No-D:\abc\", e1);

			currDirC.CurrentDir = @"D:\abc\def";
			e1 = evalC.EvaluateString(key);
			Assert.AreEqual(@"2-D:\abc\def", e1);

			key = string.Format("$<if::DirectoryExists({0})::Yes::No>$", tmpPath);
			e2 = evalC.EvaluateString(key);
			Assert.AreEqual("Yes", e2);

			key = string.Format("$<if ::FileExists({0}):: Yes:: No>$", filePath);
			e3 = evalC.EvaluateString(key);
			Assert.AreEqual("Yes", e3);

			File.Delete(filePath);
		}

		[TestMethod]
		public void TestKey()
		{
			var elems = new Dictionary<string, string>();
			elems.Add("Flat", @"testing {Counter::page}");
			elems.Add("Static flat", @"Evaluation of flat: {key::flat}");
			elems.Add("Flat indirect", "Flat");
			elems.Add("Flat2", "Eval2 of flat: {key::{Key::Flat indirect}}");
			elems.Add("Static date", @"Evaluation of date: {date::yyyy.MM.dd}");
			elems.Add("Current directory", @"current dir: {CurrentDir::}");
			elems.Add("temp", @"SpecialDirectory\{key::flat}\{key::Stamp}");
			elems.Add("Current path", @"{key::current directory}\{key::temp}");
			elems.Add("Stamp", @"{CurrentTime::yyyyMMddHHmmssfff::-1d}");
			elems.Add("CompoundStamp", "{CurrentTime::{Key::fmt1}::{Key::DaysAgo}}");
			elems.Add("CompoundStamp2", "{CurrentTime::{Key::fmt2}::{Key::DaysAgo}}");
			elems.Add("fmt1", "yyyyMMddHHmmssfff");
			elems.Add("DaysAgo", "-1d");
			elems.Add("fmt2", "dd/MM/yyyy");

			// handling
			var context = new List<IProcessEvaluate>();
			context.Add(new ProcessCounter());
			var currDir = new ProcessCurrentDir();
			context.Add(currDir);
			context.Add(new ProcessCurrentTime());
			context.Add(new ProcessDate());
			context.Add(new ProcessForeignKey());
			context.Add(new ProcessKey(elems));
			context.Add(new ProcessIf());
			var eval = new EnhancedStringEval(context);

			string ev = eval.EvaluateString("{key::Flat}");
			Assert.AreEqual("testing 0", ev);

			ev = eval.EvaluateString("{key::Static flat}");
			Assert.AreEqual("Evaluation of flat: testing 1", ev);

			ev = eval.EvaluateString("{key::flat indirect}");
			Assert.AreEqual("Flat", ev);

			ev = eval.EvaluateString("{key::{key::flat indirect}}");
			Assert.AreEqual("testing 2", ev);

			ev = eval.EvaluateString("{key::Static {key::flat indirect}}");
			Assert.AreEqual("Evaluation of flat: testing 3", ev);

			ev = eval.EvaluateString("{key::{key::flat indirect}2}");
			Assert.AreEqual("Eval2 of flat: testing 4", ev);

			ev = eval.EvaluateString("{key::Static date}");
			DateTime now = DateTime.Now;
			int yr = int.Parse(ev.Substring(20, 4));
			int mo = int.Parse(ev.Substring(25, 2));
			int dy = int.Parse(ev.Substring(28, 2));
			TimeSpan ts = new DateTime(yr, mo, dy) - now;
			Assert.AreEqual(true, ts.TotalDays <= 1.0);

			currDir.CurrentDir = @"C:\";
			ev = eval.EvaluateString("{key::current directory}");
			Assert.AreEqual(@"current dir: C:\", ev);

			currDir.CurrentDir = @"C:\Bin";
			ev = eval.EvaluateString("{key::current directory}");
			Assert.AreEqual(@"current dir: C:\Bin", ev);

			ev = eval.EvaluateString("{key::temp}");
			DateTime yesterday = DateTime.Now - TimeSpan.FromDays(1.0);
			int inx = @"SpecialDirectory\testing 5\".Length;
			Assert.AreEqual(@"SpecialDirectory\testing 5\", ev.Substring(0, inx));
			yr = int.Parse(ev.Substring(inx, 4));
			mo = int.Parse(ev.Substring(inx + 4, 2));
			dy = int.Parse(ev.Substring(inx + 6, 2));
			int hr = int.Parse(ev.Substring(inx + 8, 2));
			int mi = int.Parse(ev.Substring(inx + 10, 2));
			int sc = int.Parse(ev.Substring(inx + 12, 2));
			int ms = int.Parse(ev.Substring(inx + 14, 3));
			DateTime dtEval = new DateTime(yr, mo, dy, hr, mi, sc, ms);
			TimeSpan span = yesterday - dtEval;
			Assert.AreEqual(true, span.TotalMilliseconds < 50);

			currDir.CurrentDir = @"C:\Doc";
			ev = eval.EvaluateString("{key::Current path}");
			yesterday = DateTime.Now - TimeSpan.FromDays(1.0);
			string res = @"current dir: C:\Doc\SpecialDirectory\testing 6\";
			Assert.AreEqual(true, ev.StartsWith(res));
			inx = res.Length;
			yr = int.Parse(ev.Substring(inx, 4));
			mo = int.Parse(ev.Substring(inx + 4, 2));
			dy = int.Parse(ev.Substring(inx + 6, 2));
			hr = int.Parse(ev.Substring(inx + 8, 2));
			mi = int.Parse(ev.Substring(inx + 10, 2));
			sc = int.Parse(ev.Substring(inx + 12, 2));
			ms = int.Parse(ev.Substring(inx + 14, 3));
			dtEval = new DateTime(yr, mo, dy, hr, mi, sc, ms);
			span = yesterday - dtEval;
			Assert.AreEqual(true, span.TotalMilliseconds < 50);

			ev = eval.EvaluateString("{key::CompoundStamp}");
			yesterday = DateTime.Now - TimeSpan.FromDays(1.0);
			yr = int.Parse(ev.Substring(0, 4));
			mo = int.Parse(ev.Substring(4, 2));
			dy = int.Parse(ev.Substring(6, 2));
			hr = int.Parse(ev.Substring(8, 2));
			mi = int.Parse(ev.Substring(10, 2));
			sc = int.Parse(ev.Substring(12, 2));
			ms = int.Parse(ev.Substring(14, 3));
			dtEval = new DateTime(yr, mo, dy, hr, mi, sc, ms);
			span = yesterday - dtEval;
			Assert.AreEqual(true, span.TotalMilliseconds < 50);

			ev = eval.EvaluateString("{key::CompoundStamp2}");
			yesterday = DateTime.Now - TimeSpan.FromDays(1.0);
			dy = int.Parse(ev.Substring(0, 2));
			mo = int.Parse(ev.Substring(3, 2));
			yr = int.Parse(ev.Substring(6, 4));
			dtEval = new DateTime(yr, mo, dy);
			span = yesterday - dtEval;
			Assert.AreEqual(true, span.TotalDays <= 1);

			//
			/////   Different delimiters
			/////   C suffix for Complex-delimiter
			//

			elems = new Dictionary<string, string>();
			elems.Add("Flat", @"testing ${Counter::page}$");
			elems.Add("Static flat", @"Evaluation of flat: ${key::flat}$");
			elems.Add("Flat indirect", "Flat");
			elems.Add("Flat2", "Eval2 of flat: ${key::${Key::Flat indirect}$}$");
			elems.Add("Static date", @"Evaluation of date: ${date::yyyy.MM.dd}$");
			elems.Add("Current directory", @"current dir: ${CurrentDir::}$");
			elems.Add("temp", @"SpecialDirectory\${key::flat}$\${key::Stamp}$");
			elems.Add("Current path", @"${key::current directory}$\${key::temp}$");
			elems.Add("Stamp", @"${CurrentTime::yyyyMMddHHmmssfff::-1d}$");
			elems.Add("CompoundStamp", "${CurrentTime::${Key::fmt1}$::${Key::DaysAgo}$}$");
			elems.Add("CompoundStamp2", "${CurrentTime::${Key::fmt2}$::${Key::DaysAgo}$}$");
			elems.Add("fmt1", "yyyyMMddHHmmssfff");
			elems.Add("DaysAgo", "-1d");
			elems.Add("fmt2", "dd/MM/yyyy");

			// handling
			IDelimitersAndSeparator delim = new DelimitersAndSeparator("${", "}$");
			context = new List<IProcessEvaluate>();
			context.Add(new ProcessCounter(delim));
			currDir = new ProcessCurrentDir(delim);
			context.Add(currDir);
			context.Add(new ProcessCurrentTime(delim));
			context.Add(new ProcessDate(delim));
			context.Add(new ProcessForeignKey(delim));
			context.Add(new ProcessKey(elems, delim));
			context.Add(new ProcessIf(delim));
			eval = new EnhancedStringEval(context, delim);

			ev = eval.EvaluateString("${key::Flat}$");
			Assert.AreEqual("testing 0", ev);

			ev = eval.EvaluateString("${key::Static flat}$");
			Assert.AreEqual("Evaluation of flat: testing 1", ev);

			ev = eval.EvaluateString("${key::flat indirect}$");
			Assert.AreEqual("Flat", ev);

			ev = eval.EvaluateString("${key::${key::flat indirect}$}$");
			Assert.AreEqual("testing 2", ev);

			ev = eval.EvaluateString("${key::Static ${key::flat indirect}$}$");
			Assert.AreEqual("Evaluation of flat: testing 3", ev);

			ev = eval.EvaluateString("${key::${key::flat indirect}$2}$");
			Assert.AreEqual("Eval2 of flat: testing 4", ev);

			ev = eval.EvaluateString("${key::Static date}$");
			now = DateTime.Now;
			yr = int.Parse(ev.Substring(20, 4));
			mo = int.Parse(ev.Substring(25, 2));
			dy = int.Parse(ev.Substring(28, 2));
			ts = new DateTime(yr, mo, dy) - now;
			Assert.AreEqual(true, ts.TotalDays <= 1.0);

			currDir.CurrentDir = @"C:\";
			ev = eval.EvaluateString("${key::current directory}$");
			Assert.AreEqual(@"current dir: C:\", ev);

			currDir.CurrentDir = @"C:\Bin";
			ev = eval.EvaluateString("${key::current directory}$");
			Assert.AreEqual(@"current dir: C:\Bin", ev);

			ev = eval.EvaluateString("${key::temp}$");
			yesterday = DateTime.Now - TimeSpan.FromDays(1.0);
			inx = @"SpecialDirectory\testing 5\".Length;
			Assert.AreEqual(@"SpecialDirectory\testing 5\", ev.Substring(0, inx));
			yr = int.Parse(ev.Substring(inx, 4));
			mo = int.Parse(ev.Substring(inx + 4, 2));
			dy = int.Parse(ev.Substring(inx + 6, 2));
			hr = int.Parse(ev.Substring(inx + 8, 2));
			mi = int.Parse(ev.Substring(inx + 10, 2));
			sc = int.Parse(ev.Substring(inx + 12, 2));
			ms = int.Parse(ev.Substring(inx + 14, 3));
			dtEval = new DateTime(yr, mo, dy, hr, mi, sc, ms);
			span = yesterday - dtEval;
			Assert.AreEqual(true, span.TotalMilliseconds < 50);

			currDir.CurrentDir = @"C:\Doc";
			ev = eval.EvaluateString("${key::Current path}$");
			yesterday = DateTime.Now - TimeSpan.FromDays(1.0);
			res = @"current dir: C:\Doc\SpecialDirectory\testing 6\";
			Assert.AreEqual(true, ev.StartsWith(res));
			inx = res.Length;
			yr = int.Parse(ev.Substring(inx, 4));
			mo = int.Parse(ev.Substring(inx + 4, 2));
			dy = int.Parse(ev.Substring(inx + 6, 2));
			hr = int.Parse(ev.Substring(inx + 8, 2));
			mi = int.Parse(ev.Substring(inx + 10, 2));
			sc = int.Parse(ev.Substring(inx + 12, 2));
			ms = int.Parse(ev.Substring(inx + 14, 3));
			dtEval = new DateTime(yr, mo, dy, hr, mi, sc, ms);
			span = yesterday - dtEval;
			Assert.AreEqual(true, span.TotalMilliseconds < 50);

			ev = eval.EvaluateString("${key::CompoundStamp}$");
			yesterday = DateTime.Now - TimeSpan.FromDays(1.0);
			yr = int.Parse(ev.Substring(0, 4));
			mo = int.Parse(ev.Substring(4, 2));
			dy = int.Parse(ev.Substring(6, 2));
			hr = int.Parse(ev.Substring(8, 2));
			mi = int.Parse(ev.Substring(10, 2));
			sc = int.Parse(ev.Substring(12, 2));
			ms = int.Parse(ev.Substring(14, 3));
			dtEval = new DateTime(yr, mo, dy, hr, mi, sc, ms);
			span = yesterday - dtEval;
			Assert.AreEqual(true, span.TotalMilliseconds < 50);

			ev = eval.EvaluateString("${key::CompoundStamp2}$");
			yesterday = DateTime.Now - TimeSpan.FromDays(1.0);
			dy = int.Parse(ev.Substring(0, 2));
			mo = int.Parse(ev.Substring(3, 2));
			yr = int.Parse(ev.Substring(6, 4));
			dtEval = new DateTime(yr, mo, dy);
			span = yesterday - dtEval;
			Assert.AreEqual(true, span.TotalDays <= 1);
		}

		[TestMethod]
		public void TestLiteral()
		{
			var elems = new Dictionary<string, string>();
			elems.Add("testing", "{Literal Testing}");
			var cxt = new List<IProcessEvaluate>();
			cxt.Add(new ProcessLiteral());
			cxt.Add(new ProcessKey(elems));
			var eval = new EnhancedStringEval(cxt);
			string ev = eval.EvaluateString("{key::testing}");
			Assert.AreEqual("Literal Testing", ev);

			ev = eval.EvaluateString("{Literal testing without using key processing}");
			Assert.AreEqual("Literal testing without using key processing", ev);

			//
			/////   Different delimiters
			/////   C suffix for Complex-delimiter
			//

			IDelimitersAndSeparator delim = new DelimitersAndSeparator("[Beg]", "[enD]");
			elems = new Dictionary<string, string>();
			elems.Add("testing", @"[Beg]Literal Testing[enD]");
			cxt = new List<IProcessEvaluate>();
			cxt.Add(new ProcessLiteral(delim));
			cxt.Add(new ProcessKey(elems, delim));
			eval = new EnhancedStringEval(cxt, delim);
			ev = eval.EvaluateString("[Beg]key::testing[enD]");
			Assert.AreEqual("Literal Testing", ev);

			ev = eval.EvaluateString("[Beg]Literal testing without using key processing[enD]");
			Assert.AreEqual("Literal testing without using key processing", ev);
		}

		[TestMethod]
		public void TestMemory()
		{
			var elems = new Dictionary<string, string>();
			elems.Add("m1Get", "{Memory::m1}");
			elems.Add("m1Set", "{Memory::m1::432}");
			elems.Add("m2Get", "{Memory::m2}");
			elems.Add("m2Set", "{Memory::m2::234}");

			// handling
			var cxt = new List<IProcessEvaluate>();
			cxt.Add(new ProcessKey(elems));
			cxt.Add(new ProcessMemory());
			var eval = new EnhancedStringEval(cxt);

			var ev = eval.EvaluateString("{key::m1Get}");
			Assert.AreEqual(string.Empty, ev);

			ev = eval.EvaluateString("{key::m1Set}");
			Assert.AreEqual("432", ev);

			ev = eval.EvaluateString("{key::m1Get}");
			Assert.AreEqual("432", ev);

			ev = eval.EvaluateString("{key::m2Set}");
			Assert.AreEqual("234", ev);

			ev = eval.EvaluateString("{key::m2Get}");
			Assert.AreEqual("234", ev);

			ev = eval.EvaluateString("{Memory::FootNote::*}");
			Assert.AreEqual("*", ev);

			ev = eval.EvaluateString("{Memory::FootNote}");
			Assert.AreEqual("*", ev);

			//
			/////   Different delimiters
			/////   C suffix for Complex-delimiter
			//

			elems.Clear();
			elems.Add("m1Get", "<<<Memory::m1>>>");
			elems.Add("m1Set", "<<<Memory::m1::432>>>");
			elems.Add("m2Get", "<<<Memory::m2>>>");
			elems.Add("m2Set", "<<<Memory::m2::234>>>");

			IDelimitersAndSeparator delim = new DelimitersAndSeparator("<<<", ">>>");
			cxt = new List<IProcessEvaluate>();
			cxt.Add(new ProcessKey(elems, delim));
			cxt.Add(new ProcessMemory(delim));
			eval = new EnhancedStringEval(cxt, delim);

			ev = eval.EvaluateString("<<<key::m1Get>>>");
			Assert.AreEqual(string.Empty, ev);

			ev = eval.EvaluateString("<<<key::m1Set>>>");
			Assert.AreEqual("432", ev);

			ev = eval.EvaluateString("<<<key::m1Get>>>");
			Assert.AreEqual("432", ev);

			ev = eval.EvaluateString("<<<key::m2Set>>>");
			Assert.AreEqual("234", ev);

			ev = eval.EvaluateString("<<<key::m2Get>>>");
			Assert.AreEqual("234", ev);

			ev = eval.EvaluateString("<<<Memory::FootNote::*>>>");
			Assert.AreEqual("*", ev);

			ev = eval.EvaluateString("<<<Memory::FootNote>>>");
			Assert.AreEqual("*", ev);
		}

		[TestMethod]
		public void TestSpcialHandlingOfDate()
		{
			IDelimitersAndSeparator delim = new DelimitersAndSeparator("${", "}$");
			var ctx = new List<IProcessEvaluate>();
			ctx.Add(new ProcessCounter(delim));
			ctx.Add(new ProcessCurrentTime(delim));
			ctx.Add(new ProcessMemory(delim));
			ctx.Add(new ProcessLiteral(delim));
			ctx.Add(new ProcessForeignKey(delim));
			var eval = new EnhancedStringEvalNoyyyMMdd(ctx, delim);

			string fmtD = eval.EvaluateString("#{Memory::fmtD::dd/MM/yyyy}#");
			string fmtT = eval.EvaluateString("#{Memory::fmtT::HH:mm:ss}#");
			string ev = eval.EvaluateString("#{CurrentTime::#{Memory::fmtD}# #{Memory::fmtT}#::-#{Counter::c1}#d}#");
			DateTime today = DateTime.Now;

			int dy = int.Parse(ev.Substring(0, 2));
			int mo = int.Parse(ev.Substring(3, 2));
			int yr = int.Parse(ev.Substring(6, 4));
			int hr = int.Parse(ev.Substring(11, 2));
			int mi = int.Parse(ev.Substring(14, 2));
			int sc = int.Parse(ev.Substring(17, 2));
			DateTime dtEval = new DateTime(yr, mo, dy, hr, mi, sc);
			TimeSpan span = today - dtEval;
			Assert.AreEqual(true, span.TotalSeconds < 1.0);

			fmtD = eval.EvaluateString("${Memory::fmtD::dd/MM/yyyy}$");
			fmtT = eval.EvaluateString("${Memory::fmtT::HH:mm:ss}$");
			ev = eval.EvaluateString("#{CurrentTime::#{Memory::fmtD}# #{Memory::fmtT}#::-#{Counter::c1}#d}#");
			DateTime yesterday = DateTime.Now - TimeSpan.FromDays(1.0);
			dy = int.Parse(ev.Substring(0, 2));
			mo = int.Parse(ev.Substring(3, 2));
			yr = int.Parse(ev.Substring(6, 4));
			hr = int.Parse(ev.Substring(11, 2));
			mi = int.Parse(ev.Substring(14, 2));
			sc = int.Parse(ev.Substring(17, 2));
			dtEval = new DateTime(yr, mo, dy, hr, mi, sc);
			span = yesterday - dtEval;
			Assert.AreEqual(true, span.TotalSeconds < 1.0);

			string tmpPath = Path.GetTempPath();
			string fileNm = System.Reflection.MethodBase.GetCurrentMethod().Name;
			string filePath = Path.Combine(tmpPath, fileNm + ".txt");
			using (var fs = new FileStream(filePath, FileMode.Create))
			using (var sw = new StreamWriter(fs))
			{
				sw.WriteLine("fmtD=#{dd/MM/yyyy}#");
				sw.WriteLine("fmtT=#{HH:mm:ss}#");
			}

			fmtD = string.Format("#{{ForeignKey::{0}::fmtD}}#", filePath);
			fmtT = string.Format("#{{ForeignKey::{0}::fmtT}}#", filePath);
			var evalStr = string.Format("#{{CurrentTime::{0} {1}::-#{{Counter::c1}}#d}}#", fmtD, fmtT);
			ev = eval.EvaluateString(evalStr);
			DateTime TwoDaysAgo = DateTime.Now - TimeSpan.FromDays(2.0);
			dy = int.Parse(ev.Substring(0, 2));
			mo = int.Parse(ev.Substring(3, 2));
			yr = int.Parse(ev.Substring(6, 4));
			hr = int.Parse(ev.Substring(11, 2));
			mi = int.Parse(ev.Substring(14, 2));
			sc = int.Parse(ev.Substring(17, 2));
			dtEval = new DateTime(yr, mo, dy, hr, mi, sc);
			span = TwoDaysAgo - dtEval;
			Assert.AreEqual(true, span.TotalSeconds < 1.0);

			File.Delete(filePath);

			//
			// Key test
			//
			var elems = new Dictionary<string, string>();
			elems.Add("fmtD", "#{dd/MM/yyyy}#");
			elems.Add("fmtT", "#{HH:mm:ss}#");

			IProcessEvaluate pKeyEval = new ProcessKey(elems, delim);
			eval.OnEvaluateContext += pKeyEval.Evaluate;
			ev = eval.EvaluateString("#{CurrentTime::#{Key::fmtD}# #{Key::fmtT}#::-#{Counter::c1}#d}#");
			DateTime ThreeDaysAgo = DateTime.Now - TimeSpan.FromDays(3.0);
			dy = int.Parse(ev.Substring(0, 2));
			mo = int.Parse(ev.Substring(3, 2));
			yr = int.Parse(ev.Substring(6, 4));
			hr = int.Parse(ev.Substring(11, 2));
			mi = int.Parse(ev.Substring(14, 2));
			sc = int.Parse(ev.Substring(17, 2));
			dtEval = new DateTime(yr, mo, dy, hr, mi, sc);
			span = ThreeDaysAgo - dtEval;
			Assert.AreEqual(true, span.TotalSeconds < 1.0);

			// Try again without the Key Evaluate ProcessXxx
			eval.OnEvaluateContext -= pKeyEval.Evaluate;
			ev = eval.EvaluateString("#{CurrentTime::#{Key::fmtD}# #{Key::fmtT}#::-#{Counter::c1}#d}#");
			// The only thing to evaluate is #{Counter::c1}# (we took ${Key::value}$ away, therefore
			// ${CurrentTime::value...}$ will not evaluate correctly...
			// Also, the class EnhancedStringEvalNoyyyMMdd transforms the delimiters "#{" and "}#" to 
			// "${" and "}$" respectively, but it does not transform the delimiters back to "#{" and "}#" 
			// so our expectation is that the evaluation will yield:
			Assert.AreEqual("${CurrentTime::${Key::fmtD}$ ${Key::fmtT}$::-4d}$", ev);
		}

		[TestMethod]
		public void PrePostEvaluate()
		{
			var context = new List<IProcessEvaluate>();
			context.Add(new ProcessCurrentTime());
			EnhancedStringEval proxy = new PrePostEvaluateOnce(context);

			string ev = proxy.EvaluateString("Today, ${c}, every man should exceed his grasp");
			DateTime today = DateTime.Today;
			int mo = int.Parse(ev.Substring(7, 2));
			int dy = int.Parse(ev.Substring(10, 2));
			int yr = int.Parse(ev.Substring(13, 4));
			DateTime dtEval = new DateTime(yr, mo, dy);
			Assert.AreEqual(today, dtEval);

			ev = proxy.EvaluateString("Tomorrow, ${c+1}, every woman should exceed her grasp");
			mo = int.Parse(ev.Substring(10, 2));
			dy = int.Parse(ev.Substring(13, 2));
			yr = int.Parse(ev.Substring(16, 4));
			dtEval = new DateTime(yr, mo, dy);
			Assert.AreEqual(1, (dtEval - today).TotalDays);
		}

		[TestMethod]
		public void TestOpenClose()
		{
			////string pattern = string.Format(@"^(((?<Open>{)[^{}]*)+((?<Close-Open>(}))[^{}]*)+)*(?(Open)(?!))$", _openDelimiter, _closeDelimiter);
			//string pattern = @"^(((?<Open>{0})[^{0}{1}]*)+((?<Close-Open>({1}))[^{0}{1}]*)+)*(?(Open)(?!))$";
			//return string.Format(pattern, OpenDelimEquivalent, CloseDelimEquivalent);

			string pattern = "^[^<>]*(((?'Open'<)[^<>]*)+((?'Close-Open'>)[^<>]*)+)*(?(Open)(?!))$";
			string input = "<abc><mno<xyz>>";

			Match m = Regex.Match(input, pattern);
			Assert.AreEqual(true, m.Success);

			Regex _reOpenClose = new Regex(pattern, RegexOptions.Singleline);
			m = _reOpenClose.Match(input);
			Assert.AreEqual(true, m.Success);

			m = _reOpenClose.Match("");
			Assert.AreEqual(true, m.Success);

			Assert.AreEqual(string.Empty, m.ToString());

			pattern = @"^[^{}]*(((?<Open>{)[^{}]*)+((?<Close-Open>(}))[^{}]*)+)*(?(Open)(?!))$";
			_reOpenClose = new Regex(pattern, RegexOptions.Singleline);
			m = _reOpenClose.Match(string.Empty);
			Assert.AreEqual(true, m.Success);

			m = _reOpenClose.Match(@"{abc}");
			Assert.AreEqual(true, m.Success);

			m = _reOpenClose.Match(@"abc{def}");
			Assert.AreEqual(true, m.Success);

			m = _reOpenClose.Match(@"abc{def::{ghi::jkl}}");
			Assert.AreEqual(true, m.Success);

			m = _reOpenClose.Match(@"abc{def::{ghi:{}:jkl}}");
			Assert.AreEqual(true, m.Success);

			m = _reOpenClose.Match(@"abc{def::{ghi:{}:jkl}}{");
			Assert.AreEqual(false, m.Success);
		}
	}

	/// <summary>
	/// Process original delimiter: ("#{", "}#") as ("${", "}$")
	/// </summary>
	sealed internal class EnhancedStringEvalNoyyyMMdd : EnhancedStringEval
	{
		public EnhancedStringEvalNoyyyMMdd(List<IProcessEvaluate> context) : base(context) {}
		public EnhancedStringEvalNoyyyMMdd(List<IProcessEvaluate> context, IDelimitersAndSeparator delim) : base(context, delim) {}

		/// <summary>
		/// Change "#{" and "}#" to "${" and "}$" respectively
		/// </summary>
		/// <param name="text"></param>
		/// <returns></returns>
		protected override string PreEvaluate(string text)
		{
			return text.Replace("#{", "${").Replace("}#", "}$");
		}
	}

	/// <summary>
	/// Process Counter having a name ending with a colon suffix
	/// </summary>
	sealed internal class EnhStringTrailingColon : EnhancedStringEval
	{
		public EnhStringTrailingColon(List<IProcessEvaluate> context) : base(context)
		{
			// If {Identifier::Value} is of type:
			//		{Identifier::Name::value} where name is a string ending with a colon then
			//		>	identifier has no colons
			//		>	double colon
			//		>	Name MAY ends with a colon
			//		>	may be followed with a double colon etc
			string pat1 = @"(?<pre>({)[^{}:]+(::)(:?)[^{}:]+(:[^{}:]+)*)(?<trailingColon>:?)(?<post>(::[^{}]*)?(}))";
			_reSeparator = new Regex(pat1, RegexOptions.IgnoreCase | RegexOptions.Singleline);
		}

		protected override string PreEvaluate(string text)
		{
			if (!_reSeparator.IsMatch(text)) return text;
			string preText = _reSeparator.Replace(text,
				m => m.Groups["pre"].Value + (m.Groups["trailingColon"].Value == string.Empty ? "" : "\u0004") + m.Groups["post"].Value);
			return preText;
		}

		protected override string  PostEvaluate(string text)
		{
			 return text.Replace('\u0004', ':');
		}

		private Regex _reSeparator;
	}

	/// <summary>
	/// Process old constructs like ${c} that is supposed to yield the current calendar date
	/// to us it means: transform ${c} to {CurrentTime::MM/dd/yyyy}
	/// and ${c+/-n} transform it to {CurrentTime::MM/dd/yyyy::+/-nd}
	/// so ${c-1} that is supposed to yield yesterday transform it to {CurrentTime::MM/dd/yyyy::-1d}
	/// </summary>
	sealed internal class PrePostEvaluateOnce : EnhancedStringEval
	{
		public PrePostEvaluateOnce(IEnumerable<IProcessEvaluate> context)
			: base(context)
		{
			_reSpeicial = new Regex(@"\$\{c((?<direction>[-+])(?<amount>\d+))?\}", RegexOptions.Singleline | RegexOptions.IgnoreCase);
		}

		public override string EvaluateString(string text)
		{
			string preText = MyPreEvaluate(text);
			return base.EvaluateString(preText);
		}

		private string MyPreEvaluate(string text)
		{
			Match m = _reSpeicial.Match(text);
			if (!m.Success) return text;

			return _reSpeicial.Replace(text, me => CalendarReplace(me));
		}

		private string CalendarReplace(Match m)
		{

			string direction = m.Groups["direction"].Value;
			if (string.IsNullOrEmpty(direction))
				return "{CurrentTime::MM/dd/yyyy}";

			string amount = m.Groups["amount"].Value;
			if (direction == "+")
				return string.Format("{{CurrentTime::MM/dd/yyyy::{0}d}}", amount);

			return string.Format("{{CurrentTime::MM/dd/yyyy::-{0}d}}", amount);
		}

		private Regex _reSpeicial;
	}
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
United States United States
avifarah@gmail.com

Comments and Discussions