private void WriteAttribute(ArffAttribute attribute, int indent) { string type = attribute.Type.ToString(); if (indent != 0) { streamWriter.Write(new string(' ', indent)); } streamWriter.WriteLine("@attribute {0} {1}", QuoteAndEscape(attribute.Name), type); if (attribute.Type is ArffRelationalAttribute relationalAttribute) { foreach (ArffAttribute childAttribute in relationalAttribute.ChildAttributes) { WriteAttribute(childAttribute, indent + 2); } if (indent != 0) { streamWriter.Write(new string(' ', indent)); } streamWriter.WriteLine("@end {0}", QuoteAndEscape(attribute.Name)); } }
/// <summary> /// Determines whether this object is equal to another object (an <see cref="ArffAttribute"/> with the same name and type). /// </summary> /// <param name="obj">The object to compare with the current object.</param> /// <returns><c>true</c> if the specified object is equal to the current object; otherwise, <c>false</c>.</returns> public override bool Equals(object obj) { ArffAttribute other = obj as ArffAttribute; if (other == null) { return(false); } return(other.Name == Name && other.Type.Equals(Type)); }
/// <summary> /// Reads relation name and attribute declarations as an <see cref="ArffHeader"/> instance. /// </summary> /// <returns><see cref="ArffHeader"/> instance with read data.</returns> /// <exception cref="ObjectDisposedException"/> /// <exception cref="InvalidOperationException"/> /// <exception cref="InvalidDataException"/> public ArffHeader ReadHeader() { if (disposed) { throw new ObjectDisposedException(GetType().FullName); } if (arffHeader != null) { throw new InvalidOperationException("The header has already been read by a previous call of ReadHeader."); } List <ArffAttribute> attributes = new List <ArffAttribute>(); ReadToken(expectedToken: "@relation", ignoreCase: true, skipEndOfLine: true, endOfLine: false, quoting: false); string relationName = ReadToken(endOfLine: false); ReadToken(endOfLine: true); while (true) { string token = ReadToken(skipEndOfLine: true, endOfLine: false, quoting: false); if (string.Equals(token, "@attribute", StringComparison.OrdinalIgnoreCase)) { ArffAttribute attribute = ReadAttribute(); attributes.Add(attribute); } else if (string.Equals(token, "@data", StringComparison.OrdinalIgnoreCase)) { ReadToken(endOfLine: true); break; } else { throw new InvalidDataException($"Unexpected token \"{token}\". Expected \"@attribute\" or \"@data\"."); } } if (attributes.Count == 0) { throw new InvalidDataException("Expected at least one \"@attribute\"."); } arffHeader = new ArffHeader(relationName, attributes); return(arffHeader); }
/// <summary> /// Writes an attribute declaration (@attribute <...>) for the specified <see cref="ArffAttribute"/>. /// Must be called after the relation name has been written and before any data instances are written. /// </summary> /// <param name="attribute">An <see cref="ArffAttribute"/> object representing the attribute.</param> /// <exception cref="ObjectDisposedException"/> /// <exception cref="ArgumentNullException"/> /// <exception cref="ArgumentException"/> /// <exception cref="InvalidOperationException"/> /// <exception cref="IOException"/> public void WriteAttribute(ArffAttribute attribute) { if (disposed) { throw new ObjectDisposedException(GetType().FullName); } if (step != 1 && step != 2) { throw new InvalidOperationException("All attributes must be written after the relation name and before any instances."); } if (attribute == null) { throw new ArgumentNullException(nameof(attribute)); } WriteAttribute(attribute, 0); writtenAttributes.Add(attribute); step = 2; }
private void WriteValue(object value, ArffAttribute attribute, TextWriter textWriter) { if (value == null) { textWriter.Write("?"); } else if (value is double doubleValue) { textWriter.Write(doubleValue.ToString(CultureInfo.InvariantCulture)); } else if (value is string stringValue) { textWriter.Write(QuoteAndEscape(stringValue)); } else if (value is int || value is Enum) { // the cast fails if value is an enum with an underlying type other than int // but checking the type via Enum.GetUnderlyingType(value.GetType()) == typeof(int) // to throw a more helpful exception is probably not worth it int nominalValue = (int)value; ReadOnlyCollection <string> values = (attribute.Type as ArffNominalAttribute)?.Values; if (values == null || nominalValue < 0 || nominalValue >= values.Count) { throw new ArgumentException("Instance is incompatible with types of written attributes.", "instance"); } textWriter.Write(QuoteAndEscape(values[nominalValue])); } else if (value is DateTime dateTimeValue) { string dateFormat = (attribute.Type as ArffDateAttribute)?.DateFormat; if (dateFormat == null) { throw new ArgumentException("Instance is incompatible with types of written attributes.", "instance"); } textWriter.Write(QuoteAndEscape(dateTimeValue.ToString(dateFormat, CultureInfo.InvariantCulture))); } else if (value is object[][] relationalValue) { ReadOnlyCollection <ArffAttribute> relationalAttributes = (attribute.Type as ArffRelationalAttribute)?.ChildAttributes; if (relationalAttributes == null) { throw new ArgumentException("Instance is incompatible with types of written attributes.", "instance"); } using (StringWriter stringWriter = new StringWriter()) { for (int j = 0; j < relationalValue.Length; j++) { if (relationalValue[j].Length != relationalAttributes.Count) { throw new ArgumentException("Instance is incompatible with types of written attributes.", "instance"); } // sparse format does not seem to be supported in relational values // instance weights seem to be supported in the format but they cannot be represented with an object[] alone, so we don't support them for now WriteInstanceData(relationalValue[j], false, relationalAttributes, stringWriter); if (j != relationalValue.Length - 1) { stringWriter.WriteLine(); } } textWriter.Write(QuoteAndEscape(stringWriter.GetStringBuilder().ToString())); } } else { throw new ArgumentException("Unsupported data type in instance.", "instance"); } }
private ArffAttribute ReadAttribute() { string attributeName = ReadToken(endOfLine: false); string typeString = ReadToken(endOfLine: false, quoting: false); ArffAttributeType attributeType; if (string.Equals(typeString, "numeric", StringComparison.OrdinalIgnoreCase) || string.Equals(typeString, "integer", StringComparison.OrdinalIgnoreCase) || string.Equals(typeString, "real", StringComparison.OrdinalIgnoreCase)) { attributeType = ArffAttributeType.Numeric; ReadToken(endOfLine: true); } else if (string.Equals(typeString, "string", StringComparison.OrdinalIgnoreCase)) { attributeType = ArffAttributeType.String; ReadToken(endOfLine: true); } else if (string.Equals(typeString, "date", StringComparison.OrdinalIgnoreCase)) { string dateFormat = ReadToken(); if (dateFormat == null) { attributeType = ArffAttributeType.Date(); } else { attributeType = ArffAttributeType.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 = ArffAttributeType.Nominal(nominalValues); ReadToken(endOfLine: true); } else if (string.Equals(typeString, "relational", StringComparison.OrdinalIgnoreCase)) { ReadToken(endOfLine: true); List <ArffAttribute> childAttributes = new List <ArffAttribute>(); while (true) { string token = ReadToken(skipEndOfLine: true, endOfLine: false, quoting: false); if (string.Equals(token, "@attribute", StringComparison.OrdinalIgnoreCase)) { ArffAttribute 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 = ArffAttributeType.Relational(childAttributes); } else { throw new InvalidDataException($"Unexpected token \"{typeString}\". Expected attribute type."); } return(new ArffAttribute(attributeName, attributeType)); }