From 0cc10e73df666cec92d83c5130a9d38220d69ebf Mon Sep 17 00:00:00 2001 From: Matt Nadareski Date: Mon, 15 Jun 2020 10:56:47 -0700 Subject: [PATCH] Implement ClrMameProReader --- SabreTools.Library/DatFiles/ClrMamePro.cs | 10 +- SabreTools.Library/DatFiles/DosCenter.cs | 4 +- SabreTools.Library/Data/Constants.cs | 3 + SabreTools.Library/Data/Enums.cs | 12 + .../Readers/ClrMameProReader.cs | 216 +++++++++++++++++- SabreTools.Library/Tools/Utilities.cs | 3 +- .../Writers/ClrMameProWriter.cs | 71 +++--- 7 files changed, 269 insertions(+), 50 deletions(-) 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'); } } }