diff --git a/SabreTools.Library/DatFiles/ClrMamePro.cs b/SabreTools.Library/DatFiles/ClrMamePro.cs
index a4121a29..110cd526 100644
--- a/SabreTools.Library/DatFiles/ClrMamePro.cs
+++ b/SabreTools.Library/DatFiles/ClrMamePro.cs
@@ -15,7 +15,6 @@ namespace SabreTools.Library.DatFiles
///
/// Represents parsing and writing of a ClrMamePro DAT
///
- /// TODO: Can there be a writer like XmlTextWriter for this? Or too inconsistent?
internal class ClrMamePro : DatFile
{
///
@@ -66,8 +65,7 @@ namespace SabreTools.Library.DatFiles
string normalizedValue = gc[1].Value.ToLowerInvariant();
// If we have a known header
- if (normalizedValue == "clrmamepro"
- || normalizedValue == "romvault")
+ if (normalizedValue == "clrmamepro" || normalizedValue == "romvault")
{
ReadHeader(sr, keep);
}
@@ -240,11 +238,11 @@ namespace SabreTools.Library.DatFiles
{
containsItems = true;
ItemType temptype = ItemType.Rom;
- if (line.Trim().StartsWith("rom ("))
+ if (trimmedline.StartsWith("rom ("))
temptype = ItemType.Rom;
- else if (line.Trim().StartsWith("disk ("))
+ else if (trimmedline.StartsWith("disk ("))
temptype = ItemType.Disk;
- else if (line.Trim().StartsWith("sample"))
+ else if (trimmedline.StartsWith("sample"))
temptype = ItemType.Sample;
// Create the proper DatItem based on the type
diff --git a/SabreTools.Library/DatFiles/DosCenter.cs b/SabreTools.Library/DatFiles/DosCenter.cs
index af3eca17..b5a46731 100644
--- a/SabreTools.Library/DatFiles/DosCenter.cs
+++ b/SabreTools.Library/DatFiles/DosCenter.cs
@@ -96,7 +96,7 @@ namespace SabreTools.Library.DatFiles
// Some dats don't have the space between "Name:" and the dat name
if (line.Trim().StartsWith("Name:"))
{
- Name = (string.IsNullOrWhiteSpace(Name) ? line.Substring(6).Trim() : Name);
+ Name = (string.IsNullOrWhiteSpace(Name) ? line.Substring("Name:".Length).Trim() : Name);
line = reader.ReadLine();
continue;
}
@@ -198,7 +198,7 @@ namespace SabreTools.Library.DatFiles
&& linegc[i] != "date"
&& linegc[i] != "crc")
{
- item.Name += "{linegc[i]}";
+ item.Name += $"{linegc[i]}";
}
// Perform correction
diff --git a/SabreTools.Library/Data/Constants.cs b/SabreTools.Library/Data/Constants.cs
index cda3e605..2aa8d270 100644
--- a/SabreTools.Library/Data/Constants.cs
+++ b/SabreTools.Library/Data/Constants.cs
@@ -566,6 +566,9 @@ namespace SabreTools.Library.Data
public const string XmlPattern = @"<(.*?)>(.*?)(.*?)>";
public const string HeaderPatternCMP = @"(^.*?) \($";
+ public const string InternalPatternCMP = @"(^.*?) (\(.+\))$";
+ public const string InternalPatternAttributesCMP = @"[^\s""]+|""[^""]*""";
+ //public const string InternalPatternAttributesCMP = @"([^\s]*""[^""]+""[^\s]*)|[^""]?\w+[^""]?";
public const string ItemPatternCMP = @"^\s*(\S*?) (.*)";
public const string EndPatternCMP = @"^\s*\)\s*$";
diff --git a/SabreTools.Library/Data/Enums.cs b/SabreTools.Library/Data/Enums.cs
index 7e171970..49d2b73a 100644
--- a/SabreTools.Library/Data/Enums.cs
+++ b/SabreTools.Library/Data/Enums.cs
@@ -440,6 +440,18 @@
#region Reader related
+ ///
+ /// Different types of CMP rows being parsed
+ ///
+ public enum CmpRowType
+ {
+ None,
+ TopLevel,
+ Standalone,
+ Internal,
+ Comment,
+ }
+
///
/// Different types of INI rows being parsed
///
diff --git a/SabreTools.Library/Readers/ClrMameProReader.cs b/SabreTools.Library/Readers/ClrMameProReader.cs
index 9a147673..e4134d3c 100644
--- a/SabreTools.Library/Readers/ClrMameProReader.cs
+++ b/SabreTools.Library/Readers/ClrMameProReader.cs
@@ -1,12 +1,224 @@
using System;
using System.Collections.Generic;
+using System.IO;
using System.Linq;
using System.Text;
-using System.Threading.Tasks;
+using System.Text.RegularExpressions;
+
+using SabreTools.Library.Data;
+using SabreTools.Library.Tools;
namespace SabreTools.Library.Readers
{
- public class ClrMameProReader
+ public class ClrMameProReader : IDisposable
{
+ ///
+ /// Internal stream reader for inputting
+ ///
+ private StreamReader sr;
+
+ ///
+ /// Get if at end of stream
+ ///
+ public bool EndOfStream
+ {
+ get
+ {
+ return sr?.EndOfStream ?? true;
+ }
+ }
+
+ ///
+ /// Contents of the currently read line as an internal item
+ ///
+ public Dictionary Internal { get; private set; } = new Dictionary();
+
+ ///
+ /// Current internal item name
+ ///
+ public string InternalName { get; private set; } = null;
+
+ ///
+ /// Get if we should be making DosCenter exceptions
+ ///
+ public bool DosCenter { get; set; } = false;
+
+ ///
+ /// Current row type
+ ///
+ public CmpRowType RowType { get; private set; } = CmpRowType.None;
+
+ ///
+ /// Contents of the currently read line as a standalone item
+ ///
+ public KeyValuePair? Standalone { get; private set; } = null;
+
+ ///
+ /// Current top-level being read
+ ///
+ public string TopLevel { get; private set; } = string.Empty;
+
+ ///
+ /// Constructor for opening a write from a file
+ ///
+ public ClrMameProReader(string filename)
+ {
+ sr = new StreamReader(filename);
+ DosCenter = true;
+ }
+
+ ///
+ /// Constructor for opening a write from a stream and encoding
+ ///
+ public ClrMameProReader(Stream stream, Encoding encoding)
+ {
+ sr = new StreamReader(stream, encoding);
+ DosCenter = true;
+ }
+
+ ///
+ /// Read the next line in the file
+ ///
+ public bool ReadNextLine()
+ {
+ if (!(sr.BaseStream?.CanRead ?? false) || sr.EndOfStream)
+ return false;
+
+ string line = sr.ReadLine().Trim();
+ ProcessLine(line);
+ return true;
+ }
+
+ ///
+ /// Process the current line and extract out values
+ ///
+ private void ProcessLine(string line)
+ {
+ // Standalone (special case for DC dats)
+ if (line.StartsWith("Name:"))
+ {
+ string temp = line.Substring("Name:".Length).Trim();
+ line = $"Name: {temp}";
+ }
+
+ // Comment
+ if (line.StartsWith("#"))
+ {
+ Internal = null;
+ InternalName = null;
+ RowType = CmpRowType.Comment;
+ Standalone = null;
+ }
+
+ // Top-level
+ else if (Regex.IsMatch(line, Constants.HeaderPatternCMP))
+ {
+ GroupCollection gc = Regex.Match(line, Constants.HeaderPatternCMP).Groups;
+ string normalizedValue = gc[1].Value.ToLowerInvariant();
+
+ Internal = null;
+ InternalName = null;
+ RowType = CmpRowType.TopLevel;
+ Standalone = null;
+ TopLevel = normalizedValue;
+ }
+
+ // Internal
+ else if (Regex.IsMatch(line, Constants.InternalPatternCMP))
+ {
+ GroupCollection gc = Regex.Match(line, Constants.InternalPatternCMP).Groups;
+ string normalizedValue = gc[1].Value.ToLowerInvariant();
+ string[] linegc = Utilities.SplitLineAsCMP(gc[2].Value);
+
+ Internal = new Dictionary();
+ for (int i = 0; i < linegc.Length; i++)
+ {
+ string key = linegc[i].Replace("\"", string.Empty);
+ if (string.IsNullOrWhiteSpace(key))
+ continue;
+
+ string value = string.Empty;
+
+ // Special case for DC-style dats, only a few known fields
+ if (DosCenter)
+ {
+ // If we have a name
+ if (key == "name")
+ {
+ while (++i < linegc.Length && linegc[i] != "size" && linegc[i] != "date" && linegc[i] != "crc")
+ {
+ value += $"{linegc[i]}";
+ }
+
+ value = value.Trim();
+ i--;
+ }
+ // If we have a date (split into 2 parts)
+ else if (key == "date")
+ {
+ value = $"{linegc[++i].Replace("\"", string.Empty)} {linegc[++i].Replace("\"", string.Empty)}";
+ }
+ }
+ else
+ {
+ // Special cases for standalone statuses
+ if (key == "baddump" || key == "good" || key == "nodump" || key == "verified")
+ {
+ value = key;
+ key = "status";
+ }
+ else
+ {
+ value = linegc[++i].Replace("\"", string.Empty);
+ }
+ }
+
+ Internal[key] = value;
+ RowType = CmpRowType.Internal;
+ Standalone = null;
+ }
+
+ InternalName = normalizedValue;
+ }
+
+ // Standalone
+ else if (Regex.IsMatch(line, Constants.ItemPatternCMP))
+ {
+ GroupCollection gc = Regex.Match(line, Constants.ItemPatternCMP).Groups;
+ string itemval = gc[2].Value.Replace("\"", string.Empty);
+
+ Internal = null;
+ InternalName = null;
+ RowType = CmpRowType.Standalone;
+ Standalone = new KeyValuePair(gc[1].Value, itemval);
+ }
+
+ // End section
+ else if (Regex.IsMatch(line, Constants.EndPatternCMP))
+ {
+ Internal = null;
+ InternalName = null;
+ RowType = CmpRowType.None;
+ Standalone = null;
+ TopLevel = null;
+ }
+
+ // Invalid (usually whitespace)
+ else
+ {
+ Internal = null;
+ InternalName = null;
+ RowType = CmpRowType.None;
+ Standalone = null;
+ }
+ }
+
+ ///
+ /// Dispose of the underlying reader
+ ///
+ public void Dispose()
+ {
+ sr.Dispose();
+ }
}
}
diff --git a/SabreTools.Library/Tools/Utilities.cs b/SabreTools.Library/Tools/Utilities.cs
index 3d239e2c..71d105cc 100644
--- a/SabreTools.Library/Tools/Utilities.cs
+++ b/SabreTools.Library/Tools/Utilities.cs
@@ -2865,8 +2865,7 @@ namespace SabreTools.Library.Tools
// Now we get each string, divided up as cleanly as possible
string[] matches = Regex
- //.Matches(s, @"([^\s]*""[^""]+""[^\s]*)|[^""]?\w+[^""]?")
- .Matches(s, @"[^\s""]+|""[^""]*""")
+ .Matches(s, Constants.InternalPatternAttributesCMP)
.Cast()
.Select(m => m.Groups[0].Value)
.ToArray();
diff --git a/SabreTools.Library/Writers/ClrMameProWriter.cs b/SabreTools.Library/Writers/ClrMameProWriter.cs
index 8c2b0b2b..2f9a5118 100644
--- a/SabreTools.Library/Writers/ClrMameProWriter.cs
+++ b/SabreTools.Library/Writers/ClrMameProWriter.cs
@@ -59,7 +59,7 @@ namespace SabreTools.Library.Writers
///
/// Internal stream writer
///
- private StreamWriter textWriter;
+ private StreamWriter sw;
///
/// Stack for tracking current node
@@ -107,20 +107,9 @@ namespace SabreTools.Library.Writers
///
public ClrMameProWriter(string filename)
{
- textWriter = new StreamWriter(filename);
+ sw = new StreamWriter(filename);
Quotes = true;
- stack = new TagInfo[10];
- top = 0;
- }
- ///
- /// Constructor for opening a write from a stream and encoding
- ///
- public ClrMameProWriter(Stream stream, Encoding encoding)
- {
- textWriter = new StreamWriter(stream, encoding);
- Quotes = true;
-
// Element stack
stack = new TagInfo[10];
top = 0;
@@ -128,11 +117,17 @@ namespace SabreTools.Library.Writers
}
///
- /// Base stream for easy access
+ /// Constructor for opening a write from a stream and encoding
///
- public Stream BaseStream
+ public ClrMameProWriter(Stream stream, Encoding encoding)
{
- get { return textWriter?.BaseStream ?? null; }
+ sw = new StreamWriter(stream, encoding);
+ Quotes = true;
+
+ // Element stack
+ stack = new TagInfo[10];
+ top = 0;
+ stack[top].Init();
}
///
@@ -145,8 +140,8 @@ namespace SabreTools.Library.Writers
AutoComplete(Token.StartElement);
PushStack();
stack[top].Name = name;
- textWriter.Write(name);
- textWriter.Write(" (");
+ sw.Write(name);
+ sw.Write(" (");
}
catch
{
@@ -189,10 +184,10 @@ namespace SabreTools.Library.Writers
try
{
AutoComplete(Token.StartAttribute);
- textWriter.Write(name);
- textWriter.Write(" ");
+ sw.Write(name);
+ sw.Write(" ");
if (Quotes)
- textWriter.Write("\"");
+ sw.Write("\"");
}
catch
{
@@ -238,18 +233,18 @@ namespace SabreTools.Library.Writers
throw new ArgumentException();
AutoComplete(Token.Standalone);
- textWriter.Write(name);
- textWriter.Write(" ");
+ sw.Write(name);
+ sw.Write(" ");
if ((quoteOverride == null && Quotes)
|| (quoteOverride == true))
{
- textWriter.Write("\"");
+ sw.Write("\"");
}
- textWriter.Write(value);
+ sw.Write(value);
if ((quoteOverride == null && Quotes)
|| (quoteOverride == true))
{
- textWriter.Write("\"");
+ sw.Write("\"");
}
}
catch
@@ -269,7 +264,7 @@ namespace SabreTools.Library.Writers
if (!string.IsNullOrEmpty(value))
{
AutoComplete(Token.Content);
- textWriter.Write(value);
+ sw.Write(value);
}
}
catch
@@ -295,7 +290,7 @@ namespace SabreTools.Library.Writers
finally
{
currentState = State.Closed;
- textWriter.Close();
+ sw.Close();
}
}
@@ -305,7 +300,7 @@ namespace SabreTools.Library.Writers
public void Dispose()
{
Close();
- textWriter.Dispose();
+ sw.Dispose();
}
///
@@ -313,7 +308,7 @@ namespace SabreTools.Library.Writers
///
public void Flush()
{
- textWriter.Flush();
+ sw.Flush();
}
///
@@ -367,11 +362,11 @@ namespace SabreTools.Library.Writers
if (currentState == State.Attribute)
{
WriteEndAttributeQuote();
- textWriter.Write(' ');
+ sw.Write(' ');
}
else if (currentState == State.Element)
{
- textWriter.Write(' ');
+ sw.Write(' ');
}
break;
@@ -422,7 +417,7 @@ namespace SabreTools.Library.Writers
if (this.lastToken == Token.LongEndElement)
{
Indent(true);
- textWriter.Write(')');
+ sw.Write(')');
}
top--;
@@ -440,7 +435,7 @@ namespace SabreTools.Library.Writers
private void WriteEndStartTag(bool empty)
{
if (empty)
- textWriter.Write(" )");
+ sw.Write(" )");
}
///
@@ -449,7 +444,7 @@ namespace SabreTools.Library.Writers
private void WriteEndAttributeQuote()
{
if (Quotes)
- textWriter.Write("\"");
+ sw.Write("\"");
}
///
@@ -459,15 +454,15 @@ namespace SabreTools.Library.Writers
{
if (top == 0)
{
- textWriter.WriteLine();
+ sw.WriteLine();
}
else if (!stack[top].Mixed)
{
- textWriter.WriteLine();
+ sw.WriteLine();
int i = beforeEndElement ? top - 1 : top;
for (; i > 0; i--)
{
- textWriter.Write('\t');
+ sw.Write('\t');
}
}
}