/// <summary> /// Attempts to merge all of the 2da's in the passed array list into 1 merge /// 2da, by combining all of the non-empty rows in each 2da. If 2 2da's have /// changes the same row then the merge will fail. /// </summary> /// <param name="baseline">The bioware baseline version of the 2da</param> /// <param name="list">The list of 2da's to merge</param> /// <returns>The merged 2da or null if the 2da's cannot be merged</returns> private _2DA Merge2das(_2DA baseline, ArrayList list) { // Create a flat list to have a strongly typed list of 2da's. _2DA[] merges = new _2DA[list.Count]; list.CopyTo(merges); // Figure out the maximum number of rows we have to deal with int rows = baseline.Rows; foreach (_2DA merge in merges) rows = System.Math.Max(rows, merge.Rows); // Create the output 2da. _2DA output = new _2DA(baseline.Schema); output.Pad(rows); // Loop through all rows attempting to merge each row into the // output 2da. for (int i = 0; i < rows; i++) { StringCollection mergedRow = null; _2DA useForOutput = null; foreach (_2DA merge in merges) { // Make an attempt to filter out junk rows with things // such as "reserved", "deleted", etc in their labels. // These often conflict but are really empty rows. if (IsJunkRow(merge, i)) continue; // If we have gone past the end of this 2da or the // row is an empty row then ignore it. if (i >= merge.Rows || merge.IsEmpty(i)) continue; // If this is a row from the bioware version of the 2da // and the data is the same as the bioware 2da then // ignore this row in the 2da. if (i < baseline.Rows && _2DA.CompareRow(baseline, i, merge, i, true)) continue; // If we get here we have a non-empty row that differs from // one of the bioware rows. Only 1 2da file per row can // get past this point for use to be able to do a successful // merge, if 2 2da's get here then 2 have changed the same // row and we cannot merge. // If we don't have any proposed row data yet then // save this 2da's row data. if (null == useForOutput) { useForOutput = merge; continue; } // If we get here we have 2 2da's that want to change the same row. // Our only hope for a successful merge is that the data in the // 2 2da's is identical. if (_2DA.CompareRow(useForOutput, i, merge, i, true)) continue; // We already have an output 2da, which means that 2 2da's have // changed the same row, attempt to glue all of the merge changes // together. If we cannot generate a merged row then return null. mergedRow = GenerateMergeRow(baseline, list, i); if (null == mergedRow) return null; // If we get here we have generated a merge row for all 2da's // so we don't need to look at the data in this row any further // break out of the loopo and use the mergedRow. break; } // If we have merge 2da to copy from then copy the // cell data. If we don't have a merge 2da but the row is // withing the baseline 2da then copy the baseline data. // Otherwise don't copy any data. if (null != mergedRow) output.CopyRow(mergedRow, i); else if (null != useForOutput) output.CopyRow(useForOutput, i, i); else if (i < baseline.Rows) output.CopyRow(baseline, i, i); } return output; }
/// <summary> /// Attempts to generate a merge row by taking all of the alterations made /// to the bioware row from the merge 2da's and incorporating them into 1 /// row. This will work unless 2 different 2da's change the same column /// in the row, which will make the merge fail. /// </summary> /// <param name="baseline">The bioware baseline 2da</param> /// <param name="list">The list of 2da's being merged</param> /// <param name="row">The row for which to generate a merge row</param> /// <returns>The merged row, or null if a merge row could not be /// generated.</returns> private StringCollection GenerateMergeRow(_2DA baseline, ArrayList list, int row) { try { // We cannot merge if the row is not in the baseline. if (row > baseline.Rows) return null; // Create a copy of the merge row in the baseline 2da. StringCollection resultRow = new StringCollection(); StringCollection baselineRow = baseline.GetRowData(row); foreach (string s in baselineRow) resultRow.Add(s); // Create a bool array to keep track of which columns // we modify. bool[] writtenTo = new bool[resultRow.Count]; for (int i = 0; i < writtenTo.Length; i++) writtenTo[i] = false; foreach (_2DA merge in list) { // Get the row from the merge 2da. StringCollection mergeRow = merge.GetRowData(row); // If the collections do not have the same length then // fail the merge, the added column may not be at the end. if (mergeRow.Count != resultRow.Count) return null; // Loop through all of the columns. for (int i = 1; i < resultRow.Count; i++) { // Ignore empty data cells in the merge row. if (_2DA.Empty == mergeRow[i]) continue; // Compare the cell value against the baseline. If it is the // same then ignore it. (the result row starts out as the baseline // so we do not need to set these values, and we need to ignore // them to detect double writes to the same cell) if (_2DA.CompareCell(baselineRow[i], mergeRow[i], true)) continue; // Compare the cells from the result row and the merge row, // if they are different then we need to copy the merge // row's value into the result row. However, if a previous // merge 2da has modified this column then we have 2 different // 2da's wanting non-bioware default values in the same // column, if that happens there is no way to merge. if (!_2DA.CompareCell(mergeRow[i], resultRow[i], true)) { // If we've already changed the bioware default for this // column we cannot merge return null. if (writtenTo[i]) return null; else { // Overwrite the bioware default for this column and // save the fact that we have changed this column resultRow[i] = mergeRow[i]; writtenTo[i] = true; } } } } // If we get here we were able to take all of the various 2da // modifications to the bioware row and make 1 merge row with all // of the changes, return it. return resultRow; } catch (Exception) { return null; } }
/// <summary> /// Returns true if the row is a junk row. /// </summary> /// <param name="file">The 2da to test</param> /// <param name="row">The row to test</param> /// <returns>True if the row is a junk row.</returns> private bool IsJunkRow(_2DA file, int row) { if (row >= file.Rows) return false; int index = file.GetIndex("LABEL"); if (-1 == index) index = file.GetIndex("NAME"); if (-1 == index) return false; // Check for common labels indicating that it is a junk row. string value = file[row, index].ToLower(); if (-1 != value.IndexOf("deleted") || -1 != value.IndexOf("reserved") || -1 != value.IndexOf("user")) return true; return false; }
/// <summary> /// Copies a row from a source 2da to this 2da. /// </summary> /// <param name="source">The source 2da</param> /// <param name="sourceRow">The index of the row in the source 2da</param> /// <param name="row">The index of the row in this 2da</param> public void CopyRow(_2DA source, int sourceRow, int row) { // Get the row from the source 2da and let our overload do all of the // work. StringCollection sourceRowData = (StringCollection) source.rows[sourceRow]; CopyRow(sourceRowData, row); }
/// <summary> /// Merges 2 2da objects, saving the results in a 2da file. The method expects that /// the source 2da will have at least enough rows to be contiguous with the merge /// 2da (the source 2da should be padded by calling Pad() if necessary). If the /// source and merge 2da's share some rows, the merge 2da rows will overwrite the /// source 2da rows. /// </summary> /// <param name="source">The source 2da</param> /// <param name="merge">The merge 2da</param> /// <param name="outFile">The name of the output 2da</param> public static void Merge2da(_2DA source, _2DA merge, string outFile) { using(StreamWriter writer = new StreamWriter(outFile, false)) { // Write the 2da header. writer.WriteLine(headerString); writer.WriteLine(); writer.WriteLine(source.Heading); // Make the column sizes in the source and 2da files to be the largest // of each file, to make the columns have the correct width. for (int i = 0; i < source.colSizes.Length; i++) { source.colSizes[i] = System.Math.Max(source.colSizes[i], merge.colSizes[i]); merge.colSizes[i] = source.colSizes[i]; } // output all of the source strings before our merge. for (int i = 0; i < merge.Offset; i++) { string s = source[i]; writer.WriteLine(s); } // Test all of the rows that the merge is about to overwrite to make sure they // are really empty. If any are not then generate a warning message for those // rows. int end = System.Math.Min(source.rows.Count, merge.Offset + merge.rows.Count); for (int i = merge.Offset; i < end; i++) if (!source.IsEmpty(i)) { //CMain.Warning("Overwriting non-empty row {0} in {1}", i, source.FileName); } // output all of the merge strings. for (int i = 0; i < merge.rows.Count; i++) { string s = merge[i]; writer.WriteLine(s); } // output any remaining source strings, in case the merge is in the middle. for (int i = merge.Offset + merge.rows.Count; i < source.rows.Count; i++) { string s = source[i]; writer.WriteLine(s); } writer.Flush(); writer.Close(); } }
/// <summary> /// Factory method to create C2da objects from streams. /// </summary> /// <param name="stream">The stream to create the 2da object from</param> /// <returns>A 2da object for the stream.</returns> public static _2DA Load2da(Stream stream) { _2DA file = new _2DA(); using (StreamReader reader = new StreamReader(stream, Encoding.ASCII)) { file.Read(reader); } return file; }
/// <summary> /// Factory method to create C2da objects from 2da files. /// </summary> /// <param name="fileName">The name of the 2da file</param> /// <returns>A 2da object for the 2da file.</returns> public static _2DA Load2da(string fileName) { // Open the 2da file. _2DA file = new _2DA(fileName); using(StreamReader reader = new StreamReader(fileName)) { file.Read(reader); } return file; }
/// <summary> /// Compares rows in 2 different 2da files to see if they are equal or not. /// </summary> /// <param name="twoDA1">The first 2da to test</param> /// <param name="row1">The row in the first 2da to compare</param> /// <param name="twoDA2">The second 2da to test</param> /// <param name="row">The row in the second 2da to compare</param> /// <param name="ignoreCase">True if the comparison should be case insensitive</param> /// <returns>True if the rows are equal false if they are not</returns> public static bool CompareRow(_2DA twoDA1, int row1, _2DA twoDA2, int row2, bool ignoreCase) { // Get the data for each of the rows. StringCollection row1Data = (StringCollection) twoDA1.rows[row1]; StringCollection row2Data = (StringCollection) twoDA2.rows[row2]; // If the rows have different amounts of cells then they are by // definition different. if (row1Data.Count != row2Data.Count) return false; // Loop through the rows doing a cell by cell compare, stopping // if we find any differences. We start at column 1 to skip // the row numbrs which would of course be different. for (int i = 1; i < row1Data.Count; i++) if (!CompareCell(row1Data[i], row2Data[i], ignoreCase)) return false; // The rows are identical return true. return true; }