コード例 #1
0
        public static FormattingInfo Get(FormatDetails formatDetails, Format format, object currentValue)
        {
            var fi = s_Pool.Get();

            fi.Init(formatDetails, format, currentValue);
            return(fi);
        }
コード例 #2
0
		public FormattingInfo(FormattingInfo parent, FormatDetails formatDetails, Format format, object currentValue)
		{
			this.Parent = parent;
			CurrentValue = currentValue;
			Format = format;
			FormatDetails = formatDetails;
		}
コード例 #3
0
        public static FormattingInfo Get(FormattingInfo parent, FormatDetails formatDetails, Placeholder placeholder, object currentValue)
        {
            var fi = s_Pool.Get();

            fi.Init(parent, formatDetails, placeholder, currentValue);
            return(fi);
        }
コード例 #4
0
        public void EvaluateFormat(object current, Format format,
                                   ref bool handled, IOutput output, FormatDetails formatDetails)
        {
            XElement currentXElement = null;

            if (format != null && format.HasNested)
            {
                return;
            }
            // if we need to format list of XElements then we just take and format first
            var xElmentsAsList = current as IList <XElement>;

            if (xElmentsAsList != null && xElmentsAsList.Count > 0)
            {
                currentXElement = xElmentsAsList[0];
                handled         = true;
            }

            var currentAsXElement = (currentXElement) ?? current as XElement;

            if (currentAsXElement != null)
            {
                output.Write(currentAsXElement.Value, formatDetails);
                handled = true;
            }
        }
コード例 #5
0
		public FormattingInfo(FormattingInfo parent, FormatDetails formatDetails, Placeholder placeholder, object currentValue)
		{
			this.Parent = parent;
			this.FormatDetails = formatDetails;
			this.Placeholder = placeholder;
			this.Format = placeholder.Format;
			this.CurrentValue = currentValue;
		}
コード例 #6
0
        /// <summary>
        /// Writes the formatting result into an <see cref="IOutput"/> instance.
        /// </summary>
        /// <param name="output">The <see cref="IOutput"/> where the result is written to.</param>
        /// <param name="format">The format string.</param>
        /// <param name="args">The objects to format.</param>
        public void FormatInto(IOutput output, string format, params object[] args)
        {
            var formatParsed  = Parser.ParseFormat(format, GetNotEmptyFormatterExtensionNames());
            var current       = args.Length > 0 ? args[0] : args; // The first item is the default.
            var formatDetails = new FormatDetails(this, formatParsed, args, null, null, output);

            Format(formatDetails, formatParsed, current);
        }
コード例 #7
0
        /// <summary>
        /// Writes the formatting result into an <see cref="IOutput"/> instance, using the <see cref="FormatCache"/>.
        /// </summary>
        /// <param name="cache">The <see cref="FormatCache"/> to use.</param>
        /// <param name="output">The <see cref="IOutput"/> where the result is written to.</param>
        /// <param name="format">The format string.</param>
        /// <param name="args">The objects to format.</param>
        public void FormatWithCacheInto(ref FormatCache cache, IOutput output, string format, params object[] args)
        {
            cache ??= new FormatCache(Parser.ParseFormat(format, GetNotEmptyFormatterExtensionNames()));
            var current       = args.Length > 0 ? args[0] : args; // The first item is the default.
            var formatDetails = new FormatDetails(this, cache.Format, args, cache, null, output);

            Format(formatDetails, cache.Format, current);
        }
コード例 #8
0
        /// <summary>
        /// Creates a new instance of <see cref="FormattingInfo"/>.
        /// </summary>
        /// <param name="format">The input format string.</param>
        /// <param name="data">The data argument.</param>
        /// <returns>A new instance of <see cref="FormattingInfo"/>.</returns>
        public static FormattingInfo Create(string format, IList <object?> data)
        {
            var formatter    = new SmartFormatter(new SmartSettings());
            var formatParsed = formatter.Parser.ParseFormat(format);
            // use StringOutput because we don't have to care about disposing.
            var formatDetails = new FormatDetails().Initialize(formatter, formatParsed, data, null, new StringOutput());

            return(new FormattingInfo().Initialize(formatDetails, formatDetails.OriginalFormat, data));
        }
