private Feature ReadAttribute()
        {
            string attributeName = ReadToken(endOfLine: false);
            string typeString    = ReadToken(endOfLine: false, quoting: false);

            FeatureType attributeType;

            if (string.Equals(typeString, "integer", StringComparison.OrdinalIgnoreCase))
            {
                attributeType = FeatureType.Integer;
                ReadToken(endOfLine: true);
            }
            else if (string.Equals(typeString, "numeric", StringComparison.OrdinalIgnoreCase) ||
                     string.Equals(typeString, "real", StringComparison.OrdinalIgnoreCase))
            {
                attributeType = FeatureType.Numeric;
                ReadToken(endOfLine: true);
            }
            else if (string.Equals(typeString, "string", StringComparison.OrdinalIgnoreCase))
            {
                attributeType = FeatureType.String;
                ReadToken(endOfLine: true);
            }
            else if (string.Equals(typeString, "date", StringComparison.OrdinalIgnoreCase))
            {
                string dateFormat = ReadToken();

                if (dateFormat == null)
                {
                    attributeType = FeatureType.Date();
                }
                else
                {
                    attributeType = FeatureType.Date(dateFormat);
                    ReadToken(endOfLine: true);
                }
            }
            else if (typeString == "{")
            {
                List <string> nominalValues = new List <string>();

                while (true)
                {
                    string value = ReadToken(out bool quoted, endOfLine: false);

                    if (!quoted && value == "}")
                    {
                        break;
                    }
                    else if (!quoted && value == ",")
                    {
                        continue;
                    }
                    else
                    {
                        nominalValues.Add(value);
                    }
                }

                attributeType = FeatureType.Nominal(nominalValues);
                ReadToken(endOfLine: true);
            }
            else if (string.Equals(typeString, "relational", StringComparison.OrdinalIgnoreCase))
            {
                ReadToken(endOfLine: true);

                List <Feature> childAttributes = new List <Feature>();

                while (true)
                {
                    string token = ReadToken(skipEndOfLine: true, endOfLine: false, quoting: false);

                    if (string.Equals(token, "@attribute", StringComparison.OrdinalIgnoreCase))
                    {
                        Feature attribute = ReadAttribute();

                        childAttributes.Add(attribute);
                    }
                    else if (string.Equals(token, "@end", StringComparison.OrdinalIgnoreCase))
                    {
                        ReadToken(expectedToken: attributeName, endOfLine: false);
                        ReadToken(endOfLine: true);
                        break;
                    }
                    else
                    {
                        throw new InvalidDataException($"Unexpected token \"{token}\". Expected \"@attribute\" or \"@end\".");
                    }
                }

                attributeType = FeatureType.Relational(childAttributes);
            }
            else
            {
                throw new InvalidDataException($"Unexpected token \"{typeString}\". Expected attribute type.");
            }

            return(new Feature(attributeName, attributeType));
        }