public IParameters Read(TflTransform transform) {
            var parameters = new Parameters.Parameters(_defaultFactory);
            var fields = new Fields(_process.OutputFields(), _process.CalculatedFields.WithoutOutput());

            foreach (var p in transform.Parameters) {

                if (!string.IsNullOrEmpty(p.Field)) {
                    if (fields.FindByParamater(p).Any()) {
                        var field = fields.FindByParamater(p).Last();
                        var name = string.IsNullOrEmpty(p.Name) ? field.Alias : p.Name;
                        parameters.Add(field.Alias, name, null, field.Type);
                    } else {
                        _process.Logger.Warn("A {0} transform references {1}, but I can't find the definition for {1}.\r\nYou may need to define the entity attribute in the parameter element.\r\nOr, set the output attribute to true in the field element. Process transforms rely on fields being output.\r\nOne other possibility is that the participates in a relationship with another field with the same name and Transformalize doesn't know which one you want.  If that's the case, you have to alias one of them.", transform.Method, p.Field);
                        var name = p.Name.Equals(string.Empty) ? p.Field : p.Name;
                        parameters.Add(p.Field, name, p.HasValue() ? p.Value : null, p.Type);
                    }
                } else {
                    var parameter = new Parameter(p.Name, p.Value) {
                        SimpleType = Common.ToSimpleType(p.Type),
                        ValueReferencesField = p.HasValue() && fields.Find(p.Value).Any()
                    };
                    parameters.Add(p.Name, parameter);
                }
            }

            return parameters;
        }
        public IParameters Read(TflTransform transform) {
            var parameters = new Parameters.Parameters(new DefaultFactory(_logger));

            foreach (var p in transform.Parameters) {
                if (string.IsNullOrEmpty(p.Field) && (string.IsNullOrEmpty(p.Name) || string.IsNullOrEmpty(p.Value))) {
                    throw new TransformalizeException(_logger, "The entity {0} has a {1} transform parameter without a field attribute, or name and value attributes.  Entity parameters require one or the other.", _entity.Alias, transform.Method);
                }

                var fields = new Fields(_entity.Fields, _entity.CalculatedFields);
                if (!string.IsNullOrEmpty(p.Field)) {
                    if (fields.FindByParamater(p).Any()) {
                        var field = fields.FindByParamater(p).Last();
                        var name = string.IsNullOrEmpty(p.Name) ? field.Alias : p.Name;
                        parameters.Add(field.Alias, name, null, field.Type);
                    } else {
                        if (!p.Field.StartsWith("Tfl")) {
                            _logger.EntityWarn(_entity.Name, "The entity {0} has a {1} transform parameter that references field {2}.  This field hasn't been defined yet in {0}.", _entity.Alias, transform.Method, p.Field);
                        }
                        var name = string.IsNullOrEmpty(p.Name) ? p.Field : p.Name;
                        parameters.Add(p.Field, name, p.HasValue() ? p.Value : null, "System.String");
                    }
                } else {
                    var parameter = new Parameter(p.Name, p.Value) {
                        SimpleType = Common.ToSimpleType(p.Type),
                        ValueReferencesField = p.HasValue() && fields.Find(p.Value).Any()
                    };
                    parameters.Add(p.Name, parameter);
                }
            }

            return parameters;
        }
        public IParameters Read(TflTransform transform) {
            var parameters = new Parameters.Parameters(_defaultFactory);

            foreach (var p in transform.Parameters) {
                if (string.IsNullOrEmpty(p.Name)) {
                    return new Parameters.Parameters(_defaultFactory);
                }

                var value = p.HasValue() ? p.Value : null;
                var alias = p.HasValue() ? p.Name : p.Field;
                parameters.Add(alias, p.Name, value, p.Type);
            }

            return parameters;
        }
        private TflTransform Join(string arg, TflField field, TflTransform lastTransform) {

            if (string.IsNullOrEmpty(arg)) {
                return new TflTransform {
                    Method = "concat",
                    IsShortHand = true
                }.WithDefaults();
            }

            var split = SplitComma(arg);

            if (Guard.Against(_problems, split.Length != 1,
                "The join method requires one parameter; the separator. To get fields for join, use the copy() method.")) {
                return _guard;
            }

            return new TflTransform {
                Method = "join",
                Separator = split[0],
                IsShortHand = true
            }.WithDefaults();

        }
        private TflTransform Script(string method, string arg, TflField field, TflTransform lastTransform) {
            var split = SplitComma(arg);

            if (Guard.Against(_problems, split.Length < 1 || split.Length > 2,
                "The {0} method takes between 1 and 2 parameters: a script name and/or a snippet (e.g. a function call).",
                method)) {
                return _guard;
            };

            var element = new TflTransform {
                Method = method,
                IsShortHand = true
            }.WithDefaults();

            foreach (var parameter in split) {
                if (parameter.IndexOfAny(_codeCharaters) > -1) {
                    element.Script = parameter;
                } else {
                    element.Scripts = new List<TflNameReference> { new TflNameReference { Name = parameter } };
                }
            }

            return element;
        }
        private TflTransform Right(string arg, TflField field, TflTransform lastTransform) {
            int length;

            if (Guard.Against(_problems, !int.TryParse(arg, out length),
                "The right method requires a single integer representing the length, or how many right-most characters you want. You passed in '{0}'.",
                arg)) {
                return _guard;
            }

            return new TflTransform {
                Method = "right",
                Length = length,
                IsShortHand = true
            }.WithDefaults();
        }
 private TflTransform ToInt(string arg, TflField field, TflTransform lastTransform) {
     var element = Convert(arg, field, lastTransform);
     element.Method = "convert";
     element.To = "int";
     return element;
 }
        private TflTransform Convert(string arg, TflField field, TflTransform lastTransform) {
            var split = SplitComma(arg);

            var element = new TflTransform {
                Method = "convert",
                IsShortHand = true
            }.WithDefaults();

            if (split.Length == 0)
                return element;

            foreach (var p in split) {
                if (System.Text.Encoding.GetEncodings().Any(e => e.Name.Equals(p, StringComparison.OrdinalIgnoreCase))) {
                    element.Encoding = p;
                } else if (Common.TypeMap.ContainsKey(Common.ToSimpleType(p))) {
                    element.To = Common.ToSimpleType(p);
                } else {
                    element.Format = p;
                }
            }

            return element;
        }
        private TflTransform RegexReplace(string arg, TflField field, TflTransform lastTransform) {

            if (Guard.Against(_problems, arg.Equals(string.Empty),
                "The regexreplace requires two parameters: a regular expression pattern, and replacement text.  You didn't pass in any parameters.")) {
                return _guard;
            };

            var split = SplitComma(arg);

            if (Guard.Against(_problems, split.Length < 2,
                "The regexreplace method requires at least two parameters: the pattern, and the replacement text.  A third parameter, count (how many to replace) is optional. The argument '{0}' has {1} parameter{2}.",
                arg, split.Length, split.Length.Plural())) {
                return _guard;
            }

            var element = new TflTransform {
                Method = "regexreplace",
                Pattern = split[0],
                Replacement = split[1],
                IsShortHand = true
            }.WithDefaults();

            if (split.Length <= 2)
                return element;

            int count;
            if (Guard.Against(_problems, !int.TryParse(split[2], out count),
                "The regexreplace's third parameter; count, must be an integer. The argument '{0}' contains '{1}'.", arg,
                split[2])) {
                return _guard;
            }
            element.Count = count;

            return element;
        }
 private TflTransform Concat(string arg, TflField field, TflTransform lastTransform) {
     return Parameterless("concat", "concatenated", arg, field, lastTransform);
 }
        private TflTransform Map(string arg, TflField field, TflTransform lastTransform) {

            var split = SplitComma(arg);

            if (Guard.Against(_problems, split.Length == 0,
                "The map method requires a map name (e.g. 'map'), or a set of parameters that represent an inline map (e.g. 'a=1,b=2,c=3').")) {
                return _guard;
            }

            var element = new TflTransform {
                Method = "map",
                IsShortHand = true
            }.WithDefaults();

            if (_maps.ContainsKey(split[0])) {
                element.Map = split[0];
                Guard.Against(_problems, split.Length > 1, "If you reference a map name in a map method, that's the only parameter you can have.  It will map what is in the current field.");
                return element;
            }

            element.Map = string.Join(",", split);

            return element;
        }
        private TflTransform Web(string arg, TflField field, TflTransform lastTransform) {
            var split = SplitComma(arg);
            if (Guard.Against(_problems, split.Length > 2,
                "The web method takes two optional parameters: a parameter referencing a field, and an integer representing sleep ms in between web requests.  You have {0} parameter{1} in '{2}'.",
                split.Length, split.Length.Plural(), arg)) {
                return _guard;
            }

            var element = new TflTransform {
                Method = "web",
                IsShortHand = true
            }.WithDefaults();

            foreach (var p in split) {
                int sleep;
                if (int.TryParse(p, out sleep)) {
                    element.Sleep = sleep;
                } else {
                    element.Parameter = p;
                }
            }
            return element;
        }
        private TflTransform Pad(string method, string arg, TflField field, TflTransform lastTransform) {

            if (Guard.Against(_problems, arg.Equals(string.Empty),
                "The {0} method requires two pararmeters: the total width, and the padding character(s).", method)) {
                return _guard;
            }

            var split = SplitComma(arg);

            if (Guard.Against(_problems, split.Length < 2,
                "The {0} method requires two pararmeters: the total width, and the padding character(s).  You've provided {1} parameter{2}.",
                method, split.Length, split.Length.Plural())) {
                return _guard;
            }

            var element = new TflTransform {
                Method = method,
                IsShortHand = true
            }.WithDefaults();

            int totalWidth;
            if (int.TryParse(split[0], out totalWidth)) {
                element.TotalWidth = totalWidth;
            } else {
                _problems.Add(string.Format("The {0} method requires the first parameter to be total width; an integer. {1} is not an integer", method, split[0]));
                return _guard;
            }

            element.PaddingChar = split[1][0];

            if (Guard.Against(_problems, element.PaddingChar == default(char),
                "The {0} second parameter, the padding character, must be a character.  You can't pad something with nothing.", method)) {
                return _guard;
            }

            if (split.Length > 2) {
                element.Parameter = split[2];
            }
            return element;
        }
 private TflTransform PadRight(string arg, TflField field, TflTransform lastTransform) {
     return Pad("padright", arg, field, lastTransform);
 }
 private TflTransform PadLeft(string arg, TflField field, TflTransform lastTransform) {
     return Pad("padleft", arg, field, lastTransform);
 }
 private TflTransform JavaScript(string arg, TflField field, TflTransform lastTransform) {
     return Script("javascript", arg, field, lastTransform);
 }
 private TflTransform CSharp(string arg, TflField field, TflTransform lastTransform) {
     return Script("csharp", arg, field, lastTransform);
 }
        private TflTransform Add(string arg, TflField field, TflTransform lastTransform) {
            var split = SplitComma(arg);

            if (Guard.Against(_problems, split.Length == 0,
                "The add method requires a * parameter, or a comma delimited list of parameters that reference numeric fields.")) {
                return _guard;
            }

            var element = new TflTransform {
                Method = "add",
                IsShortHand = true
            }.WithDefaults();

            if (split.Length == 1) {
                element.Parameter = split[0];
            } else {
                for (var i = 0; i < split.Length; i++) {
                    var p = split[i];
                    element.Parameters.Add(
                        p.IsNumeric()
                            ? new TflParameter {
                                Name = p,
                                Value = p
                            }.WithDefaults()
                            : new TflParameter {
                                Field = p
                            }.WithDefaults());
                }
            }
            return element;
        }
        private TflTransform Parameters(string method, string arg, int skip, TflField f, TflTransform lastTransform) {
            var split = SplitComma(arg, skip);

            if (Guard.Against(_problems, split.Length == 0, "The {0} method requires parameters.", method)) {
                return _guard;
            }

            var element = new TflTransform {
                Method = method,
                IsShortHand = true
            }.WithDefaults();

            if (split.Length == 1) {
                element.Parameter = split[0];
            } else {
                for (var i = 0; i < split.Length; i++) {
                    var p = split[i];
                    element.Parameters.Add(new TflParameter { Field = p }.WithDefaults());
                }
            }

            // handle single parameter that is named parameter
            if (element.Parameter.Contains(":")) {
                var pair = Common.Split(element.Parameter, ":");
                element.Parameters.Insert(0, new TflParameter {
                    Field = string.Empty,
                    Name = pair[0],
                    Value = pair[1]
                }.WithDefaults());
                element.Parameter = string.Empty;
            }

            // handle regular parameters
            foreach (var p in element.Parameters) {
                if (!p.Field.Contains(":"))
                    continue;
                var pair = Common.Split(p.Field, ":");
                p.Field = string.Empty;
                p.Name = pair[0];
                p.Value = pair[1];
            }

            return element;
        }
        private TflTransform TrimStartAppend(string arg, TflField field, TflTransform lastTransform) {
            var split = SplitComma(arg);

            if (Guard.Against(_problems, split.Length < 1,
                "The trimstartappend method requires at least one parameter indicating the trim characters.")) {
                return _guard;
            }

            var element = new TflTransform {
                Method = "trimstartappend",
                TrimChars = split[0],
                IsShortHand = true
            }.WithDefaults();

            if (split.Length > 1) {
                element.Separator = split[1];
            }

            if (split.Length > 2) {
                element.Parameter = split[2];
            }

            return element;
        }
        public TflTransform Interpret(string expression, TflField field, TflTransform lastTransform = null) {

            string method;
            var arg = string.Empty;

            if (Guard.Against(_problems, expression == null, "You may not pass a null expression.")) {
                return _guard;
            }

            // ReSharper disable once PossibleNullReferenceException
            if (expression.Contains("(")) {
                var index = expression.IndexOf('(');
                method = expression.Left(index).ToLower();
                arg = expression.Remove(0, index + 1).TrimEnd(new[] { ')' });
            } else {
                method = expression;
            }

            if (Guard.Against(_problems, !_methods.ContainsKey(method),
                "Sorry. Your expression '{0}' references an undefined method: '{1}'.", expression, method)) {
                return _guard;
            }

            return _functions[_methods[method]](arg, field, lastTransform);
        }
        private TflTransform Substring(string arg, TflField field, TflTransform lastTransform) {
            var split = SplitComma(arg);

            if (Guard.Against(_problems, split.Length < 1, "The substring method requires a start index.")) {
                return _guard;
            }

            int length;
            int startIndex;
            if (int.TryParse(split[0], out startIndex)) {
                return new TflTransform {
                    Method = "substring",
                    StartIndex = startIndex,
                    Length = split.Length > 1 && int.TryParse(split[1], out length) ? length : 0,
                    IsShortHand = true
                }.WithDefaults();
            }

            _problems.Add($"The substring method requires two integers indicating start index and length. '{arg}' doesn't represent two integers.");
            return _guard;
        }
        private TflTransform Replace(string arg, TflField field, TflTransform lastTransform) {
            var split = SplitComma(arg);

            if (Guard.Against(_problems, split.Length < 2,
                "The replace method requires two parameters: an old value, and a new value. Your arguments '{0}' resolve {1} parameter{2}.",
                arg, split.Length, split.Length.Plural())) {
                return _guard;
            }

            var oldValue = split[0];
            var newValue = split[1];

            return new TflTransform {
                Method = "replace",
                OldValue = oldValue,
                NewValue = newValue,
                IsShortHand = true
            }.WithDefaults();
        }
        private TflTransform Remove(string arg, TflField field, TflTransform lastTransform) {
            var split = SplitComma(arg);

            if (Guard.Against(_problems, split.Length < 2, "The remove method requires start index and length. You have {0} parameter{1}.", split.Length, split.Length.Plural())) {
                return _guard;
            }

            int startIndex;
            int length;
            if (int.TryParse(split[0], out startIndex) && int.TryParse(split[1], out length)) {
                return new TflTransform {
                    Method = "remove",
                    StartIndex = startIndex,
                    Length = length,
                    IsShortHand = true
                }.WithDefaults();
            }

            _problems.Add(
                $"The remove method requires two integer parameters indicating start index and length. '{arg}' doesn't represent two integers.");
            return _guard;
        }
 private TflTransform ToString(string arg, TflField field, TflTransform lastTransform) {
     var element = Convert(arg, field, lastTransform);
     element.Method = "tostring";
     element.To = "string";
     return element;
 }
        private TflTransform Slug(string arg, TflField field, TflTransform lastTransform) {
            var element = new TflTransform {
                Method = "slug",
                IsShortHand = true
            }.WithDefaults();

            var split = SplitComma(arg);

            if (Guard.Against(_problems, split.Length > 1,
                "The slug method takes 1 argument; an integer representing the maximum length of the slug. You passed in '{0}' arguments.",
                split.Length)) {
                return _guard;
            }

            int length;
            if (split.Length > 0 && int.TryParse(split[0], out length)) {
                element.Length = length;
            } else {
                element.Length = 128;
            }

            return element;
        }
        private TflTransform If(string arg, TflField field, TflTransform lastTransform) {
            var linked = new LinkedList<string>(SplitComma(arg));

            if (Guard.Against(_problems, linked.Count < 2,
                "The if method requires at least 2 arguments. Your argument '{0}' has {1}.", arg, linked.Count)) {
                return _guard;
            }

            // left is required first, assign and remove
            var element = new TflTransform {
                Method = "if",
                Left = linked.First.Value,
                IsShortHand = true
            }.WithDefaults();
            linked.RemoveFirst();

            // operator is second, but optional, assign and remove if present
            ComparisonOperator op;
            if (Enum.TryParse(linked.First.Value, true, out op)) {
                element.Operator = op.ToString();
                linked.RemoveFirst();
            }

            // right, then, and else in that order
            var split = linked.ToList();
            for (var i = 0; i < split.Count; i++) {
                switch (i) {
                    case 0:
                        element.Right = split[i];
                        break;
                    case 1:
                        element.Then = split[i];
                        break;
                    case 2:
                        element.Else = split[i];
                        break;
                }
            }
            return element;
        }
        private TflTransform Insert(string arg, TflField field, TflTransform lastTransform) {
            var split = SplitComma(arg);

            if (Guard.Against(_problems, split.Length != 2,
                "The insert method requires two parameters; the start index, and the value (or field reference) you'd like to insert.  '{0}' has {1} parameter{2}.",
                arg, split.Length, split.Length.Plural())) {
                return _guard;
            }


            var element = new TflTransform {
                Method = "insert",
                IsShortHand = true
            }.WithDefaults();

            int startIndex;
            if (int.TryParse(split[0], out startIndex)) {
                element.StartIndex = startIndex;
            } else {
                _problems.Add($"The insert method's first parameter must be an integer.  {split[0]} is not an integer.");
                return _guard;
            }

            element.Parameter = split[1];
            return element;
        }
        private TflTransform InsertInterval(string arg, TflField field, TflTransform lastTransform) {
            //interval, value
            var split = SplitComma(arg);

            if (Guard.Against(_problems, split.Length != 2, "The insertinterval method requires two parameters: the interval (e.g. every certain number of characters), and the value to insert. '{0}' has {1} parameter{2}.", arg, split.Length, split.Length.Plural())) {
                return _guard;
            }

            var element = new TflTransform {
                Method = "insertinterval",
                IsShortHand = true
            }.WithDefaults();

            int interval;
            if (int.TryParse(split[0], out interval)) {
                element.Interval = interval;
            } else {
                _problems.Add(string.Format("The insertinterval method's first parameter must be an integer.  {0} is not an integer.", split[0]));
                return _guard;
            }

            element.Value = split[1];
            return element;
        }
        private TflTransform Velocity(string arg, TflField field, TflTransform lastTransform) {
            var split = SplitComma(arg);

            if (Guard.Against(_problems, split.Length < 1 || split.Length > 2,
                "The velocity method takes between 1 and 2 parameters: the template name, and a snippet (of template). Use the copy method to inject other data into the template.")) {
                return _guard;
            }

            var element = new TflTransform {
                Method = "velocity",
                IsShortHand = true
            }.WithDefaults();

            foreach (var parameter in split) {
                if (parameter.IndexOfAny(_codeCharaters) > -1) {
                    element.Template = parameter;
                } else {
                    element.Templates = new List<TflNameReference> { new TflNameReference { Name = parameter } };
                }
            }

            return element;
        }