using System; using System.Collections.Generic; using System.Text; using System.Xml.Schema; using System.IO; using System.Xml; namespace Wayne.Lib.IO { /// /// A class that can be used to convert XML documents that conforms to the FlatFile.xsd into flat text files. /// public class FlatFileFormatter { #region Fields XmlSchemaSet schemaSet; #endregion #region Construction /// /// Initializes a new intance of the Flat file formatter. /// public FlatFileFormatter() { schemaSet = new XmlSchemaSet(); using (var schemaStream = System.Reflection.Assembly.GetExecutingAssembly().GetManifestResourceStream("Wayne.Lib.IO.FlatFileFormatter.FlatFile.xsd")) { XmlSchema schema = XmlSchema.Read(schemaStream, null); schemaSet.Add(schema); schemaSet.Compile(); } } #endregion #region Public methods /// /// Creates a text file from the specified flat file XML document stream. /// /// A stream that contains a Flat file XML document. /// File that should be created by the formatting routine. public void Format(Stream flatFileXmlStream, string fileName) { //Start by validating the input stream, so it is a correct flatfile XML file. ValidateFile(flatFileXmlStream); HandleXml(flatFileXmlStream, fileName); } #endregion #region Private methods private void ValidateFile(Stream flatFileXmlStream) { long startPosition = flatFileXmlStream.Position; XmlReaderSettings validationReaderSettings = new XmlReaderSettings(); validationReaderSettings.Schemas = schemaSet; validationReaderSettings.CloseInput = false; //There is a bug in the compact/full framework boundary that makes, that if we set the validation type to Schema, it is actually set to //'Auto'. Therefore we need to do this very hack and check if we are running compact framework for real or if we are running a CF assembly //in a full-framework environment. if (System.Environment.OSVersion.Platform == PlatformID.WinCE) validationReaderSettings.ValidationType = ValidationType.Schema; else validationReaderSettings.ValidationType = (System.Xml.ValidationType)4; try { using (XmlReader validationReader = XmlReader.Create(flatFileXmlStream, validationReaderSettings)) { while (validationReader.Read()) { } } } catch (XmlSchemaException xmlSchemaException) { throw new FlatFileFormatException("Invalid flat file Xml. Xml schema validation failed.", xmlSchemaException); } flatFileXmlStream.Position = startPosition; } private void HandleXml(Stream flatFileXmlStream, string fileName) { try { string fieldSeparator = ""; string recordSeparator = "\r\n"; using (XmlReader reader = XmlReader.Create(flatFileXmlStream)) { Dictionary predefinedFormats = new Dictionary(); using (Stream outputFile = FileSupport.Open(fileName, FileMode.Create, FileAccess.Write, FileShare.Read, 100, 100)) { while (reader.Read()) { switch (reader.NodeType) { case XmlNodeType.Element: { if (reader.LocalName == "Section") { if (reader.MoveToAttribute("fieldSeparator")) fieldSeparator = reader.Value; if (reader.MoveToAttribute("recordSeparator")) recordSeparator = reader.Value; reader.MoveToElement(); } else if (reader.LocalName == "FormatDefinitions") HandleFormatDefinitions(reader, predefinedFormats); else if (reader.LocalName == "Record") HandleRecord(reader, predefinedFormats, outputFile, fieldSeparator, recordSeparator); break; } case XmlNodeType.EndElement: { if (reader.LocalName == "FlatFile") return; break; } } } } } } catch (Exception exception) { throw new FlatFileFormatException("Exception when formatting flat file.", exception); } } private void HandleFormatDefinitions(XmlReader reader, Dictionary predefinedFormats) { bool moved = false; while (true) { if (!moved) if (!reader.Read()) break; moved = false; switch (reader.NodeType) { case XmlNodeType.Element: { if (reader.LocalName == "Format") { if (reader.MoveToAttribute("formatId")) { string formatId; formatId = reader.Value; reader.MoveToElement(); string format = reader.ReadElementContentAsString(); moved = true; predefinedFormats.Add(formatId, format); } } break; } case XmlNodeType.EndElement: { if (reader.LocalName == "FormatDefinitions") return; break; } } } } #region Inner class Field private class Field { #region Fields object value; string format; #endregion #region Construction public Field(string type, string format, string value) { this.format = format; #region Decode the type & value switch (type) { case "String": { this.value = value; break; } case "Boolean": { this.value = XmlConvert.ToBoolean(value); break; } case "Byte": { this.value = XmlConvert.ToByte(value); break; } case "Int16": { this.value = XmlConvert.ToInt16(value); break; } case "Int32": { this.value = XmlConvert.ToInt32(value); break; } case "Int64": { this.value = XmlConvert.ToInt64(value); break; } case "UInt16": { this.value = XmlConvert.ToUInt64(value); break; } case "UInt32": { this.value = XmlConvert.ToUInt32(value); break; } case "UInt64": { this.value = XmlConvert.ToUInt64(value); break; } case "Decimal": { this.value = XmlConvert.ToDecimal(value); break; } case "Double": { this.value = XmlConvert.ToDouble(value); break; } case "DateTime": { this.value = XmlConvert.ToDateTime(value, XmlDateTimeSerializationMode.Unspecified); break; } } #endregion } #endregion #region Properties public object Value { get { return value; } } public string Format { get { return format; } } #endregion } #endregion private void HandleRecord(XmlReader reader, Dictionary predefinedFormats, Stream outputFile, string fieldSeparator, string recordSeparator) { bool endRecordFound = false; List fieldList = new List(); bool moved = false; while (!endRecordFound) { if (!moved) endRecordFound = !reader.Read(); moved = false; if (!endRecordFound) { switch (reader.NodeType) { case XmlNodeType.Element: { if (reader.LocalName == "Field") { string type = "String"; string format = null; string definedFormatId = null; string value; if (reader.MoveToAttribute("type")) type = reader.Value; if (reader.MoveToAttribute("format")) format = reader.Value; if (reader.MoveToAttribute("formatId")) definedFormatId = reader.Value; reader.MoveToContent(); value = reader.ReadElementContentAsString(); moved = true; //If there was no format defined, try to find a predefined format, if that was specified. Otherwise run with no format. if ((format == null) && (definedFormatId != null)) predefinedFormats.TryGetValue(definedFormatId, out format); Field field = new Field(type, format, value); fieldList.Add(field); } break; } case XmlNodeType.Text: { } break; case XmlNodeType.EndElement: { if (reader.LocalName == "Record") endRecordFound = true; break; } } } } //Time to save the data. StringBuilder formatStringBuilder = new StringBuilder(); object[] valueList = new object[fieldList.Count]; for (int i = 0; i < fieldList.Count; i++) { valueList[i] = fieldList[i].Value; formatStringBuilder.Append("{"); formatStringBuilder.Append(i.ToString(System.Globalization.CultureInfo.InvariantCulture)); //Append if (fieldList[i].Format != null) { formatStringBuilder.Append(","); formatStringBuilder.Append(fieldList[i].Format); } formatStringBuilder.Append("}"); //If this is the last field, we should not insert any field separator. if (i != (fieldList.Count - 1)) formatStringBuilder.Append(fieldSeparator); } //Insert the record separator. formatStringBuilder.Append(recordSeparator); string formattedRecord = string.Format(System.Globalization.CultureInfo.InvariantCulture, formatStringBuilder.ToString(), valueList); byte[] encodedData = Encoding.Default.GetBytes(formattedRecord); outputFile.Write(encodedData, 0, encodedData.Length); } #endregion } }