using System; using System.Collections.Generic; using System.IO; using System.Text; using System.Xml; using System.Xml.Schema; using System.Xml.Serialization; using SabreTools.DatItems; namespace SabreTools.DatFiles.Formats { /// /// Represents parsing and writing of a SabreDAT XML /// internal class SabreXML : DatFile { /// /// Constructor designed for casting a base DatFile /// /// Parent DatFile to copy from public SabreXML(DatFile datFile) : base(datFile) { } /// public override void ParseFile(string filename, int indexId, bool keep, 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 "header": XmlSerializer xs = new XmlSerializer(typeof(DatHeader)); DatHeader header = xs.Deserialize(xtr.ReadSubtree()) as DatHeader; Header.ConditionalCopy(header); xtr.Skip(); break; case "directory": ReadDirectory(xtr.ReadSubtree(), filename, indexId); // Skip the directory node now that we've processed it xtr.Read(); break; default: xtr.Read(); break; } } } catch (Exception ex) { logger.Warning(ex, $"Exception found while parsing '{filename}'"); if (throwOnError) { xtr.Dispose(); throw ex; } // For XML errors, just skip the affected node xtr?.Read(); } xtr.Dispose(); } /// /// Read directory information /// /// XmlReader to use to parse the header /// Name of the file to be parsed /// Index ID for the DAT private void ReadDirectory(XmlReader xtr, string filename, int indexId) { // If the reader is invalid, skip if (xtr == null) return; // Prepare internal variables Machine machine = null; // Otherwise, read the directory xtr.MoveToContent(); while (!xtr.EOF) { // We only want elements if (xtr.NodeType != XmlNodeType.Element) { xtr.Read(); continue; } switch (xtr.Name) { case "machine": XmlSerializer xs = new XmlSerializer(typeof(Machine)); machine = xs.Deserialize(xtr.ReadSubtree()) as Machine; xtr.Skip(); break; case "files": ReadFiles(xtr.ReadSubtree(), machine, filename, indexId); // Skip the directory node now that we've processed it xtr.Read(); break; default: xtr.Read(); break; } } } /// /// Read Files information /// /// XmlReader to use to parse the header /// Machine to copy information from /// Name of the file to be parsed /// Index ID for the DAT private void ReadFiles(XmlReader xtr, Machine machine, string filename, int indexId) { // If the reader is invalid, skip if (xtr == null) return; // Otherwise, read the items xtr.MoveToContent(); while (!xtr.EOF) { // We only want elements if (xtr.NodeType != XmlNodeType.Element) { xtr.Read(); continue; } switch (xtr.Name) { case "datitem": XmlSerializer xs = new XmlSerializer(typeof(DatItem)); DatItem item = xs.Deserialize(xtr.ReadSubtree()) as DatItem; item.CopyMachineInformation(machine); item.Source = new Source { Name = filename, Index = indexId }; ParseAddHelper(item); xtr.Skip(); break; default: xtr.Read(); break; } } } /// public override bool WriteToFile(string outfile, bool ignoreblanks = false, bool throwOnError = false) { try { logger.User($"Opening file for writing: {outfile}"); FileStream fs = 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 XmlTextWriter(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) { List 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.Verbose("File written!" + Environment.NewLine); xtw.Dispose(); fs.Dispose(); } catch (Exception ex) { logger.Error(ex); if (throwOnError) throw 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.WriteStartElement("datafile"); XmlSerializer xs = new XmlSerializer(typeof(DatHeader)); XmlSerializerNamespaces ns = new XmlSerializerNamespaces(); ns.Add("", ""); xs.Serialize(xtw, Header, ns); xtw.WriteStartElement("data"); 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) ?? string.Empty; // Write the machine xtw.WriteStartElement("directory"); XmlSerializer xs = new XmlSerializer(typeof(Machine)); XmlSerializerNamespaces ns = new XmlSerializerNamespaces(); ns.Add("", ""); xs.Serialize(xtw, datItem.Machine, ns); xtw.WriteStartElement("files"); xtw.Flush(); } /// /// Write out Game start using the supplied StreamWriter /// /// XmlTextWriter to output to private void WriteEndGame(XmlTextWriter xtw) { // End files xtw.WriteEndElement(); // End directory 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); // Write the DatItem XmlSerializer xs = new XmlSerializer(typeof(DatItem)); XmlSerializerNamespaces ns = new XmlSerializerNamespaces(); ns.Add("", ""); xs.Serialize(xtw, datItem, ns); xtw.Flush(); } /// /// Write out DAT footer using the supplied StreamWriter /// /// XmlTextWriter to output to private void WriteFooter(XmlTextWriter xtw) { // End files xtw.WriteEndElement(); // End directory xtw.WriteEndElement(); // End data xtw.WriteEndElement(); // End datafile xtw.WriteEndElement(); xtw.Flush(); } } }