diff --git a/CHANGELOG.md b/CHANGELOG.md
index 40c6ed2..7405af0 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -27,6 +27,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
- Console uses unicode for table drawing on Net Standard
- Using UTC for tracking scheduled events, fixes issues with DST
- Using UTC for tracking durations
+ - Fixed a bug where table width calculations were not considering unprintable characters
## [10.3.0] - 2020-01-20
### Changed
diff --git a/ICD.Common.Utils/AnsiUtils.cs b/ICD.Common.Utils/AnsiUtils.cs
index bf9ea2d..a59f2a1 100644
--- a/ICD.Common.Utils/AnsiUtils.cs
+++ b/ICD.Common.Utils/AnsiUtils.cs
@@ -37,7 +37,7 @@ namespace ICD.Common.Utils
///
/// Matches ANSI escape codes, e.g. \x1b[31m and \x1b[30;1m
///
- private const string ANSI_PATTERN = "(?'match'\x01b\\[(?'code'[\\d;]+)m)";
+ public const string ANSI_REGEX = "(?'match'\x01b\\[(?'code'[\\d;]+)m)";
///
/// Matches ANSI escape codes to HTML styles.
@@ -105,7 +105,7 @@ namespace ICD.Common.Utils
if (string.IsNullOrEmpty(data))
yield break;
- Regex regex = new Regex(ANSI_PATTERN);
+ Regex regex = new Regex(ANSI_REGEX);
Match match = regex.Match(data);
// No matches
diff --git a/ICD.Common.Utils/Extensions/StringExtensions.cs b/ICD.Common.Utils/Extensions/StringExtensions.cs
index 066e984..f785d60 100644
--- a/ICD.Common.Utils/Extensions/StringExtensions.cs
+++ b/ICD.Common.Utils/Extensions/StringExtensions.cs
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
+using System.Text.RegularExpressions;
using ICD.Common.Properties;
namespace ICD.Common.Utils.Extensions
@@ -256,5 +257,55 @@ namespace ICD.Common.Utils.Extensions
return hash1 + (hash2 * 1566083941);
}
}
+
+ ///
+ /// Strips all of the non-printable characters and control codes from the string.
+ ///
+ ///
+ ///
+ public static string ToPrintableCharacters([NotNull] this string extends)
+ {
+ if (extends == null)
+ throw new ArgumentNullException("extends");
+
+ // Strip ANSI escape sequences
+ extends = Regex.Replace(extends, AnsiUtils.ANSI_REGEX, string.Empty);
+
+ // Strip control characters
+ extends = Regex.Replace(extends, @"\p{C}+", string.Empty);
+
+ return extends;
+ }
+
+ ///
+ /// Gets the printable length of the string.
+ ///
+ ///
+ ///
+ public static int GetPrintableLength([NotNull] this string extends)
+ {
+ if (extends == null)
+ throw new ArgumentNullException("extends");
+
+ return extends.ToPrintableCharacters().Length;
+ }
+
+ ///
+ /// Pads the string to the number of printable characters.
+ ///
+ ///
+ ///
+ ///
+ public static string PadRightPrintable([NotNull] this string extends, int length)
+ {
+ if (extends == null)
+ throw new ArgumentNullException("extends");
+
+ int printableLength = extends.GetPrintableLength();
+ int actualLength = extends.Length;
+ int delta = actualLength - printableLength;
+
+ return extends.PadRight(length + delta);
+ }
}
}
diff --git a/ICD.Common.Utils/TableBuilder.cs b/ICD.Common.Utils/TableBuilder.cs
index 0a7bc6a..3d86dee 100644
--- a/ICD.Common.Utils/TableBuilder.cs
+++ b/ICD.Common.Utils/TableBuilder.cs
@@ -169,14 +169,14 @@ namespace ICD.Common.Utils
private int GetColumnWidth(int index)
{
- int titleLength = m_Columns[index].Length + 1;
+ int titleLength = m_Columns[index].GetPrintableLength() + 1;
if (m_Rows.Count == 0)
return titleLength;
int maxColumnWidth = m_Rows.Except((string[])null)
- .Max(x => x[index] != null ? x[index].Length : 0) + 1;
+ .Max(x => x[index] != null ? x[index].GetPrintableLength() : 0) + 1;
- return (titleLength > maxColumnWidth) ? titleLength : maxColumnWidth;
+ return titleLength > maxColumnWidth ? titleLength : maxColumnWidth;
}
private void AppendTopSeparator(StringBuilder builder, IList columnWidths)
@@ -245,7 +245,7 @@ namespace ICD.Common.Utils
builder.Append(' ');
string value = row[index] ?? string.Empty;
- builder.Append(value.PadRight(columnWidths[index]));
+ builder.Append(value.PadRightPrintable(columnWidths[index]));
if (index < row.Count - 1)
builder.Append(VERTICAL);