コード例 #9
0
        private static CultureInfo GetCulture(FormatDetails formatDetails)
        {
            if (formatDetails.Provider is CultureInfo info)
            {
                return(info);
            }

            return(CultureInfo.CurrentUICulture);
        }
コード例 #10
0
        /// <summary>
        /// Replaces one or more format items in a specified string with the string representation of a specific object.
        /// </summary>
        /// <param name="provider">The <see cref="IFormatProvider" /> to use.</param>
        /// <param name="format">A composite format string.</param>
        /// <param name="args">The object to format.</param>
        /// <returns>Returns the formatted input with items replaced with their string representation.</returns>
        public string Format(IFormatProvider?provider, string format, params object[] args)
        {
            var output        = new StringOutput(format.Length + args.Length * 8);
            var formatParsed  = Parser.ParseFormat(format, GetNotEmptyFormatterExtensionNames());
            var current       = args.Length > 0 ? args[0] : args; // The first item is the default.
            var formatDetails = new FormatDetails(this, formatParsed, args, null, provider, output);

            Format(formatDetails, formatParsed, current);

            return(output.ToString());
        }
コード例 #11
0
        /// <summary>
        /// Replaces one or more format items in a specified string with the string representation of a specific object,
        /// using the <see cref="FormatCache"/>.
        /// </summary>
        /// <param name="cache">The <see cref="FormatCache" /> to use.</param>
        /// <param name="format">A composite format string.</param>
        /// <param name="args">The objects to format.</param>
        /// <returns>Returns the formatted input with items replaced with their string representation.</returns>
        public string FormatWithCache(ref FormatCache?cache, string format, params object[] args)
        {
            var output = new StringOutput(format.Length + args.Length * 8);

            cache ??= new FormatCache(Parser.ParseFormat(format, GetNotEmptyFormatterExtensionNames()));
            var current       = args.Length > 0 ? args[0] : args; // The first item is the default.
            var formatDetails = new FormatDetails(this, cache.Format, args, cache, null, output);

            Format(formatDetails, cache.Format, current);

            return(output.ToString());
        }
コード例 #12
0
 private void GetFormatsConverted(IntPtr format)
 {
     newFormat = IntPtr.Zero;
     //cbFormatConverted.DataSource = AudioCompressionManager.GetCompatibleFormatList(format);
     cbFormatConverted.Items.Clear();
     FormatDetails[] fdArr = AudioCompressionManager.GetCompatibleFormatList(format);
     for (int i = 0; i < fdArr.Length; i++)
     {
         FormatDetails fd = fdArr[i];
         fd.ShowFormatTag = true;
         cbFormatConverted.Items.Add(fd);
     }
 }
コード例 #13
0
        private void btnFile2_Click(object sender, EventArgs e)
        {
            if (ofdFile.ShowDialog(this) == DialogResult.OK)
            {
                tbFile2.Text = ofdFile.FileName;
                int    lenExt = 4;
                string ext    = ofdFile.FileName.Substring(ofdFile.FileName.Length - lenExt,
                                                           lenExt).ToLower();
                switch (ext)
                {
                case ".au":
                case ".snd":
                    ar = new AuReader(File.OpenRead(ofdFile.FileName));
                    break;

                case ".wav":
                    ar = new WaveReader(File.OpenRead(ofdFile.FileName));
                    break;

                case ".avi":
                    ar = new AviReader(File.OpenRead(ofdFile.FileName));
                    if (!((AviReader)ar).HasAudio)
                    {
                        MessageBox.Show("Avi stream has not audio track");
                        return;
                    }
                    break;

                case ".mp3":
                    ar = new Mp3Reader(File.OpenRead(ofdFile.FileName));
                    break;

                default:
                    ar = new DsReader(ofdFile.FileName);
                    if (!((DsReader)ar).HasAudio)
                    {
                        MessageBox.Show("DirectShow stream has not audio track");
                        return;
                    }
                    break;
                }
                oldFormat = ar.ReadFormat();
                FormatDetails fd = AudioCompressionManager.GetFormatDetails(oldFormat);
                lblFileFormat.Text = string.Format("{0} {1}", AudioCompressionManager.GetFormatTagDetails(fd.FormatTag).FormatTagName, fd.FormatName);
                GetFormatsConverted(oldFormat);
                gbConvert.Enabled  = true;
                btnMakeMp3.Enabled = false;
            }
        }
