/// <summary> /// Determine if data kind on intake must allow fields to be added "on the fly" (i.e. after the first row). /// Note that if true, then OnTheFlyInputFieldsCanBeAllowed must also be true. /// </summary> /// <param name="dataKind"></param> /// <returns>True means that on-the-fly fields must be allowed irrespective of AllowOnTheFlyInputFields); false means that on-the-fly fields may be disallowed via AllowOnTheFlyInputFields).</returns> internal static bool OnTheFlyInputFieldsAreAlwasyAllowed(this KindOfTextData dataKind) { switch (dataKind) { case KindOfTextData.X12: return(true); default: return(false); } }
/// <summary> /// Define the type of feeder/dispenser to be used on intake/ouput. /// </summary> /// <param name="dataKind"></param> /// <returns></returns> internal static ExternalLineType ExternalLineType(this KindOfTextData dataKind) { switch (dataKind) { case KindOfTextData.X12: case KindOfTextData.HL7: return(Common.ExternalLineType.Xsegment); case KindOfTextData.XML: case KindOfTextData.JSON: case KindOfTextData.UnboundJSON: return(Common.ExternalLineType.Xrecord); default: return(Common.ExternalLineType.Xtext); } }
/// <summary> /// Determine if is possible for the fields to be added "on the fly" (i.e. after the first row) on intake /// </summary> /// <param name="inputDataKind">Kind of input data</param> /// <returns>True when on-the-fly fields are allowed (assuming AllowOnTheFlyInputFields=true); false when not (regardless of AllowOnTheFlyInputFields)</returns> internal static bool OnTheFlyInputFieldsCanBeAllowed(this KindOfTextData inputDataKind) { switch (inputDataKind) { case KindOfTextData.Keyword: case KindOfTextData.Delimited: case KindOfTextData.X12: case KindOfTextData.XML: case KindOfTextData.JSON: case KindOfTextData.UnboundJSON: //records of these kinds "can grow", i.e. it is possible for fields to be added on the fly return(true); case KindOfTextData.Raw: case KindOfTextData.Flat: case KindOfTextData.Arbitrary: //these kinds must have all fields fixed after 1st row (even if AllowOnTheFlyInputFields is true) return(false); default: //IMPORTANT: Make sure each newly implemented data kind is assigned to either "fixed" or "can grow" category throw new NotSupportedException($"Unrecognized {inputDataKind} kind encountered."); } }
//TODO: Replace these extension methods by properties of the respective classes (derived from Intake/OutputProvider - Strategy pattern) /// <summary> /// Determine if the type supports possibility for column headers to be in the 1st row /// </summary> /// <param name="dataKind">Kind of text data (either input or output)</param> /// <returns>True if it is possible to have header row; false if not, in which case the HeadersInFirstInputRow or HeadersInFirstOutputRow setting is ignored</returns> internal static bool CanHaveHeaderRow(this KindOfTextData dataKind) { switch (dataKind) { case KindOfTextData.Raw: case KindOfTextData.Keyword: case KindOfTextData.Arbitrary: case KindOfTextData.X12: case KindOfTextData.XML: case KindOfTextData.JSON: case KindOfTextData.UnboundJSON: //"speedy" data kinds, they can start output without knowing the output fields return(false); case KindOfTextData.Delimited: case KindOfTextData.Flat: //"needy" data kinds, they can't start output unless all fields are known return(true); default: //IMPORTANT: Make sure each newly implemented data kind is assigned to either "speedy" or "needy" category throw new NotSupportedException($"Unrecognized {dataKind} kind encountered."); } }
/// <summary> /// Helper method to create a single constituent feeder. /// </summary> /// <param name="reader"></param> /// <param name="sourceNo"></param> /// <param name="inputDataKind"></param> /// <param name="intakeIsAsync"></param> /// <param name="skipHeader"></param> /// <param name="x12SegmentDelimiter"></param> /// <param name="xmlJsonSettings"></param> /// <returns></returns> private static LineFeederForSource CreateLineFeeder(TextReader reader, int sourceNo, KindOfTextData inputDataKind, bool intakeIsAsync, bool skipHeader, string x12SegmentDelimiter, string xmlJsonSettings) { if (inputDataKind.ExternalLineType() == ExternalLineType.Xtext) { return(new TextFeederForSource(reader, sourceNo, skipHeader)); } if (inputDataKind == KindOfTextData.X12) { return(new X12FeederForSource(reader, sourceNo, x12SegmentDelimiter)); } if (inputDataKind == KindOfTextData.XML) { return(new XmlFeederForSource(reader, sourceNo, xmlJsonSettings, intakeIsAsync)); } if (inputDataKind == KindOfTextData.JSON) { return(new JsonFeederForSource(reader, sourceNo, xmlJsonSettings, intakeIsAsync)); } if (inputDataKind == KindOfTextData.UnboundJSON) { return(new UnboundJsonFeederForSource(reader, sourceNo, xmlJsonSettings)); } throw new NotSupportedException($"Feeder type for {inputDataKind} could not be determined."); }
/// <summary> /// Create feeder based on arbitrary set of readers. /// </summary> /// <param name="readers"></param> /// <param name="inputDataKind"></param> /// <param name="intakeIsAsync"></param> /// <param name="skipRepeatedHeaders"></param> /// <param name="x12SegmentDelimiter">Any non-default char means X12, i.e. segments</param> /// <param name="xmlJsonSettings"></param> /// <returns></returns> internal static ILineFeeder CreateLineFeeder(IEnumerable <TextReader> readers, KindOfTextData inputDataKind, bool intakeIsAsync, bool skipRepeatedHeaders, string x12SegmentDelimiter, string xmlJsonSettings) { int sourceNo = 1; bool skipHeader = false; // false for 1st source, same as skipRepeatedHeaders for remaining sources return(new LineFeeder(readers.Select(r => { var feeder = CreateLineFeeder(r, sourceNo++, inputDataKind, intakeIsAsync, skipHeader, x12SegmentDelimiter, xmlJsonSettings); skipHeader = skipRepeatedHeaders; return feeder; }).ToList())); //Here, ToList is needed to prevent multiple iterations over readers (which would've messed up sourceNo closure); besides LineFeeder ctor demands IList (not IEnumerable) }
/// <summary> /// Create a feeder based on file names. /// </summary> /// <param name="files"></param> /// <param name="inputDataKind"></param> /// <param name="intakeIsAsync"></param> /// <param name="skipRepeatedHeaders"></param> /// <param name="x12SegmentDelimiter">non-default char means X12 mode</param> /// <param name="xmlJsonSettings"></param> /// <returns></returns> internal static ILineFeeder CreateLineFeeder(IEnumerable <string> files, KindOfTextData inputDataKind, bool intakeIsAsync, bool skipRepeatedHeaders, string x12SegmentDelimiter, string xmlJsonSettings) { return(CreateLineFeeder(files.Select(f => File.OpenText(f)).ToList(), inputDataKind, intakeIsAsync, skipRepeatedHeaders, x12SegmentDelimiter, xmlJsonSettings)); // OpenText may throw // Note the ToList above; its purpose is to force eager evaluation, so that exception (e.g. FileNotFoundException) is thrown (& caught) // during IntakeProvider initialization (InitIntake method); otherwise, the exception would've been deferred until start of reading records. }
/// <summary> /// Determine if output processing needs to know all output fields before starting output /// </summary> /// <param name="outputDataKind">Kind of output data</param> /// <returns>True for "needy" kinds (output fields needed up-front), false for "speedy" data kinds (output doesn't need to know all fields at start)</returns> internal static bool OutputFieldsAreNeededUpFront(this KindOfTextData outputDataKind) { return(outputDataKind.CanHaveHeaderRow()); //both settings CanHaveHeaderRow and OutputFieldsAreNeededUpFront are synonymous, but they are kept separate for clarity of intents }
/// <summary> /// Helper method to create constituent dispenser: arbitrary writer, target number, X12 indicator provided /// </summary> /// <param name="writer"></param> /// <param name="targetNo"></param> /// <param name="outputDataKind"></param> /// <param name="outputIsAsync"></param> /// <param name="x12SegmentDelimiter"></param> /// <param name="xmlSettings"></param> /// <returns></returns> private static LineDispenserForTarget CreateLineDispenser(TextWriter writer, int targetNo, KindOfTextData outputDataKind, bool outputIsAsync, Lazy<string> x12SegmentDelimiter, string xmlSettings) { if (outputDataKind.ExternalLineType() == ExternalLineType.Xtext) return new TextDispenserForTarget(writer, targetNo); if (outputDataKind == KindOfTextData.X12) return new X12DispenserForTarget(writer, targetNo, x12SegmentDelimiter); if (outputDataKind == KindOfTextData.XML) return new XmlDispenserForTarget(writer, targetNo, xmlSettings, outputIsAsync); if (outputDataKind == KindOfTextData.JSON) return new JsonDispenserForTarget(writer, targetNo, xmlSettings, outputIsAsync); if (outputDataKind == KindOfTextData.UnboundJSON) return new UnboundJsonDispenserForTarget(writer, targetNo, xmlSettings); throw new NotSupportedException($"Dispenser type for {outputDataKind} could not be determined."); }
/// <summary> /// Creates LineDispenser for a collection of text writers (generates sequential target numbers). /// </summary> /// <param name="writers"></param> /// <param name="outputDataKind"></param> /// <param name="outputIsAsync"></param> /// <param name="x12SegmentDelimiter">Segment delimiter in case of X12 output (may include whitespace, e.g. "~\r\n"); null otherwise.</param> /// <param name="xmlSettings"></param> /// <returns></returns> internal static ILineDispenser CreateLineDispenser(IEnumerable<TextWriter> writers, KindOfTextData outputDataKind, bool outputIsAsync, Lazy<string> x12SegmentDelimiter, string xmlSettings) { int targetNo = 1; return new LineDispenser(writers.Select(w => { var dispTarget = CreateLineDispenser(w, targetNo++, outputDataKind, outputIsAsync, x12SegmentDelimiter, xmlSettings); return dispTarget; }).ToList()); //Here, ToList is needed to prevent multiple iterations over writers (which would've messed up targetNo closure); besides LineDispenser ctor demands IList (not IEnumerable) }
/// <summary> /// Creates LineDispenser for a collection of file names and boolean flags /// </summary> /// <param name="files"></param> /// <param name="outputDataKind"></param> /// <param name="outputIsAsync"></param> /// <param name="x12SegmentDelimiter">Segment delimiter in case of X12 output (may include whitespace, e.g. "~\r\n"); null otherwise.</param> /// <param name="xmlSettings"></param> /// <returns></returns> internal static ILineDispenser CreateLineDispenser(IEnumerable<string> files, KindOfTextData outputDataKind, bool outputIsAsync, Lazy<string> x12SegmentDelimiter, string xmlSettings) { return CreateLineDispenser(files.Select(f => File.CreateText(f)).ToList(), outputDataKind, outputIsAsync, x12SegmentDelimiter, xmlSettings); // CreateText may throw // Note the ToList above; its purpose is to force eager evaluation, so that exception (e.g. UnauthorizedAccessException) is thrown (& caught) // during OutputProvider initialization (InitOutput method); otherwise, the exception would've been deferred until start of writing records. }