diff --git a/SabreTools.DatFiles/Formats/SoftwareList.Reader.cs b/SabreTools.DatFiles/Formats/SoftwareList.Reader.cs new file mode 100644 index 00000000..51683867 --- /dev/null +++ b/SabreTools.DatFiles/Formats/SoftwareList.Reader.cs @@ -0,0 +1,496 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Xml; +using System.Xml.Schema; +using SabreTools.Core; +using SabreTools.Core.Tools; +using SabreTools.DatItems; +using SabreTools.DatItems.Formats; + +namespace SabreTools.DatFiles.Formats +{ + /// + /// Represents parsing a SoftwareList + /// + internal partial class SoftwareList : DatFile + { + /// + public override void ParseFile(string filename, int indexId, bool keep, bool statsOnly = false, bool throwOnError = false) + { + // Prepare all internal variables + XmlReader xtr = XmlReader.Create(filename, new XmlReaderSettings + { + CheckCharacters = false, + DtdProcessing = DtdProcessing.Ignore, + IgnoreComments = true, + IgnoreWhitespace = true, + ValidationFlags = XmlSchemaValidationFlags.None, + ValidationType = ValidationType.None, + }); + + // If we got a null reader, just return + if (xtr == null) + return; + + // Otherwise, read the file to the end + try + { + xtr.MoveToContent(); + while (!xtr.EOF) + { + // We only want elements + if (xtr.NodeType != XmlNodeType.Element) + { + xtr.Read(); + continue; + } + + switch (xtr.Name) + { + case "softwarelist": + Header.Name ??= xtr.GetAttribute("name") ?? string.Empty; + Header.Description ??= xtr.GetAttribute("description") ?? string.Empty; + + xtr.Read(); + break; + + // We want to process the entire subtree of the machine + case "software": + ReadSoftware(xtr.ReadSubtree(), statsOnly, filename, indexId); + + // Skip the software now that we've processed it + xtr.Skip(); + break; + + default: + xtr.Read(); + break; + } + } + } + catch (Exception ex) when (!throwOnError) + { + logger.Warning(ex, $"Exception found while parsing '{filename}'"); + + // For XML errors, just skip the affected node + xtr?.Read(); + } + + xtr.Dispose(); + } + + /// + /// Read software information + /// + /// XmlReader representing a software block + /// True to only add item statistics while parsing, false otherwise + /// Name of the file to be parsed + /// Index ID for the DAT + private void ReadSoftware(XmlReader reader, bool statsOnly, string filename, int indexId) + { + // If we have an empty software, skip it + if (reader == null) + return; + + // Otherwise, add what is possible + reader.MoveToContent(); + + bool containsItems = false; + + // Create a new machine + Machine machine = new() + { + Name = reader.GetAttribute("name"), + CloneOf = reader.GetAttribute("cloneof"), + Supported = reader.GetAttribute("supported").AsSupported(), + }; + + while (!reader.EOF) + { + // We only want elements + if (reader.NodeType != XmlNodeType.Element) + { + reader.Read(); + continue; + } + + // Get the elements from the software + switch (reader.Name) + { + case "description": + machine.Description = reader.ReadElementContentAsString(); + break; + + case "year": + machine.Year = reader.ReadElementContentAsString(); + break; + + case "publisher": + machine.Publisher = reader.ReadElementContentAsString(); + break; + + case "info": + ParseAddHelper(new Info + { + Name = reader.GetAttribute("name"), + Value = reader.GetAttribute("value"), + + Source = new Source + { + Index = indexId, + Name = filename, + }, + }, statsOnly); + + reader.Read(); + break; + + case "sharedfeat": + ParseAddHelper(new SharedFeature + { + Name = reader.GetAttribute("name"), + Value = reader.GetAttribute("value"), + + Source = new Source + { + Index = indexId, + Name = filename, + }, + }, statsOnly); + + reader.Read(); + break; + + case "part": // Contains all rom and disk information + var part = new Part() + { + Name = reader.GetAttribute("name"), + Interface = reader.GetAttribute("interface"), + }; + + // Now read the internal tags + containsItems = ReadPart(reader.ReadSubtree(), machine, part, statsOnly, filename, indexId); + + // Skip the part now that we've processed it + reader.Skip(); + break; + + default: + reader.Read(); + break; + } + } + + // If no items were found for this machine, add a Blank placeholder + if (!containsItems) + { + Blank blank = new() + { + Source = new Source + { + Index = indexId, + Name = filename, + }, + }; + + blank.CopyMachineInformation(machine); + + // Now process and add the rom + ParseAddHelper(blank, statsOnly); + } + } + + /// + /// Read part information + /// + /// XmlReader representing a part block + /// Machine information to pass to contained items + /// Part information to pass to contained items + /// True to only add item statistics while parsing, false otherwise + /// Name of the file to be parsed + /// Index ID for the DAT + private bool ReadPart(XmlReader reader, Machine machine, Part part, bool statsOnly, string filename, int indexId) + { + // If we have an empty port, skip it + if (reader == null) + return false; + + // Get lists ready + part.Features = new List(); + List items = new(); + + while (!reader.EOF) + { + // We only want elements + if (reader.NodeType != XmlNodeType.Element) + { + reader.Read(); + continue; + } + + // Get the elements from the software + switch (reader.Name) + { + case "feature": + var feature = new PartFeature() + { + Name = reader.GetAttribute("name"), + Value = reader.GetAttribute("value"), + }; + + part.Features.Add(feature); + + reader.Read(); + break; + + case "dataarea": + var dataArea = new DataArea + { + Name = reader.GetAttribute("name"), + Size = Utilities.CleanLong(reader.GetAttribute("size")), + Width = Utilities.CleanLong(reader.GetAttribute("width")), + Endianness = reader.GetAttribute("endianness").AsEndianness(), + }; + + List roms = ReadDataArea(reader.ReadSubtree(), dataArea); + + // If we got valid roms, add them to the list + if (roms != null) + items.AddRange(roms); + + // Skip the dataarea now that we've processed it + reader.Skip(); + break; + + case "diskarea": + var diskArea = new DiskArea + { + Name = reader.GetAttribute("name"), + }; + + List disks = ReadDiskArea(reader.ReadSubtree(), diskArea); + + // If we got valid disks, add them to the list + if (disks != null) + items.AddRange(disks); + + // Skip the diskarea now that we've processed it + reader.Skip(); + break; + + case "dipswitch": + var dipSwitch = new DipSwitch + { + Name = reader.GetAttribute("name"), + Tag = reader.GetAttribute("tag"), + Mask = reader.GetAttribute("mask"), + }; + + // Now read the internal tags + ReadDipSwitch(reader.ReadSubtree(), dipSwitch); + + items.Add(dipSwitch); + + // Skip the dipswitch now that we've processed it + reader.Skip(); + break; + + default: + reader.Read(); + break; + } + } + + // Loop over all of the items, if they exist + string key = string.Empty; + foreach (DatItem item in items) + { + // Add all missing information + switch (item.ItemType) + { + case ItemType.DipSwitch: + (item as DipSwitch).Part = part; + break; + case ItemType.Disk: + (item as Disk).Part = part; + break; + case ItemType.Rom: + (item as Rom).Part = part; + + // If the rom is continue or ignore, add the size to the previous rom + // TODO: Can this be done on write? We technically lose information this way. + // Order is not guaranteed, and since these don't tend to have any way + // of determining what the "previous" item was after this, that info would + // have to be stored *with* the item somehow + if ((item as Rom).LoadFlag == LoadFlag.Continue || (item as Rom).LoadFlag == LoadFlag.Ignore) + { + int index = Items[key].Count - 1; + DatItem lastrom = Items[key][index]; + if (lastrom.ItemType == ItemType.Rom) + { + (lastrom as Rom).Size += (item as Rom).Size; + Items[key].RemoveAt(index); + Items[key].Add(lastrom); + } + + continue; + } + + break; + } + + item.Source = new Source(indexId, filename); + item.CopyMachineInformation(machine); + + // Finally add each item + key = ParseAddHelper(item, statsOnly); + } + + return items.Any(); + } + + /// + /// Read dataarea information + /// + /// XmlReader representing a dataarea block + /// DataArea representing the enclosing area + private List ReadDataArea(XmlReader reader, DataArea dataArea) + { + List items = new(); + + while (!reader.EOF) + { + // We only want elements + if (reader.NodeType != XmlNodeType.Element) + { + reader.Read(); + continue; + } + + // Get the elements from the software + switch (reader.Name) + { + case "rom": + var rom = new Rom + { + Name = reader.GetAttribute("name"), + Size = Utilities.CleanLong(reader.GetAttribute("size")), + CRC = reader.GetAttribute("crc"), + SHA1 = reader.GetAttribute("sha1"), + Offset = reader.GetAttribute("offset"), + Value = reader.GetAttribute("value"), + ItemStatus = reader.GetAttribute("status").AsItemStatus(), + LoadFlag = reader.GetAttribute("loadflag").AsLoadFlag(), + + DataArea = dataArea, + }; + + items.Add(rom); + reader.Read(); + break; + + default: + reader.Read(); + break; + } + } + + return items; + } + + /// + /// Read diskarea information + /// + /// XmlReader representing a diskarea block + /// DiskArea representing the enclosing area + private List ReadDiskArea(XmlReader reader, DiskArea diskArea) + { + List items = new(); + + while (!reader.EOF) + { + // We only want elements + if (reader.NodeType != XmlNodeType.Element) + { + reader.Read(); + continue; + } + + // Get the elements from the software + switch (reader.Name) + { + case "disk": + DatItem disk = new Disk + { + Name = reader.GetAttribute("name"), + SHA1 = reader.GetAttribute("sha1"), + ItemStatus = reader.GetAttribute("status").AsItemStatus(), + Writable = reader.GetAttribute("writable").AsYesNo(), + + DiskArea = diskArea, + }; + + items.Add(disk); + reader.Read(); + break; + + default: + reader.Read(); + break; + } + } + + return items; + } + + /// + /// Read DipSwitch DipValues information + /// + /// XmlReader representing a diskarea block + /// DipSwitch to populate + private void ReadDipSwitch(XmlReader reader, DipSwitch dipSwitch) + { + // If we have an empty dipswitch, skip it + if (reader == null) + return; + + // Get list ready + dipSwitch.Values = new List(); + + // Otherwise, add what is possible + reader.MoveToContent(); + + while (!reader.EOF) + { + // We only want elements + if (reader.NodeType != XmlNodeType.Element) + { + reader.Read(); + continue; + } + + // Get the information from the dipswitch + switch (reader.Name) + { + case "dipvalue": + var dipValue = new Setting + { + Name = reader.GetAttribute("name"), + Value = reader.GetAttribute("value"), + Default = reader.GetAttribute("default").AsYesNo(), + }; + + dipSwitch.Values.Add(dipValue); + + reader.Read(); + break; + + default: + reader.Read(); + break; + } + } + } + } +} diff --git a/SabreTools.DatFiles/Formats/SoftwareList.Writer.cs b/SabreTools.DatFiles/Formats/SoftwareList.Writer.cs new file mode 100644 index 00000000..44500bed --- /dev/null +++ b/SabreTools.DatFiles/Formats/SoftwareList.Writer.cs @@ -0,0 +1,430 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Xml; +using SabreTools.Core; +using SabreTools.Core.Tools; +using SabreTools.DatItems; +using SabreTools.DatItems.Formats; +using SabreTools.IO; + +namespace SabreTools.DatFiles.Formats +{ + /// + /// Represents writing a SoftwareList + /// + internal partial class SoftwareList : DatFile + { + /// + protected override ItemType[] GetSupportedTypes() + { + return new ItemType[] + { + ItemType.DipSwitch, + ItemType.Disk, + ItemType.Info, + ItemType.Rom, + ItemType.SharedFeature, + }; + } + + /// + protected override List GetMissingRequiredFields(DatItem datItem) + { + List missingFields = new(); + + switch (datItem.ItemType) + { + case ItemType.DipSwitch: + DipSwitch dipSwitch = datItem as DipSwitch; + if (!dipSwitch.PartSpecified) + { + missingFields.Add(DatItemField.Part_Name); + missingFields.Add(DatItemField.Part_Interface); + } + else + { + if (string.IsNullOrWhiteSpace(dipSwitch.Part.Name)) + missingFields.Add(DatItemField.Part_Name); + if (string.IsNullOrWhiteSpace(dipSwitch.Part.Interface)) + missingFields.Add(DatItemField.Part_Interface); + } + if (string.IsNullOrWhiteSpace(dipSwitch.Name)) + missingFields.Add(DatItemField.Name); + if (string.IsNullOrWhiteSpace(dipSwitch.Tag)) + missingFields.Add(DatItemField.Tag); + if (string.IsNullOrWhiteSpace(dipSwitch.Mask)) + missingFields.Add(DatItemField.Mask); + if (dipSwitch.ValuesSpecified) + { + if (dipSwitch.Values.Any(dv => string.IsNullOrWhiteSpace(dv.Name))) + missingFields.Add(DatItemField.Part_Feature_Name); + if (dipSwitch.Values.Any(dv => string.IsNullOrWhiteSpace(dv.Value))) + missingFields.Add(DatItemField.Part_Feature_Value); + } + + break; + case ItemType.Disk: + Disk disk = datItem as Disk; + if (!disk.PartSpecified) + { + missingFields.Add(DatItemField.Part_Name); + missingFields.Add(DatItemField.Part_Interface); + } + else + { + if (string.IsNullOrWhiteSpace(disk.Part.Name)) + missingFields.Add(DatItemField.Part_Name); + if (string.IsNullOrWhiteSpace(disk.Part.Interface)) + missingFields.Add(DatItemField.Part_Interface); + } + if (!disk.DiskAreaSpecified) + { + missingFields.Add(DatItemField.AreaName); + } + else + { + if (string.IsNullOrWhiteSpace(disk.DiskArea.Name)) + missingFields.Add(DatItemField.AreaName); + } + if (string.IsNullOrWhiteSpace(disk.Name)) + missingFields.Add(DatItemField.Name); + break; + case ItemType.Info: + Info info = datItem as Info; + if (string.IsNullOrWhiteSpace(info.Name)) + missingFields.Add(DatItemField.Name); + break; + case ItemType.Rom: + Rom rom = datItem as Rom; + if (!rom.PartSpecified) + { + missingFields.Add(DatItemField.Part_Name); + missingFields.Add(DatItemField.Part_Interface); + } + else + { + if (string.IsNullOrWhiteSpace(rom.Part.Name)) + missingFields.Add(DatItemField.Part_Name); + if (string.IsNullOrWhiteSpace(rom.Part.Interface)) + missingFields.Add(DatItemField.Part_Interface); + } + if (!rom.DataAreaSpecified) + { + missingFields.Add(DatItemField.AreaName); + missingFields.Add(DatItemField.AreaSize); + } + else + { + if (string.IsNullOrWhiteSpace(rom.DataArea.Name)) + missingFields.Add(DatItemField.AreaName); + if (!rom.DataArea.SizeSpecified) + missingFields.Add(DatItemField.AreaSize); + } + break; + case ItemType.SharedFeature: + SharedFeature sharedFeature = datItem as SharedFeature; + if (string.IsNullOrWhiteSpace(sharedFeature.Name)) + missingFields.Add(DatItemField.Name); + break; + default: + // Unsupported ItemTypes should be caught already + return null; + } + + return missingFields; + } + + /// + public override bool WriteToFile(string outfile, bool ignoreblanks = false, bool throwOnError = false) + { + try + { + logger.User($"Writing to '{outfile}'..."); + FileStream fs = System.IO.File.Create(outfile); + + // If we get back null for some reason, just log and return + if (fs == null) + { + logger.Warning($"File '{outfile}' could not be created for writing! Please check to see if the file is writable"); + return false; + } + + XmlTextWriter xtw = new(fs, new UTF8Encoding(false)) + { + Formatting = Formatting.Indented, + IndentChar = '\t', + Indentation = 1 + }; + + // Write out the header + WriteHeader(xtw); + + // Write out each of the machines and roms + string lastgame = null; + + // Use a sorted list of games to output + foreach (string key in Items.SortedKeys) + { + ConcurrentList datItems = Items.FilteredItems(key); + + // If this machine doesn't contain any writable items, skip + if (!ContainsWritable(datItems)) + continue; + + // Resolve the names in the block + datItems = DatItem.ResolveNames(datItems); + + for (int index = 0; index < datItems.Count; index++) + { + DatItem datItem = datItems[index]; + + // If we have a different game and we're not at the start of the list, output the end of last item + if (lastgame != null && lastgame.ToLowerInvariant() != datItem.Machine.Name.ToLowerInvariant()) + WriteEndGame(xtw); + + // If we have a new game, output the beginning of the new item + if (lastgame == null || lastgame.ToLowerInvariant() != datItem.Machine.Name.ToLowerInvariant()) + WriteStartGame(xtw, datItem); + + // Check for a "null" item + datItem = ProcessNullifiedItem(datItem); + + // Write out the item if we're not ignoring + if (!ShouldIgnore(datItem, ignoreblanks)) + WriteDatItem(xtw, datItem); + + // Set the new data to compare against + lastgame = datItem.Machine.Name; + } + } + + // Write the file footer out + WriteFooter(xtw); + + logger.User($"'{outfile}' written!{Environment.NewLine}"); + xtw.Dispose(); + fs.Dispose(); + } + catch (Exception ex) when (!throwOnError) + { + logger.Error(ex); + return false; + } + + return true; + } + + /// + /// Write out DAT header using the supplied StreamWriter + /// + /// XmlTextWriter to output to + private void WriteHeader(XmlTextWriter xtw) + { + xtw.WriteStartDocument(); + xtw.WriteDocType("softwarelist", null, "softwarelist.dtd", null); + + xtw.WriteStartElement("softwarelist"); + xtw.WriteRequiredAttributeString("name", Header.Name); + xtw.WriteRequiredAttributeString("description", Header.Description); + + xtw.Flush(); + } + + /// + /// Write out Game start using the supplied StreamWriter + /// + /// XmlTextWriter to output to + /// DatItem object to be output + private void WriteStartGame(XmlTextWriter xtw, DatItem datItem) + { + // No game should start with a path separator + datItem.Machine.Name = datItem.Machine.Name.TrimStart(Path.DirectorySeparatorChar); + + // Build the state + xtw.WriteStartElement("software"); + xtw.WriteRequiredAttributeString("name", datItem.Machine.Name); + if (!string.Equals(datItem.Machine.Name, datItem.Machine.CloneOf, StringComparison.OrdinalIgnoreCase)) + xtw.WriteOptionalAttributeString("cloneof", datItem.Machine.CloneOf); + xtw.WriteOptionalAttributeString("supported", datItem.Machine.Supported.FromSupported(false)); + + xtw.WriteOptionalElementString("description", datItem.Machine.Description); + xtw.WriteOptionalElementString("year", datItem.Machine.Year); + xtw.WriteOptionalElementString("publisher", datItem.Machine.Publisher); + + xtw.Flush(); + } + + /// + /// Write out Game start using the supplied StreamWriter + /// + /// XmlTextWriter to output to + private void WriteEndGame(XmlTextWriter xtw) + { + // End software + xtw.WriteEndElement(); + + xtw.Flush(); + } + + /// + /// Write out DatItem using the supplied StreamWriter + /// + /// XmlTextWriter to output to + /// DatItem object to be output + private void WriteDatItem(XmlTextWriter xtw, DatItem datItem) + { + // Pre-process the item name + ProcessItemName(datItem, true); + + // Build the state + switch (datItem.ItemType) + { + case ItemType.DipSwitch: + var dipSwitch = datItem as DipSwitch; + xtw.WriteStartElement("dipswitch"); + xtw.WriteRequiredAttributeString("name", dipSwitch.Name); + xtw.WriteRequiredAttributeString("tag", dipSwitch.Tag); + xtw.WriteRequiredAttributeString("mask", dipSwitch.Mask); + if (dipSwitch.ValuesSpecified) + { + foreach (Setting dipValue in dipSwitch.Values) + { + xtw.WriteStartElement("dipvalue"); + xtw.WriteRequiredAttributeString("name", dipValue.Name); + xtw.WriteRequiredAttributeString("value", dipValue.Value); + xtw.WriteOptionalAttributeString("default", dipValue.Default.FromYesNo()); + xtw.WriteEndElement(); + } + } + xtw.WriteEndElement(); + break; + + case ItemType.Disk: + var disk = datItem as Disk; + string diskAreaName = disk.DiskArea?.Name; + if (string.IsNullOrWhiteSpace(diskAreaName)) + diskAreaName = "cdrom"; + + xtw.WriteStartElement("part"); + xtw.WriteRequiredAttributeString("name", disk.Part?.Name); + xtw.WriteRequiredAttributeString("interface", disk.Part?.Interface); + + if (disk.Part?.FeaturesSpecified == true) + { + foreach (PartFeature partFeature in disk.Part.Features) + { + xtw.WriteStartElement("feature"); + xtw.WriteRequiredAttributeString("name", partFeature.Name); + xtw.WriteOptionalAttributeString("value", partFeature.Value); + xtw.WriteEndElement(); + } + } + + xtw.WriteStartElement("diskarea"); + xtw.WriteRequiredAttributeString("name", diskAreaName); + + xtw.WriteStartElement("disk"); + xtw.WriteRequiredAttributeString("name", disk.Name); + xtw.WriteOptionalAttributeString("md5", disk.MD5?.ToLowerInvariant()); + xtw.WriteOptionalAttributeString("sha1", disk.SHA1?.ToLowerInvariant()); + if (disk.ItemStatus != ItemStatus.None) + xtw.WriteOptionalAttributeString("status", disk.ItemStatus.FromItemStatus(false)); + xtw.WriteOptionalAttributeString("writable", disk.Writable.FromYesNo()); + xtw.WriteEndElement(); + + // End diskarea + xtw.WriteEndElement(); + + // End part + xtw.WriteEndElement(); + break; + + case ItemType.Info: + var info = datItem as Info; + xtw.WriteStartElement("info"); + xtw.WriteRequiredAttributeString("name", info.Name); + xtw.WriteOptionalAttributeString("value", info.Value); + xtw.WriteEndElement(); + break; + + case ItemType.Rom: + var rom = datItem as Rom; + string dataAreaName = rom.DataArea?.Name; + if (string.IsNullOrWhiteSpace(dataAreaName)) + dataAreaName = "rom"; + + xtw.WriteStartElement("part"); + xtw.WriteRequiredAttributeString("name", rom.Part?.Name); + xtw.WriteRequiredAttributeString("interface", rom.Part?.Interface); + + if (rom.Part?.FeaturesSpecified == true) + { + foreach (PartFeature kvp in rom.Part.Features) + { + xtw.WriteStartElement("feature"); + xtw.WriteRequiredAttributeString("name", kvp.Name); + xtw.WriteOptionalAttributeString("value", kvp.Value); + xtw.WriteEndElement(); + } + } + + xtw.WriteStartElement("dataarea"); + xtw.WriteOptionalAttributeString("name", dataAreaName); + xtw.WriteOptionalAttributeString("size", rom.DataArea?.Size.ToString()); + xtw.WriteOptionalAttributeString("width", rom.DataArea?.Width?.ToString()); + xtw.WriteOptionalAttributeString("endianness", rom.DataArea?.Endianness.FromEndianness()); + + xtw.WriteStartElement("rom"); + xtw.WriteRequiredAttributeString("name", rom.Name); + xtw.WriteOptionalAttributeString("size", rom.Size?.ToString()); + xtw.WriteOptionalAttributeString("crc", rom.CRC?.ToLowerInvariant()); + xtw.WriteOptionalAttributeString("md5", rom.MD5?.ToLowerInvariant()); + xtw.WriteOptionalAttributeString("sha1", rom.SHA1?.ToLowerInvariant()); + xtw.WriteOptionalAttributeString("sha256", rom.SHA256?.ToLowerInvariant()); + xtw.WriteOptionalAttributeString("sha384", rom.SHA384?.ToLowerInvariant()); + xtw.WriteOptionalAttributeString("sha512", rom.SHA512?.ToLowerInvariant()); + xtw.WriteOptionalAttributeString("offset", rom.Offset); + xtw.WriteOptionalAttributeString("value", rom.Value); + if (rom.ItemStatus != ItemStatus.None) + xtw.WriteOptionalAttributeString("status", rom.ItemStatus.FromItemStatus(false)); + xtw.WriteOptionalAttributeString("loadflag", rom.LoadFlag.FromLoadFlag()); + xtw.WriteEndElement(); + + // End dataarea + xtw.WriteEndElement(); + + // End part + xtw.WriteEndElement(); + break; + + case ItemType.SharedFeature: + var sharedFeature = datItem as SharedFeature; + xtw.WriteStartElement("sharedfeat"); + xtw.WriteRequiredAttributeString("name", sharedFeature.Name); + xtw.WriteOptionalAttributeString("value", sharedFeature.Value); + xtw.WriteEndElement(); + break; + } + + xtw.Flush(); + } + + /// + /// Write out DAT footer using the supplied StreamWriter + /// + /// XmlTextWriter to output to + private void WriteFooter(XmlTextWriter xtw) + { + // End software + xtw.WriteEndElement(); + + // End softwarelist + xtw.WriteEndElement(); + + xtw.Flush(); + } + } +} diff --git a/SabreTools.DatFiles/Formats/SoftwareList.cs b/SabreTools.DatFiles/Formats/SoftwareList.cs index 949663a0..a984bae4 100644 --- a/SabreTools.DatFiles/Formats/SoftwareList.cs +++ b/SabreTools.DatFiles/Formats/SoftwareList.cs @@ -1,17 +1,4 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text; -using System.Xml; -using System.Xml.Schema; -using SabreTools.Core; -using SabreTools.Core.Tools; -using SabreTools.DatItems; -using SabreTools.DatItems.Formats; -using SabreTools.IO; - -// TODO: Use softwarelist.dtd and *try* to make this write more correctly +// TODO: Use softwarelist.dtd and *try* to make this write more correctly namespace SabreTools.DatFiles.Formats { /// @@ -20,7 +7,7 @@ namespace SabreTools.DatFiles.Formats /// /// TODO: Check and enforce required fields in output /// - internal class SoftwareList : DatFile + internal partial class SoftwareList : DatFile { /// /// DTD for original MAME Software List DATs @@ -91,893 +78,5 @@ namespace SabreTools.DatFiles.Formats : base(datFile) { } - - /// - public override void ParseFile(string filename, int indexId, bool keep, bool statsOnly = false, bool throwOnError = false) - { - // Prepare all internal variables - XmlReader xtr = XmlReader.Create(filename, new XmlReaderSettings - { - CheckCharacters = false, - DtdProcessing = DtdProcessing.Ignore, - IgnoreComments = true, - IgnoreWhitespace = true, - ValidationFlags = XmlSchemaValidationFlags.None, - ValidationType = ValidationType.None, - }); - - // If we got a null reader, just return - if (xtr == null) - return; - - // Otherwise, read the file to the end - try - { - xtr.MoveToContent(); - while (!xtr.EOF) - { - // We only want elements - if (xtr.NodeType != XmlNodeType.Element) - { - xtr.Read(); - continue; - } - - switch (xtr.Name) - { - case "softwarelist": - Header.Name ??= xtr.GetAttribute("name") ?? string.Empty; - Header.Description ??= xtr.GetAttribute("description") ?? string.Empty; - - xtr.Read(); - break; - - // We want to process the entire subtree of the machine - case "software": - ReadSoftware(xtr.ReadSubtree(), statsOnly, filename, indexId); - - // Skip the software now that we've processed it - xtr.Skip(); - break; - - default: - xtr.Read(); - break; - } - } - } - catch (Exception ex) when (!throwOnError) - { - logger.Warning(ex, $"Exception found while parsing '{filename}'"); - - // For XML errors, just skip the affected node - xtr?.Read(); - } - - xtr.Dispose(); - } - - /// - /// Read software information - /// - /// XmlReader representing a software block - /// True to only add item statistics while parsing, false otherwise - /// Name of the file to be parsed - /// Index ID for the DAT - private void ReadSoftware(XmlReader reader, bool statsOnly, string filename, int indexId) - { - // If we have an empty software, skip it - if (reader == null) - return; - - // Otherwise, add what is possible - reader.MoveToContent(); - - bool containsItems = false; - - // Create a new machine - Machine machine = new() - { - Name = reader.GetAttribute("name"), - CloneOf = reader.GetAttribute("cloneof"), - Supported = reader.GetAttribute("supported").AsSupported(), - }; - - while (!reader.EOF) - { - // We only want elements - if (reader.NodeType != XmlNodeType.Element) - { - reader.Read(); - continue; - } - - // Get the elements from the software - switch (reader.Name) - { - case "description": - machine.Description = reader.ReadElementContentAsString(); - break; - - case "year": - machine.Year = reader.ReadElementContentAsString(); - break; - - case "publisher": - machine.Publisher = reader.ReadElementContentAsString(); - break; - - case "info": - ParseAddHelper(new Info - { - Name = reader.GetAttribute("name"), - Value = reader.GetAttribute("value"), - - Source = new Source - { - Index = indexId, - Name = filename, - }, - }, statsOnly); - - reader.Read(); - break; - - case "sharedfeat": - ParseAddHelper(new SharedFeature - { - Name = reader.GetAttribute("name"), - Value = reader.GetAttribute("value"), - - Source = new Source - { - Index = indexId, - Name = filename, - }, - }, statsOnly); - - reader.Read(); - break; - - case "part": // Contains all rom and disk information - var part = new Part() - { - Name = reader.GetAttribute("name"), - Interface = reader.GetAttribute("interface"), - }; - - // Now read the internal tags - containsItems = ReadPart(reader.ReadSubtree(), machine, part, statsOnly, filename, indexId); - - // Skip the part now that we've processed it - reader.Skip(); - break; - - default: - reader.Read(); - break; - } - } - - // If no items were found for this machine, add a Blank placeholder - if (!containsItems) - { - Blank blank = new() - { - Source = new Source - { - Index = indexId, - Name = filename, - }, - }; - - blank.CopyMachineInformation(machine); - - // Now process and add the rom - ParseAddHelper(blank, statsOnly); - } - } - - /// - /// Read part information - /// - /// XmlReader representing a part block - /// Machine information to pass to contained items - /// Part information to pass to contained items - /// True to only add item statistics while parsing, false otherwise - /// Name of the file to be parsed - /// Index ID for the DAT - private bool ReadPart(XmlReader reader, Machine machine, Part part, bool statsOnly, string filename, int indexId) - { - // If we have an empty port, skip it - if (reader == null) - return false; - - // Get lists ready - part.Features = new List(); - List items = new(); - - while (!reader.EOF) - { - // We only want elements - if (reader.NodeType != XmlNodeType.Element) - { - reader.Read(); - continue; - } - - // Get the elements from the software - switch (reader.Name) - { - case "feature": - var feature = new PartFeature() - { - Name = reader.GetAttribute("name"), - Value = reader.GetAttribute("value"), - }; - - part.Features.Add(feature); - - reader.Read(); - break; - - case "dataarea": - var dataArea = new DataArea - { - Name = reader.GetAttribute("name"), - Size = Utilities.CleanLong(reader.GetAttribute("size")), - Width = Utilities.CleanLong(reader.GetAttribute("width")), - Endianness = reader.GetAttribute("endianness").AsEndianness(), - }; - - List roms = ReadDataArea(reader.ReadSubtree(), dataArea); - - // If we got valid roms, add them to the list - if (roms != null) - items.AddRange(roms); - - // Skip the dataarea now that we've processed it - reader.Skip(); - break; - - case "diskarea": - var diskArea = new DiskArea - { - Name = reader.GetAttribute("name"), - }; - - List disks = ReadDiskArea(reader.ReadSubtree(), diskArea); - - // If we got valid disks, add them to the list - if (disks != null) - items.AddRange(disks); - - // Skip the diskarea now that we've processed it - reader.Skip(); - break; - - case "dipswitch": - var dipSwitch = new DipSwitch - { - Name = reader.GetAttribute("name"), - Tag = reader.GetAttribute("tag"), - Mask = reader.GetAttribute("mask"), - }; - - // Now read the internal tags - ReadDipSwitch(reader.ReadSubtree(), dipSwitch); - - items.Add(dipSwitch); - - // Skip the dipswitch now that we've processed it - reader.Skip(); - break; - - default: - reader.Read(); - break; - } - } - - // Loop over all of the items, if they exist - string key = string.Empty; - foreach (DatItem item in items) - { - // Add all missing information - switch (item.ItemType) - { - case ItemType.DipSwitch: - (item as DipSwitch).Part = part; - break; - case ItemType.Disk: - (item as Disk).Part = part; - break; - case ItemType.Rom: - (item as Rom).Part = part; - - // If the rom is continue or ignore, add the size to the previous rom - // TODO: Can this be done on write? We technically lose information this way. - // Order is not guaranteed, and since these don't tend to have any way - // of determining what the "previous" item was after this, that info would - // have to be stored *with* the item somehow - if ((item as Rom).LoadFlag == LoadFlag.Continue || (item as Rom).LoadFlag == LoadFlag.Ignore) - { - int index = Items[key].Count - 1; - DatItem lastrom = Items[key][index]; - if (lastrom.ItemType == ItemType.Rom) - { - (lastrom as Rom).Size += (item as Rom).Size; - Items[key].RemoveAt(index); - Items[key].Add(lastrom); - } - - continue; - } - - break; - } - - item.Source = new Source(indexId, filename); - item.CopyMachineInformation(machine); - - // Finally add each item - key = ParseAddHelper(item, statsOnly); - } - - return items.Any(); - } - - /// - /// Read dataarea information - /// - /// XmlReader representing a dataarea block - /// DataArea representing the enclosing area - private List ReadDataArea(XmlReader reader, DataArea dataArea) - { - List items = new(); - - while (!reader.EOF) - { - // We only want elements - if (reader.NodeType != XmlNodeType.Element) - { - reader.Read(); - continue; - } - - // Get the elements from the software - switch (reader.Name) - { - case "rom": - var rom = new Rom - { - Name = reader.GetAttribute("name"), - Size = Utilities.CleanLong(reader.GetAttribute("size")), - CRC = reader.GetAttribute("crc"), - SHA1 = reader.GetAttribute("sha1"), - Offset = reader.GetAttribute("offset"), - Value = reader.GetAttribute("value"), - ItemStatus = reader.GetAttribute("status").AsItemStatus(), - LoadFlag = reader.GetAttribute("loadflag").AsLoadFlag(), - - DataArea = dataArea, - }; - - items.Add(rom); - reader.Read(); - break; - - default: - reader.Read(); - break; - } - } - - return items; - } - - /// - /// Read diskarea information - /// - /// XmlReader representing a diskarea block - /// DiskArea representing the enclosing area - private List ReadDiskArea(XmlReader reader, DiskArea diskArea) - { - List items = new(); - - while (!reader.EOF) - { - // We only want elements - if (reader.NodeType != XmlNodeType.Element) - { - reader.Read(); - continue; - } - - // Get the elements from the software - switch (reader.Name) - { - case "disk": - DatItem disk = new Disk - { - Name = reader.GetAttribute("name"), - SHA1 = reader.GetAttribute("sha1"), - ItemStatus = reader.GetAttribute("status").AsItemStatus(), - Writable = reader.GetAttribute("writable").AsYesNo(), - - DiskArea = diskArea, - }; - - items.Add(disk); - reader.Read(); - break; - - default: - reader.Read(); - break; - } - } - - return items; - } - - /// - /// Read DipSwitch DipValues information - /// - /// XmlReader representing a diskarea block - /// DipSwitch to populate - private void ReadDipSwitch(XmlReader reader, DipSwitch dipSwitch) - { - // If we have an empty dipswitch, skip it - if (reader == null) - return; - - // Get list ready - dipSwitch.Values = new List(); - - // Otherwise, add what is possible - reader.MoveToContent(); - - while (!reader.EOF) - { - // We only want elements - if (reader.NodeType != XmlNodeType.Element) - { - reader.Read(); - continue; - } - - // Get the information from the dipswitch - switch (reader.Name) - { - case "dipvalue": - var dipValue = new Setting - { - Name = reader.GetAttribute("name"), - Value = reader.GetAttribute("value"), - Default = reader.GetAttribute("default").AsYesNo(), - }; - - dipSwitch.Values.Add(dipValue); - - reader.Read(); - break; - - default: - reader.Read(); - break; - } - } - } - - /// - protected override ItemType[] GetSupportedTypes() - { - return new ItemType[] - { - ItemType.DipSwitch, - ItemType.Disk, - ItemType.Info, - ItemType.Rom, - ItemType.SharedFeature, - }; - } - - /// - protected override List GetMissingRequiredFields(DatItem datItem) - { - List missingFields = new(); - - switch (datItem.ItemType) - { - case ItemType.DipSwitch: - DipSwitch dipSwitch = datItem as DipSwitch; - if (!dipSwitch.PartSpecified) - { - missingFields.Add(DatItemField.Part_Name); - missingFields.Add(DatItemField.Part_Interface); - } - else - { - if (string.IsNullOrWhiteSpace(dipSwitch.Part.Name)) - missingFields.Add(DatItemField.Part_Name); - if (string.IsNullOrWhiteSpace(dipSwitch.Part.Interface)) - missingFields.Add(DatItemField.Part_Interface); - } - if (string.IsNullOrWhiteSpace(dipSwitch.Name)) - missingFields.Add(DatItemField.Name); - if (string.IsNullOrWhiteSpace(dipSwitch.Tag)) - missingFields.Add(DatItemField.Tag); - if (string.IsNullOrWhiteSpace(dipSwitch.Mask)) - missingFields.Add(DatItemField.Mask); - if (dipSwitch.ValuesSpecified) - { - if (dipSwitch.Values.Any(dv => string.IsNullOrWhiteSpace(dv.Name))) - missingFields.Add(DatItemField.Part_Feature_Name); - if (dipSwitch.Values.Any(dv => string.IsNullOrWhiteSpace(dv.Value))) - missingFields.Add(DatItemField.Part_Feature_Value); - } - - break; - case ItemType.Disk: - Disk disk = datItem as Disk; - if (!disk.PartSpecified) - { - missingFields.Add(DatItemField.Part_Name); - missingFields.Add(DatItemField.Part_Interface); - } - else - { - if (string.IsNullOrWhiteSpace(disk.Part.Name)) - missingFields.Add(DatItemField.Part_Name); - if (string.IsNullOrWhiteSpace(disk.Part.Interface)) - missingFields.Add(DatItemField.Part_Interface); - } - if (!disk.DiskAreaSpecified) - { - missingFields.Add(DatItemField.AreaName); - } - else - { - if (string.IsNullOrWhiteSpace(disk.DiskArea.Name)) - missingFields.Add(DatItemField.AreaName); - } - if (string.IsNullOrWhiteSpace(disk.Name)) - missingFields.Add(DatItemField.Name); - break; - case ItemType.Info: - Info info = datItem as Info; - if (string.IsNullOrWhiteSpace(info.Name)) - missingFields.Add(DatItemField.Name); - break; - case ItemType.Rom: - Rom rom = datItem as Rom; - if (!rom.PartSpecified) - { - missingFields.Add(DatItemField.Part_Name); - missingFields.Add(DatItemField.Part_Interface); - } - else - { - if (string.IsNullOrWhiteSpace(rom.Part.Name)) - missingFields.Add(DatItemField.Part_Name); - if (string.IsNullOrWhiteSpace(rom.Part.Interface)) - missingFields.Add(DatItemField.Part_Interface); - } - if (!rom.DataAreaSpecified) - { - missingFields.Add(DatItemField.AreaName); - missingFields.Add(DatItemField.AreaSize); - } - else - { - if (string.IsNullOrWhiteSpace(rom.DataArea.Name)) - missingFields.Add(DatItemField.AreaName); - if (!rom.DataArea.SizeSpecified) - missingFields.Add(DatItemField.AreaSize); - } - break; - case ItemType.SharedFeature: - SharedFeature sharedFeature = datItem as SharedFeature; - if (string.IsNullOrWhiteSpace(sharedFeature.Name)) - missingFields.Add(DatItemField.Name); - break; - default: - // Unsupported ItemTypes should be caught already - return null; - } - - return missingFields; - } - - /// - public override bool WriteToFile(string outfile, bool ignoreblanks = false, bool throwOnError = false) - { - try - { - logger.User($"Writing to '{outfile}'..."); - FileStream fs = System.IO.File.Create(outfile); - - // If we get back null for some reason, just log and return - if (fs == null) - { - logger.Warning($"File '{outfile}' could not be created for writing! Please check to see if the file is writable"); - return false; - } - - XmlTextWriter xtw = new(fs, new UTF8Encoding(false)) - { - Formatting = Formatting.Indented, - IndentChar = '\t', - Indentation = 1 - }; - - // Write out the header - WriteHeader(xtw); - - // Write out each of the machines and roms - string lastgame = null; - - // Use a sorted list of games to output - foreach (string key in Items.SortedKeys) - { - ConcurrentList datItems = Items.FilteredItems(key); - - // If this machine doesn't contain any writable items, skip - if (!ContainsWritable(datItems)) - continue; - - // Resolve the names in the block - datItems = DatItem.ResolveNames(datItems); - - for (int index = 0; index < datItems.Count; index++) - { - DatItem datItem = datItems[index]; - - // If we have a different game and we're not at the start of the list, output the end of last item - if (lastgame != null && lastgame.ToLowerInvariant() != datItem.Machine.Name.ToLowerInvariant()) - WriteEndGame(xtw); - - // If we have a new game, output the beginning of the new item - if (lastgame == null || lastgame.ToLowerInvariant() != datItem.Machine.Name.ToLowerInvariant()) - WriteStartGame(xtw, datItem); - - // Check for a "null" item - datItem = ProcessNullifiedItem(datItem); - - // Write out the item if we're not ignoring - if (!ShouldIgnore(datItem, ignoreblanks)) - WriteDatItem(xtw, datItem); - - // Set the new data to compare against - lastgame = datItem.Machine.Name; - } - } - - // Write the file footer out - WriteFooter(xtw); - - logger.User($"'{outfile}' written!{Environment.NewLine}"); - xtw.Dispose(); - fs.Dispose(); - } - catch (Exception ex) when (!throwOnError) - { - logger.Error(ex); - return false; - } - - return true; - } - - /// - /// Write out DAT header using the supplied StreamWriter - /// - /// XmlTextWriter to output to - private void WriteHeader(XmlTextWriter xtw) - { - xtw.WriteStartDocument(); - xtw.WriteDocType("softwarelist", null, "softwarelist.dtd", null); - - xtw.WriteStartElement("softwarelist"); - xtw.WriteRequiredAttributeString("name", Header.Name); - xtw.WriteRequiredAttributeString("description", Header.Description); - - xtw.Flush(); - } - - /// - /// Write out Game start using the supplied StreamWriter - /// - /// XmlTextWriter to output to - /// DatItem object to be output - private void WriteStartGame(XmlTextWriter xtw, DatItem datItem) - { - // No game should start with a path separator - datItem.Machine.Name = datItem.Machine.Name.TrimStart(Path.DirectorySeparatorChar); - - // Build the state - xtw.WriteStartElement("software"); - xtw.WriteRequiredAttributeString("name", datItem.Machine.Name); - if (!string.Equals(datItem.Machine.Name, datItem.Machine.CloneOf, StringComparison.OrdinalIgnoreCase)) - xtw.WriteOptionalAttributeString("cloneof", datItem.Machine.CloneOf); - xtw.WriteOptionalAttributeString("supported", datItem.Machine.Supported.FromSupported(false)); - - xtw.WriteOptionalElementString("description", datItem.Machine.Description); - xtw.WriteOptionalElementString("year", datItem.Machine.Year); - xtw.WriteOptionalElementString("publisher", datItem.Machine.Publisher); - - xtw.Flush(); - } - - /// - /// Write out Game start using the supplied StreamWriter - /// - /// XmlTextWriter to output to - private void WriteEndGame(XmlTextWriter xtw) - { - // End software - xtw.WriteEndElement(); - - xtw.Flush(); - } - - /// - /// Write out DatItem using the supplied StreamWriter - /// - /// XmlTextWriter to output to - /// DatItem object to be output - private void WriteDatItem(XmlTextWriter xtw, DatItem datItem) - { - // Pre-process the item name - ProcessItemName(datItem, true); - - // Build the state - switch (datItem.ItemType) - { - case ItemType.DipSwitch: - var dipSwitch = datItem as DipSwitch; - xtw.WriteStartElement("dipswitch"); - xtw.WriteRequiredAttributeString("name", dipSwitch.Name); - xtw.WriteRequiredAttributeString("tag", dipSwitch.Tag); - xtw.WriteRequiredAttributeString("mask", dipSwitch.Mask); - if (dipSwitch.ValuesSpecified) - { - foreach (Setting dipValue in dipSwitch.Values) - { - xtw.WriteStartElement("dipvalue"); - xtw.WriteRequiredAttributeString("name", dipValue.Name); - xtw.WriteRequiredAttributeString("value", dipValue.Value); - xtw.WriteOptionalAttributeString("default", dipValue.Default.FromYesNo()); - xtw.WriteEndElement(); - } - } - xtw.WriteEndElement(); - break; - - case ItemType.Disk: - var disk = datItem as Disk; - string diskAreaName = disk.DiskArea?.Name; - if (string.IsNullOrWhiteSpace(diskAreaName)) - diskAreaName = "cdrom"; - - xtw.WriteStartElement("part"); - xtw.WriteRequiredAttributeString("name", disk.Part?.Name); - xtw.WriteRequiredAttributeString("interface", disk.Part?.Interface); - - if (disk.Part?.FeaturesSpecified == true) - { - foreach (PartFeature partFeature in disk.Part.Features) - { - xtw.WriteStartElement("feature"); - xtw.WriteRequiredAttributeString("name", partFeature.Name); - xtw.WriteOptionalAttributeString("value", partFeature.Value); - xtw.WriteEndElement(); - } - } - - xtw.WriteStartElement("diskarea"); - xtw.WriteRequiredAttributeString("name", diskAreaName); - - xtw.WriteStartElement("disk"); - xtw.WriteRequiredAttributeString("name", disk.Name); - xtw.WriteOptionalAttributeString("md5", disk.MD5?.ToLowerInvariant()); - xtw.WriteOptionalAttributeString("sha1", disk.SHA1?.ToLowerInvariant()); - if (disk.ItemStatus != ItemStatus.None) - xtw.WriteOptionalAttributeString("status", disk.ItemStatus.FromItemStatus(false)); - xtw.WriteOptionalAttributeString("writable", disk.Writable.FromYesNo()); - xtw.WriteEndElement(); - - // End diskarea - xtw.WriteEndElement(); - - // End part - xtw.WriteEndElement(); - break; - - case ItemType.Info: - var info = datItem as Info; - xtw.WriteStartElement("info"); - xtw.WriteRequiredAttributeString("name", info.Name); - xtw.WriteOptionalAttributeString("value", info.Value); - xtw.WriteEndElement(); - break; - - case ItemType.Rom: - var rom = datItem as Rom; - string dataAreaName = rom.DataArea?.Name; - if (string.IsNullOrWhiteSpace(dataAreaName)) - dataAreaName = "rom"; - - xtw.WriteStartElement("part"); - xtw.WriteRequiredAttributeString("name", rom.Part?.Name); - xtw.WriteRequiredAttributeString("interface", rom.Part?.Interface); - - if (rom.Part?.FeaturesSpecified == true) - { - foreach (PartFeature kvp in rom.Part.Features) - { - xtw.WriteStartElement("feature"); - xtw.WriteRequiredAttributeString("name", kvp.Name); - xtw.WriteOptionalAttributeString("value", kvp.Value); - xtw.WriteEndElement(); - } - } - - xtw.WriteStartElement("dataarea"); - xtw.WriteOptionalAttributeString("name", dataAreaName); - xtw.WriteOptionalAttributeString("size", rom.DataArea?.Size.ToString()); - xtw.WriteOptionalAttributeString("width", rom.DataArea?.Width?.ToString()); - xtw.WriteOptionalAttributeString("endianness", rom.DataArea?.Endianness.FromEndianness()); - - xtw.WriteStartElement("rom"); - xtw.WriteRequiredAttributeString("name", rom.Name); - xtw.WriteOptionalAttributeString("size", rom.Size?.ToString()); - xtw.WriteOptionalAttributeString("crc", rom.CRC?.ToLowerInvariant()); - xtw.WriteOptionalAttributeString("md5", rom.MD5?.ToLowerInvariant()); - xtw.WriteOptionalAttributeString("sha1", rom.SHA1?.ToLowerInvariant()); - xtw.WriteOptionalAttributeString("sha256", rom.SHA256?.ToLowerInvariant()); - xtw.WriteOptionalAttributeString("sha384", rom.SHA384?.ToLowerInvariant()); - xtw.WriteOptionalAttributeString("sha512", rom.SHA512?.ToLowerInvariant()); - xtw.WriteOptionalAttributeString("offset", rom.Offset); - xtw.WriteOptionalAttributeString("value", rom.Value); - if (rom.ItemStatus != ItemStatus.None) - xtw.WriteOptionalAttributeString("status", rom.ItemStatus.FromItemStatus(false)); - xtw.WriteOptionalAttributeString("loadflag", rom.LoadFlag.FromLoadFlag()); - xtw.WriteEndElement(); - - // End dataarea - xtw.WriteEndElement(); - - // End part - xtw.WriteEndElement(); - break; - - case ItemType.SharedFeature: - var sharedFeature = datItem as SharedFeature; - xtw.WriteStartElement("sharedfeat"); - xtw.WriteRequiredAttributeString("name", sharedFeature.Name); - xtw.WriteOptionalAttributeString("value", sharedFeature.Value); - xtw.WriteEndElement(); - break; - } - - xtw.Flush(); - } - - /// - /// Write out DAT footer using the supplied StreamWriter - /// - /// XmlTextWriter to output to - private void WriteFooter(XmlTextWriter xtw) - { - // End software - xtw.WriteEndElement(); - - // End softwarelist - xtw.WriteEndElement(); - - xtw.Flush(); - } } }