コード例 #14
0
        public virtual void TestSendSheet()
        {
            server.setResponseBody("../../../TestSDK/resources/sendEmails.json");

            string[]   emailAddress = new string[] { "*****@*****.**" };
            SheetEmail email        = new SheetEmail();

            email.Format = SheetEmailFormat.PDF;
            FormatDetails format = new FormatDetails();

            format.PaperSize    = PaperSize.A0;
            email.FormatDetails = format;
            email.To            = new List <string>(emailAddress);
            sheetResource.SendSheet(1234L, email);
        }
コード例 #15
0
        private void TestSendSheet()
        {
            List <Recipient> recipients = new List <Recipient>();
            Recipient        recipient  = new Recipient();

            recipient.Email = "*****@*****.**";
            recipients.Add(recipient);

            FormatDetails formatDetails = new FormatDetails();

            formatDetails.PaperSize = PaperSize.A4;

            SheetEmail email = new SheetEmail.CreateSheetEmail(recipients, SheetEmailFormat.PDF).SetFormatDetails(formatDetails).Build();

            smartsheet.SheetResources.SendSheet(newSheetHome.Id.Value, email);
        }
コード例 #16
0
        public void EvaluateSelector(object current, Selector selector, ref bool handled,
                                     ref object result, FormatDetails formatDetails)
        {
            var element = current as XElement;

            if (element != null)
            {
                // Find elements that match a selector
                var selectorMatchedElements = element.Elements()
                                              .Where(x => x.Name.LocalName == selector.Text).ToList();
                if (selectorMatchedElements.Any())
                {
                    result  = selectorMatchedElements;
                    handled = true;
                }
            }
        }
コード例 #17
0
 public void Write(string text, int startIndex, int length, FormatDetails formatDetails)
 {
     // Depending on the nested level, we will color this item differently:
     if (formatDetails.FormatError != null)
     {
         output.BackColor(errorColor).Append(text, startIndex, length);
     }
     else if (formatDetails.Placeholder == null)
     {
         // There is no "nesting" so just output plain text:
         output.Append(text, startIndex, length);
     }
     else
     {
         var nestedDepth = formatDetails.Placeholder.NestedDepth;
         var backcolor   = this.nestedColors[nestedDepth % nestedColors.Length];
         output.BackColor(backcolor).Append(text, startIndex, length);
     }
 }
コード例 #18
0
        public void EvaluateSelector(object current, Selector selector, ref bool handled, ref object result,
                                     FormatDetails formatDetails)
        {
            // Only makes sense to check if current is an IDictionary<string, object>
            var genericDictionary = current as IDictionary <string, object>;

            if (genericDictionary != null)
            {
                // First, try to see if we can find it using the normal casing.
                if (!genericDictionary.TryGetValue(selector.Text, out result))
                {
                    // If we can't find it with normal casing, try to find it in all lower case
                    var selectorTextLower = selector.Text.ToLowerInvariant();
                    if (!genericDictionary.TryGetValue(selectorTextLower, out result))
                    {
                        // result = new MissingDictionarySourceResult();
                    }
                }
                handled = true;
            }
        }
コード例 #19
0
        public void SmartFormatter_FormatDetails()
        {
            var args = new object[] { new Dictionary <string, string> {
                                          { "Greeting", "Hello" }
                                      } };
            var format    = "{Greeting}";
            var output    = new StringOutput();
            var formatter = new SmartFormatter();

            formatter.Settings.CaseSensitivity = CaseSensitivityType.CaseInsensitive;
            formatter.Settings.ConvertCharacterStringLiterals = true;
            formatter.Settings.FormatErrorAction = ErrorAction.OutputErrorInResult;
            formatter.Settings.ParseErrorAction  = ErrorAction.OutputErrorInResult;
            var formatParsed  = formatter.Parser.ParseFormat(format, new [] { string.Empty });
            var formatDetails = new FormatDetails(formatter, formatParsed, args, null, null, output);

            Assert.AreEqual(args, formatDetails.OriginalArgs);
            Assert.AreEqual(format, formatDetails.OriginalFormat.RawText);
            Assert.AreEqual(formatter.Settings, formatDetails.Settings);
            Assert.IsTrue(formatDetails.FormatCache == null);
        }
