/// <summary> /// Formats a strongly-typed field value in a format suitable for writing to the 2DA file, /// according to the schema for the corresponding column. /// </summary> /// <param name="value">The value to format.</param> /// <param name="schemaColumn">The schema of the column from which the value was taken.</param> /// <returns></returns> private static string FormatFieldValue(object value, TwoDASchema.Column schemaColumn) { if (value == null || value is DBNull) { return(BlankEntry); } if (value is int && schemaColumn != null && schemaColumn.DataType == TwoDASchema.DataType.HexInteger) { return("0x" + ((int)value).ToString("X" + schemaColumn.Digits)); } string result = Convert.ToString(value, CultureInfo.InvariantCulture); result = result.Replace('"', '\''); if (result.Contains(' ')) { result = '"' + result + '"'; } return(result); }
/// <summary> /// Loads 2DA data from the specified read. /// </summary> /// <param name="reader">The reader to read the data from.</param> /// <param name="lineNumberCallback"> /// The line number callback. For each line read from the reader, the callback will /// be invoked once, with the number of the line read passed as an argument. /// </param> /// <exception cref="InvalidDataException"> /// 2DA data in the reader is not in a correct format, is corrupted, or does not match /// the schema. /// </exception> public void Load(TextReader reader, Action <int> lineNumberCallback) { int lineNumber = 0; if (reader == null) { throw new ArgumentNullException("reader"); } try { isLoading = true; Data.Clear(); Data.Columns.Clear(); string signatureLine = reader.ReadLine().TrimEnd(); ++lineNumber; if (signatureLine != validSignature) { throw new InvalidDataException(string.Format( "Error at line {0}: '{1}' is not a valid 2DA signature", lineNumber, signatureLine)); } if (lineNumberCallback != null) { lineNumberCallback(lineNumber); } string defaultValueLine = reader.ReadLine().TrimEnd(); ++lineNumber; if (defaultValueLine == null) { throw new InvalidDataException(string.Format( "Error at line {0}: default value line is missing", lineNumber)); } if (defaultValueLine.Length != 0) { if (!defaultValueLine.StartsWith(defaultValueMarker)) { throw new InvalidDataException(string.Format( "Error at line {0}: default value line must either be blank, or begin with '{1}'", lineNumber, defaultValueMarker)); } DefaultString = defaultValueLine.Remove(0, defaultValueMarker.Length).TrimStart(); } if (lineNumberCallback != null) { lineNumberCallback(lineNumber); } string columnNamesLine = null; do { columnNamesLine = reader.ReadLine().TrimEnd(); ++lineNumber; } while (columnNamesLine == ""); if (columnNamesLine == null) { throw new InvalidDataException(string.Format( "Error at line {0}: column names line is missing", lineNumber)); } var rowNumberColumn = new DataColumn("#", typeof(int)); rowNumberColumn.AllowDBNull = true; Data.Columns.Add(rowNumberColumn); var columnNames = GetFieldValues(columnNamesLine); foreach (string columnName in columnNames) { var column = new DataColumn(columnName, typeof(string)); TwoDASchema.Column schemaColumn = null; if (Schema != null && Schema.Columns != null) { schemaColumn = Schema.Columns.FirstOrDefault(c => c.Name == columnName); } if (schemaColumn == null) { column.DataType = typeof(string); column.MaxLength = 267; column.AllowDBNull = true; column.DefaultValue = DBNull.Value; } else { column.AllowDBNull = schemaColumn.AllowBlanks; switch (schemaColumn.DataType) { case TwoDASchema.DataType.String: column.DataType = typeof(string); column.MaxLength = 267; break; case TwoDASchema.DataType.Float: column.DataType = typeof(float); break; case TwoDASchema.DataType.Integer: case TwoDASchema.DataType.HexInteger: case TwoDASchema.DataType.StrRef: column.DataType = typeof(int); break; } } Data.Columns.Add(column); } FillSchemaColumns(); if (lineNumberCallback != null) { lineNumberCallback(lineNumber); } string line; while ((line = reader.ReadLine()) != null) { ++lineNumber; line = line.TrimEnd(); if (line.Length == 0) { continue; } DataRow row = Data.NewRow(); IEnumerable <string> fieldValues = GetFieldValues(line); int columnIndex = 0; foreach (string fieldValue in fieldValues) { if (columnIndex >= Data.Columns.Count) { throw new InvalidDataException(string.Format( "Error at line {0}: too many field values", lineNumber)); } object actualValue = fieldValue == BlankEntry ? (object)DBNull.Value : fieldValue; var schemaColumn = schemaColumns[columnIndex]; if (schemaColumn != null && actualValue != DBNull.Value) { switch (schemaColumn.DataType) { case TwoDASchema.DataType.Integer: case TwoDASchema.DataType.StrRef: { actualValue = int.Parse(fieldValue, CultureInfo.InvariantCulture); } break; case TwoDASchema.DataType.HexInteger: { if (fieldValue.StartsWith("0x", StringComparison.OrdinalIgnoreCase)) { actualValue = int.Parse(fieldValue.Substring(2), NumberStyles.HexNumber, CultureInfo.InvariantCulture); } else { actualValue = int.Parse(fieldValue, CultureInfo.InvariantCulture); } } break; case TwoDASchema.DataType.Float: { actualValue = float.Parse(fieldValue, CultureInfo.InvariantCulture); } break; } } try { row[columnIndex] = actualValue; } catch (ArgumentException ex) { throw new InvalidDataException(string.Format( "Error at line {0}: {1}", lineNumber, ex.Message), ex); } ++columnIndex; } Data.Rows.Add(row); if (lineNumberCallback != null) { lineNumberCallback(lineNumber); } } Data.AcceptChanges(); IsModified = false; } catch (DataException ex) { throw new InvalidDataException(string.Format( "Error at line {0}: {1}", lineNumber, ex.Message), ex); } finally { isLoading = false; } undoStack.Clear(); redoStack.Clear(); OnPropertyChanged(new PropertyChangedEventArgs("CanUndo")); OnPropertyChanged(new PropertyChangedEventArgs("CanRedo")); }