mirror of
https://github.com/claunia/SabreTools.git
synced 2025-12-16 19:14:27 +00:00
Implement ClrMameProReader
This commit is contained in:
@@ -15,7 +15,6 @@ namespace SabreTools.Library.DatFiles
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents parsing and writing of a ClrMamePro DAT
|
/// Represents parsing and writing of a ClrMamePro DAT
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// TODO: Can there be a writer like XmlTextWriter for this? Or too inconsistent?
|
|
||||||
internal class ClrMamePro : DatFile
|
internal class ClrMamePro : DatFile
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -66,8 +65,7 @@ namespace SabreTools.Library.DatFiles
|
|||||||
string normalizedValue = gc[1].Value.ToLowerInvariant();
|
string normalizedValue = gc[1].Value.ToLowerInvariant();
|
||||||
|
|
||||||
// If we have a known header
|
// If we have a known header
|
||||||
if (normalizedValue == "clrmamepro"
|
if (normalizedValue == "clrmamepro" || normalizedValue == "romvault")
|
||||||
|| normalizedValue == "romvault")
|
|
||||||
{
|
{
|
||||||
ReadHeader(sr, keep);
|
ReadHeader(sr, keep);
|
||||||
}
|
}
|
||||||
@@ -240,11 +238,11 @@ namespace SabreTools.Library.DatFiles
|
|||||||
{
|
{
|
||||||
containsItems = true;
|
containsItems = true;
|
||||||
ItemType temptype = ItemType.Rom;
|
ItemType temptype = ItemType.Rom;
|
||||||
if (line.Trim().StartsWith("rom ("))
|
if (trimmedline.StartsWith("rom ("))
|
||||||
temptype = ItemType.Rom;
|
temptype = ItemType.Rom;
|
||||||
else if (line.Trim().StartsWith("disk ("))
|
else if (trimmedline.StartsWith("disk ("))
|
||||||
temptype = ItemType.Disk;
|
temptype = ItemType.Disk;
|
||||||
else if (line.Trim().StartsWith("sample"))
|
else if (trimmedline.StartsWith("sample"))
|
||||||
temptype = ItemType.Sample;
|
temptype = ItemType.Sample;
|
||||||
|
|
||||||
// Create the proper DatItem based on the type
|
// Create the proper DatItem based on the type
|
||||||
|
|||||||
@@ -96,7 +96,7 @@ namespace SabreTools.Library.DatFiles
|
|||||||
// Some dats don't have the space between "Name:" and the dat name
|
// Some dats don't have the space between "Name:" and the dat name
|
||||||
if (line.Trim().StartsWith("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();
|
line = reader.ReadLine();
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -198,7 +198,7 @@ namespace SabreTools.Library.DatFiles
|
|||||||
&& linegc[i] != "date"
|
&& linegc[i] != "date"
|
||||||
&& linegc[i] != "crc")
|
&& linegc[i] != "crc")
|
||||||
{
|
{
|
||||||
item.Name += "{linegc[i]}";
|
item.Name += $"{linegc[i]}";
|
||||||
}
|
}
|
||||||
|
|
||||||
// Perform correction
|
// Perform correction
|
||||||
|
|||||||
@@ -566,6 +566,9 @@ namespace SabreTools.Library.Data
|
|||||||
|
|
||||||
public const string XmlPattern = @"<(.*?)>(.*?)</(.*?)>";
|
public const string XmlPattern = @"<(.*?)>(.*?)</(.*?)>";
|
||||||
public const string HeaderPatternCMP = @"(^.*?) \($";
|
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 ItemPatternCMP = @"^\s*(\S*?) (.*)";
|
||||||
public const string EndPatternCMP = @"^\s*\)\s*$";
|
public const string EndPatternCMP = @"^\s*\)\s*$";
|
||||||
|
|
||||||
|
|||||||
@@ -440,6 +440,18 @@
|
|||||||
|
|
||||||
#region Reader related
|
#region Reader related
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Different types of CMP rows being parsed
|
||||||
|
/// </summary>
|
||||||
|
public enum CmpRowType
|
||||||
|
{
|
||||||
|
None,
|
||||||
|
TopLevel,
|
||||||
|
Standalone,
|
||||||
|
Internal,
|
||||||
|
Comment,
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Different types of INI rows being parsed
|
/// Different types of INI rows being parsed
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -1,12 +1,224 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
|
using SabreTools.Library.Data;
|
||||||
|
using SabreTools.Library.Tools;
|
||||||
|
|
||||||
namespace SabreTools.Library.Readers
|
namespace SabreTools.Library.Readers
|
||||||
{
|
{
|
||||||
public class ClrMameProReader
|
public class ClrMameProReader : IDisposable
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Internal stream reader for inputting
|
||||||
|
/// </summary>
|
||||||
|
private StreamReader sr;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get if at end of stream
|
||||||
|
/// </summary>
|
||||||
|
public bool EndOfStream
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return sr?.EndOfStream ?? true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Contents of the currently read line as an internal item
|
||||||
|
/// </summary>
|
||||||
|
public Dictionary<string, string> Internal { get; private set; } = new Dictionary<string, string>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Current internal item name
|
||||||
|
/// </summary>
|
||||||
|
public string InternalName { get; private set; } = null;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get if we should be making DosCenter exceptions
|
||||||
|
/// </summary>
|
||||||
|
public bool DosCenter { get; set; } = false;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Current row type
|
||||||
|
/// </summary>
|
||||||
|
public CmpRowType RowType { get; private set; } = CmpRowType.None;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Contents of the currently read line as a standalone item
|
||||||
|
/// </summary>
|
||||||
|
public KeyValuePair<string, string>? Standalone { get; private set; } = null;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Current top-level being read
|
||||||
|
/// </summary>
|
||||||
|
public string TopLevel { get; private set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Constructor for opening a write from a file
|
||||||
|
/// </summary>
|
||||||
|
public ClrMameProReader(string filename)
|
||||||
|
{
|
||||||
|
sr = new StreamReader(filename);
|
||||||
|
DosCenter = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Constructor for opening a write from a stream and encoding
|
||||||
|
/// </summary>
|
||||||
|
public ClrMameProReader(Stream stream, Encoding encoding)
|
||||||
|
{
|
||||||
|
sr = new StreamReader(stream, encoding);
|
||||||
|
DosCenter = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Read the next line in the file
|
||||||
|
/// </summary>
|
||||||
|
public bool ReadNextLine()
|
||||||
|
{
|
||||||
|
if (!(sr.BaseStream?.CanRead ?? false) || sr.EndOfStream)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
string line = sr.ReadLine().Trim();
|
||||||
|
ProcessLine(line);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Process the current line and extract out values
|
||||||
|
/// </summary>
|
||||||
|
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<string, string>();
|
||||||
|
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<string, string>(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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Dispose of the underlying reader
|
||||||
|
/// </summary>
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
sr.Dispose();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2865,8 +2865,7 @@ namespace SabreTools.Library.Tools
|
|||||||
|
|
||||||
// Now we get each string, divided up as cleanly as possible
|
// Now we get each string, divided up as cleanly as possible
|
||||||
string[] matches = Regex
|
string[] matches = Regex
|
||||||
//.Matches(s, @"([^\s]*""[^""]+""[^\s]*)|[^""]?\w+[^""]?")
|
.Matches(s, Constants.InternalPatternAttributesCMP)
|
||||||
.Matches(s, @"[^\s""]+|""[^""]*""")
|
|
||||||
.Cast<Match>()
|
.Cast<Match>()
|
||||||
.Select(m => m.Groups[0].Value)
|
.Select(m => m.Groups[0].Value)
|
||||||
.ToArray();
|
.ToArray();
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ namespace SabreTools.Library.Writers
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Internal stream writer
|
/// Internal stream writer
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private StreamWriter textWriter;
|
private StreamWriter sw;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Stack for tracking current node
|
/// Stack for tracking current node
|
||||||
@@ -107,18 +107,7 @@ namespace SabreTools.Library.Writers
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public ClrMameProWriter(string filename)
|
public ClrMameProWriter(string filename)
|
||||||
{
|
{
|
||||||
textWriter = new StreamWriter(filename);
|
sw = new StreamWriter(filename);
|
||||||
Quotes = true;
|
|
||||||
stack = new TagInfo[10];
|
|
||||||
top = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Constructor for opening a write from a stream and encoding
|
|
||||||
/// </summary>
|
|
||||||
public ClrMameProWriter(Stream stream, Encoding encoding)
|
|
||||||
{
|
|
||||||
textWriter = new StreamWriter(stream, encoding);
|
|
||||||
Quotes = true;
|
Quotes = true;
|
||||||
|
|
||||||
// Element stack
|
// Element stack
|
||||||
@@ -128,11 +117,17 @@ namespace SabreTools.Library.Writers
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Base stream for easy access
|
/// Constructor for opening a write from a stream and encoding
|
||||||
/// </summary>
|
/// </summary>
|
||||||
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();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -145,8 +140,8 @@ namespace SabreTools.Library.Writers
|
|||||||
AutoComplete(Token.StartElement);
|
AutoComplete(Token.StartElement);
|
||||||
PushStack();
|
PushStack();
|
||||||
stack[top].Name = name;
|
stack[top].Name = name;
|
||||||
textWriter.Write(name);
|
sw.Write(name);
|
||||||
textWriter.Write(" (");
|
sw.Write(" (");
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
@@ -189,10 +184,10 @@ namespace SabreTools.Library.Writers
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
AutoComplete(Token.StartAttribute);
|
AutoComplete(Token.StartAttribute);
|
||||||
textWriter.Write(name);
|
sw.Write(name);
|
||||||
textWriter.Write(" ");
|
sw.Write(" ");
|
||||||
if (Quotes)
|
if (Quotes)
|
||||||
textWriter.Write("\"");
|
sw.Write("\"");
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
@@ -238,18 +233,18 @@ namespace SabreTools.Library.Writers
|
|||||||
throw new ArgumentException();
|
throw new ArgumentException();
|
||||||
|
|
||||||
AutoComplete(Token.Standalone);
|
AutoComplete(Token.Standalone);
|
||||||
textWriter.Write(name);
|
sw.Write(name);
|
||||||
textWriter.Write(" ");
|
sw.Write(" ");
|
||||||
if ((quoteOverride == null && Quotes)
|
if ((quoteOverride == null && Quotes)
|
||||||
|| (quoteOverride == true))
|
|| (quoteOverride == true))
|
||||||
{
|
{
|
||||||
textWriter.Write("\"");
|
sw.Write("\"");
|
||||||
}
|
}
|
||||||
textWriter.Write(value);
|
sw.Write(value);
|
||||||
if ((quoteOverride == null && Quotes)
|
if ((quoteOverride == null && Quotes)
|
||||||
|| (quoteOverride == true))
|
|| (quoteOverride == true))
|
||||||
{
|
{
|
||||||
textWriter.Write("\"");
|
sw.Write("\"");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
@@ -269,7 +264,7 @@ namespace SabreTools.Library.Writers
|
|||||||
if (!string.IsNullOrEmpty(value))
|
if (!string.IsNullOrEmpty(value))
|
||||||
{
|
{
|
||||||
AutoComplete(Token.Content);
|
AutoComplete(Token.Content);
|
||||||
textWriter.Write(value);
|
sw.Write(value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
@@ -295,7 +290,7 @@ namespace SabreTools.Library.Writers
|
|||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
currentState = State.Closed;
|
currentState = State.Closed;
|
||||||
textWriter.Close();
|
sw.Close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -305,7 +300,7 @@ namespace SabreTools.Library.Writers
|
|||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
Close();
|
Close();
|
||||||
textWriter.Dispose();
|
sw.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -313,7 +308,7 @@ namespace SabreTools.Library.Writers
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public void Flush()
|
public void Flush()
|
||||||
{
|
{
|
||||||
textWriter.Flush();
|
sw.Flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -367,11 +362,11 @@ namespace SabreTools.Library.Writers
|
|||||||
if (currentState == State.Attribute)
|
if (currentState == State.Attribute)
|
||||||
{
|
{
|
||||||
WriteEndAttributeQuote();
|
WriteEndAttributeQuote();
|
||||||
textWriter.Write(' ');
|
sw.Write(' ');
|
||||||
}
|
}
|
||||||
else if (currentState == State.Element)
|
else if (currentState == State.Element)
|
||||||
{
|
{
|
||||||
textWriter.Write(' ');
|
sw.Write(' ');
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
@@ -422,7 +417,7 @@ namespace SabreTools.Library.Writers
|
|||||||
if (this.lastToken == Token.LongEndElement)
|
if (this.lastToken == Token.LongEndElement)
|
||||||
{
|
{
|
||||||
Indent(true);
|
Indent(true);
|
||||||
textWriter.Write(')');
|
sw.Write(')');
|
||||||
}
|
}
|
||||||
|
|
||||||
top--;
|
top--;
|
||||||
@@ -440,7 +435,7 @@ namespace SabreTools.Library.Writers
|
|||||||
private void WriteEndStartTag(bool empty)
|
private void WriteEndStartTag(bool empty)
|
||||||
{
|
{
|
||||||
if (empty)
|
if (empty)
|
||||||
textWriter.Write(" )");
|
sw.Write(" )");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -449,7 +444,7 @@ namespace SabreTools.Library.Writers
|
|||||||
private void WriteEndAttributeQuote()
|
private void WriteEndAttributeQuote()
|
||||||
{
|
{
|
||||||
if (Quotes)
|
if (Quotes)
|
||||||
textWriter.Write("\"");
|
sw.Write("\"");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -459,15 +454,15 @@ namespace SabreTools.Library.Writers
|
|||||||
{
|
{
|
||||||
if (top == 0)
|
if (top == 0)
|
||||||
{
|
{
|
||||||
textWriter.WriteLine();
|
sw.WriteLine();
|
||||||
}
|
}
|
||||||
else if (!stack[top].Mixed)
|
else if (!stack[top].Mixed)
|
||||||
{
|
{
|
||||||
textWriter.WriteLine();
|
sw.WriteLine();
|
||||||
int i = beforeEndElement ? top - 1 : top;
|
int i = beforeEndElement ? top - 1 : top;
|
||||||
for (; i > 0; i--)
|
for (; i > 0; i--)
|
||||||
{
|
{
|
||||||
textWriter.Write('\t');
|
sw.Write('\t');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user