コード例 #20
0
ファイル: TimeFormatter.cs プロジェクト: rajeshwarn/Creek
        public void EvaluateFormat(object current, Core.Parsing.Format format, ref bool handled, IOutput output, FormatDetails formatDetails)
        {
            if (format != null && format.HasNested)
            {
                return;
            }
            var      formatText = format != null ? format.Text : "";
            TimeSpan fromTime;

            if (current is TimeSpan)
            {
                fromTime = (TimeSpan)current;
            }
            else if (current is DateTime && formatText.StartsWith("timestring"))
            {
                formatText = formatText.Substring(10);
                fromTime   = DateTime.Now.Subtract((DateTime)current);
            }
            else
            {
                return;
            }
            var timeTextInfo = GetTimeTextInfo(formatDetails.Provider);

            if (timeTextInfo == null)
            {
                return;
            }
            var formattingOptions = TimeSpanFormatOptionsConverter.Parse(formatText);
            var timeString        = TimeSpanUtility.ToTimeString(fromTime, formattingOptions, timeTextInfo);

            output.Write(timeString, formatDetails);
            handled = true;
        }
コード例 #21
0
        public void EvaluateFormat(object current, Format format, ref bool handled, IOutput output,
                                   FormatDetails formatDetails)
        {
            if (format == null)
            {
                return;
            }

            // Ignore a leading ":", which is used to bypass the PluralLocalizationExtension
            if (format.baseString[format.startIndex] == ':')
            {
                format = format.Substring(1);
            }

            // See if the format string contains un-nested "|":
            var parameters = format.Split("|");

            if (parameters.Count == 1)
            {
                return;                        // There are no parameters found.
            }
            // See if the value is a number:
            var currentIsNumber =
                current is byte || current is short || current is int || current is long ||
                current is float || current is double || current is decimal;

            // An Enum is a number too:
            if (currentIsNumber == false && current != null && current.GetType().IsEnum)
            {
                currentIsNumber = true;
            }
            var currentNumber = currentIsNumber ? Convert.ToDecimal(current) : 0;

            int paramIndex; // Determines which parameter to use for output

            // First, we'll see if we are using "complex conditions":
            if (currentIsNumber)
            {
                paramIndex = -1;
                while (true)
                {
                    paramIndex++;
                    if (paramIndex == parameters.Count)
                    {
                        // We reached the end of our parameters,
                        // so we output nothing
                        handled = true;
                        return;
                    }
                    bool   conditionWasTrue;
                    Format outputItem;
                    if (!TryEvaluateCondition(parameters[paramIndex], currentNumber, out conditionWasTrue, out outputItem))
                    {
                        // This parameter doesn't have a
                        // complex condition (making it a "else" condition)

                        // Only do "complex conditions" if the first item IS a "complex condition".
                        if (paramIndex == 0)
                        {
                            break;
                        }
                        // Otherwise, output the "else" section:
                        conditionWasTrue = true;
                    }

                    // If the conditional statement was true, then we can break.
                    if (conditionWasTrue)
                    {
                        formatDetails.Formatter.Format(output, outputItem, current, formatDetails);
                        handled = true;
                        return;
                    }
                }
                // We don't have any "complex conditions",
                // so let's do the normal conditional formatting:
            }

            var paramCount = parameters.Count;

            // Determine the Current item's Type:
            if (currentIsNumber)
            {
                if (currentNumber < 0)
                {
                    paramIndex = paramCount - 1;
                }
                else
                {
                    paramIndex = Math.Min((int)Math.Floor(currentNumber), paramCount - 1);
                }
            }
            else if (current is bool)
            {
                // Bool: True|False
                var arg = (bool)current;
                if (arg)
                {
                    paramIndex = 0;
                }
                else
                {
                    paramIndex = 1;
                }
            }
            else if (current is DateTime)
            {
                // Date: Past|Present|Future   or   Past/Present|Future
                var arg = (DateTime)current;
                if (paramCount == 3 && arg.Date == DateTime.Today)
                {
                    paramIndex = 1;
                }
                else if (arg <= DateTime.Now)
                {
                    paramIndex = 0;
                }
                else
                {
                    paramIndex = paramCount - 1;
                }
            }
            else if (current is TimeSpan)
            {
                // TimeSpan: Negative|Zero|Positive  or  Negative/Zero|Positive
                var arg = (TimeSpan)current;
                if (paramCount == 3 && arg == TimeSpan.Zero)
                {
                    paramIndex = 1;
                }
                else if (arg.CompareTo(TimeSpan.Zero) <= 0)
                {
                    paramIndex = 0;
                }
                else
                {
                    paramIndex = paramCount - 1;
                }
            }
            else if (current is string)
            {
                // String: Value|NullOrEmpty
                var arg = (string)current;
                paramIndex = !string.IsNullOrEmpty(arg) ? 0 : 1;
            }
            else
            {
                // Object: Something|Nothing
                var arg = current;
                paramIndex = arg != null ? 0 : 1;
            }

            // Now, output the selected parameter:
            var selectedParameter = parameters[paramIndex];

            // Output the selectedParameter:
            formatDetails.Formatter.Format(output, selectedParameter, current, formatDetails);
            handled = true;
        }
コード例 #22
0
		public FormattingInfo(FormatDetails formatDetails, Format format, object currentValue)
			: this(null, formatDetails, format, currentValue)
		{
		}
コード例 #23
0
        public void EvaluateSelector(object current, Selector selector, ref bool handled, ref object result, FormatDetails formatDetails)
        {
            // See if current is a IDictionary and contains the selector:
            var dict = current as IDictionary;

            if (dict != null && dict.Contains(selector.Text))
            {
                result  = dict[selector.Text];
                handled = true;
            }
        }
        /// <summary>Process a format token and write the resulting output if it can be handled.</summary>
        /// <param name="current">The current token value.</param>
        /// <param name="format">The format token to apply.</param>
        /// <param name="handled">Whether this formatter plugin can handle the format token.</param>
        /// <param name="output">The result output to which to write the formatted value.</param>
        /// <param name="formatDetails">The format metadata.</param>
        public void EvaluateFormat(object current, Format format, ref bool handled, IOutput output, FormatDetails formatDetails)
        {
            // validate
            if (format == null || !(current is DateTime))
            {
                return;
            }

            // parse token
            FormatItem item = format.Items[0];

            string[] formatSpec = item.Text.Split('|');
            if (formatSpec.Length < 2)
            {
                return;
            }
            string dateFormat = formatSpec[0];
            string offset     = formatSpec[1];

            // write offset date
            DateTime date = (DateTime)current;

            try
            {
                date = date.Offset(offset);
            }
            catch (Exception ex)
            {
                throw new SmartFormat.Core.FormatException(format, ex, item.endIndex);
            }
            output.Write(date.ToString(dateFormat), formatDetails);
            handled = true;
        }
コード例 #25
0
        public static PooledObject <FormatDetails> Get(SmartFormatter formatter, Format originalFormat, object[] originalArgs, FormatCache formatCache, IFormatProvider provider, IOutput output, out FormatDetails value)
        {
            var po = s_Pool.Get(out value);

            value.Init(formatter, originalFormat, originalArgs, formatCache, provider, output);
            return(po);
        }
コード例 #26
0
 public static void Release(FormatDetails toRelease) => s_Pool.Release(toRelease);
コード例 #27
0
        /// <summary>
        /// This allows an integer to be used as a selector to index an array (or list).
        ///
        /// This is better described using an example:
        /// CustomFormat("{Dates.2.Year}", {#1/1/2000#, #12/31/2999#, #9/9/9999#}) = "9999"
        /// The ".2" selector is used to reference Dates[2].
        /// </summary>
        public void EvaluateSelector(object current, Selector selector, ref bool handled, ref object result, FormatDetails formatDetails)
        {
            // See if we're trying to access a specific index:
            int itemIndex;
            var currentList = current as IList;
            var isAbsolute  = (selector.SelectorIndex == 0 && selector.Operator.Length == 0);

            if (!isAbsolute && currentList != null && int.TryParse(selector.Text, out itemIndex) && itemIndex < currentList.Count)
            {
                // The current is a List, and the selector is a number;
                // let's return the List item:
                // Example: {People[2].Name}
                //           ^List  ^itemIndex
                result  = currentList[itemIndex];
                handled = true;
            }


            // We want to see if there is an "Index" property that was supplied.
            if (selector.Text.Equals("index", StringComparison.OrdinalIgnoreCase))
            {
                // Looking for "{Index}"
                if (selector.SelectorIndex == 0)
                {
                    result  = CollectionIndex;
                    handled = true;
                    return;
                }

                // Looking for 2 lists to sync: "{List1: {List2[Index]} }"
                if (currentList != null && 0 <= CollectionIndex && CollectionIndex < currentList.Count)
                {
                    result  = currentList[CollectionIndex];
                    handled = true;
                }
            }
        }
コード例 #28
0
        /// <summary>Process a selector token and get the represented value if it can be handled.</summary>
        /// <param name="current">The current token value.</param>
        /// <param name="selector">The selector token to apply.</param>
        /// <param name="handled">Whether this selector plugin can handle the format token.</param>
        /// <param name="result">The selected value.</param>
        /// <param name="formatDetails">The format metadata.</param>
        public void EvaluateSelector(object current, Selector selector, ref bool handled, ref object result, FormatDetails formatDetails)
        {
            // parse date
            DateTime?parsed = new TimeParser().ParseName(selector.Text);

            if (!parsed.HasValue)
            {
                return;
            }

            // apply token
            result  = parsed;
            handled = true;
        }
コード例 #29
0
        private void cbFormat_SelectedIndexChanged(object sender, EventArgs e)
        {
            FormatDetails pafd = (FormatDetails)cbFormat.SelectedItem;

            format = pafd.FormatHandle;
        }
コード例 #30
0
        /// <summary>
        /// Performs the default index-based selector, same as String.Format.
        /// </summary>
        public void EvaluateSelector(object current, Selector selector, ref bool handled, ref object result, FormatDetails formatDetails)
        {
            int selectorValue;

            if (int.TryParse(selector.Text, out selectorValue))
            {
                // Argument Index:
                // Just like String.Format, the arg index must be in-range,
                // should be the first item, and shouldn't have any operator:
                if (selector.SelectorIndex == 0 &&
                    selectorValue < formatDetails.OriginalArgs.Length &&
                    selector.Operator == "")
                {
                    // This selector is an argument index.
                    result  = formatDetails.OriginalArgs[selectorValue];
                    handled = true;
                }
                // Alignment:
                // An alignment item should be preceeded by a comma
                else if (selector.Operator == ",")
                {
                    // This selector is actually an Alignment modifier.
                    result = current;                                    // (don't change the current item)
                    formatDetails.Placeholder.Alignment = selectorValue; // Set the alignment
                    handled = true;
                }
            }
        }
コード例 #31
0
        public void EvaluateFormat(object current, Core.Parsing.Format format, ref bool handled, IOutput output, FormatDetails formatDetails)
        {
            // Ignore formats that start with "?" (this can be used to bypass this extension)
            if (format == null || format.baseString[format.startIndex] == ':')
            {
                return;
            }

            // Extract the plural words from the format string:
            var pluralWords = format.Split("|");

            // This extension requires at least two plural words:
            if (pluralWords.Count == 1)
            {
                return;
            }

            // See if the value is a number:
            var currentIsNumber =
                current is byte || current is short || current is int || current is long ||
                current is float || current is double || current is decimal;

            // This extension only formats numbers:
            if (!currentIsNumber)
            {
                return;
            }

            // Normalize the number to decimal:
            var value = Convert.ToDecimal(current);

            // Get the plural rule:
            var provider   = formatDetails.Provider;
            var pluralRule = GetPluralRule(provider);

            if (pluralRule == null)
            {
                // Not a supported language.
                return;
            }

            var pluralCount = pluralWords.Count;
            var pluralIndex = pluralRule(value, pluralCount);

            if (pluralIndex < 0 || pluralWords.Count <= pluralIndex)
            {
                // The plural rule should always return a value in-range!
                throw new FormatException(format, "Invalid number of plural parameters", pluralWords.Last().endIndex);
            }

            // Output the selected word (allowing for nested formats):
            var pluralForm = pluralWords[pluralIndex];

            formatDetails.Formatter.Format(output, pluralForm, current, formatDetails);
            handled = true;
        }
コード例 #32
0
        private void Format(FormatDetails formatDetails, Format format, object current)
        {
            var formattingInfo = new FormattingInfo(formatDetails, format, current);

            Format(formattingInfo);
        }
コード例 #33
0
        /// <summary>
        /// If the source value is an array (or supports ICollection),
        /// then each item will be custom formatted.
        ///
        ///
        /// Syntax:
        /// #1: "format|spacer"
        /// #2: "format|spacer|last spacer"
        /// #3: "format|spacer|last spacer|two spacer"
        ///
        /// The format will be used for each item in the collection, the spacer will be between all items, and the last spacer will replace the spacer for the last item only.
        ///
        /// Example:
        /// CustomFormat("{Dates:D|; |; and }", {#1/1/2000#, #12/31/2999#, #9/9/9999#}) = "January 1, 2000; December 31, 2999; and September 9, 9999"
        /// In this example, format = "D", spacer = "; ", and last spacer = "; and "
        ///
        ///
        ///
        /// Advanced:
        /// Composite Formatting is allowed in the format by using nested braces.
        /// If a nested item is detected, Composite formatting will be used.
        ///
        /// Example:
        /// CustomFormat("{Sizes:{Width}x{Height}|, }", {new Size(4,3), new Size(16,9)}) = "4x3, 16x9"
        /// In this example, format = "{Width}x{Height}".  Notice the nested braces.
        ///
        /// </summary>
        public void EvaluateFormat(object current, Format format, ref bool handled, IOutput output, FormatDetails formatDetails)
        {
            // This method needs the Highest priority so that it comes before the PluralLocalizationExtension and ConditionalExtension

            // This extension requires at least IEnumerable
            var enumerable = current as IEnumerable;

            if (enumerable == null)
            {
                return;
            }
            // Ignore Strings, because they're IEnumerable.
            // This issue might actually need a solution
            // for other objects that are IEnumerable.
            if (current is string)
            {
                return;
            }
            // If the object is IFormattable, ignore it
            if (current is IFormattable)
            {
                return;
            }

            // This extension requires a | to specify the spacer:
            if (format == null)
            {
                return;
            }
            var parameters = format.Split("|", 4);

            if (parameters.Count < 2)
            {
                return;
            }

            // Grab all formatting options:
            // They must be in one of these formats:
            // itemFormat|spacer
            // itemFormat|spacer|lastSpacer
            // itemFormat|spacer|lastSpacer|twoSpacer
            var itemFormat = parameters[0];
            var spacer     = (parameters.Count >= 2) ? parameters[1].Text : "";
            var lastSpacer = (parameters.Count >= 3) ? parameters[2].Text : spacer;
            var twoSpacer  = (parameters.Count >= 4) ? parameters[3].Text : lastSpacer;

            if (!itemFormat.HasNested)
            {
                // The format is not nested,
                // so we will treat it as an itemFormat:
                var newItemFormat = new Format(itemFormat.baseString);
                newItemFormat.startIndex = itemFormat.startIndex;
                newItemFormat.endIndex   = itemFormat.endIndex;
                newItemFormat.HasNested  = true;
                var newPlaceholder = new Placeholder(newItemFormat, itemFormat.startIndex, formatDetails.Placeholder.NestedDepth);
                newPlaceholder.Format   = itemFormat;
                newPlaceholder.endIndex = itemFormat.endIndex;
                newItemFormat.Items.Add(newPlaceholder);
                itemFormat = newItemFormat;
            }

            // Let's buffer all items from the enumerable (to ensure the Count without double-enumeration):
            ICollection items = current as ICollection;

            if (items == null)
            {
                var allItems = new List <object>();
                foreach (var item in enumerable)
                {
                    allItems.Add(item);
                }
                items = allItems;
            }

            int oldCollectionIndex = CollectionIndex; // In case we have nested arrays, we might need to restore the CollectionIndex

            CollectionIndex = -1;
            foreach (object item in items)
            {
                CollectionIndex += 1; // Keep track of the index

                // Determine which spacer to write:
                if (spacer == null || CollectionIndex == 0)
                {
                    // Don't write the spacer.
                }
                else if (CollectionIndex < items.Count - 1)
                {
                    output.Write(spacer, formatDetails);
                }
                else if (CollectionIndex == 1)
                {
                    output.Write(twoSpacer, formatDetails);
                }
                else
                {
                    output.Write(lastSpacer, formatDetails);
                }

                // Output the nested format for this item:
                formatDetails.Formatter.Format(output, itemFormat, item, formatDetails);
            }

            CollectionIndex = oldCollectionIndex; // Restore the CollectionIndex

            handled = true;
        }