Example #1
0
        // Static Methods

        /// <summary>
        /// Parses CEV files.
        /// </summary>
        /// <param name="lines">The string array of SEL.cev lines to process</param>
        /// <param name="fileIdentifier">For error logging, an identifier of the file being processed -- typically the filename.</param>
        /// <param name="maxFileDuration">Set to a positive value limit the number of data records processed.</param>
        /// <remarks>Removed lineIndex since file must be processed in sequence. </remarks>
        /// <returns>Data model representing the comma separated event report.</returns>
        public static CommaSeparatedEventReport Parse(string[] lines, string fileIdentifier = "", double maxFileDuration = 0.0D)
        {
            //OnDebugMessage(string.Format("Parsing SEL CEV file: {0}", fileIdentifier));

            if (lines == null || lines.Length == 0)
            {
                OnDebugMessage(string.Format("SEL CEV Parse aborted.  Nothing to do. Sent line array from file {0} is null or empty.", fileIdentifier));
                return(null);
            }

            CommaSeparatedEventReport commaSeparatedEventReport = new CommaSeparatedEventReport();

            commaSeparatedEventReport.Firmware = new Firmware();
            commaSeparatedEventReport.Header   = new Header();

            int    lineIndex          = 0; //relative to the first line in the file
            string inString           = string.Empty;
            int    headerRecordNumber = 1;

            string[] headerFields     = null;
            string[] lastHeaderFields = null;

            //advance to first header record

            while (lineIndex < lines.Length)
            {
                headerFields = StringParser.ParseStandardCSV(lines[lineIndex]);
                if (headerFields != null && headerFields[0].ToUpper().Contains("FID"))
                {
                    break;
                }
                lineIndex++;
            }
            if (lineIndex >= lines.Length)
            {
                OnDebugMessage(string.Format("No SEL CEV data found. Nothing to do processing file {0} of length {1}", fileIdentifier, lines.Length.ToString()));
                return(null);
            }

            //Header Section -- 7 records expected
            //It's reasonable to assume that for a file to be valid it must contain the correct number of headers in the proper order
            //Returns null if header is significantly malformed.
            //However, it will try to survive bad bytesum checks

            while (headerRecordNumber < 8)
            {
                inString = lines[lineIndex].Trim();

                if (!string.IsNullOrEmpty(inString))
                {
                    ByteSum byteSum = new ByteSum();

                    headerFields = StringParser.ParseStandardCSV(inString);

                    switch (headerRecordNumber)
                    {
                    case 1:
                        //field names for headerRecord 2 -- already verified that field 0 contains 'FID'
                        byteSum.Check(inString, fileIdentifier);

                        if (!byteSum.Match)      //moved inside case switch due to case 7
                        {
                            OnDebugMessage(string.Format("ByteSum check failed for header record {0} in SEL CEV file: {1}", headerRecordNumber, fileIdentifier));
                        }

                        if (headerFields.Length < 2)
                        {
                            OnDebugMessage(string.Format("Processing SEL CEV header record 1 for SEL CEV file: {0}  Expected at least 2 fields and {1} were found.", fileIdentifier, headerFields.Length));
                        }

                        headerRecordNumber++;
                        //lastHeaderFields = headerFields;
                        break;

                    case 2:
                        //The FID and Firmware Version Number
                        byteSum.Check(inString, fileIdentifier);

                        if (!byteSum.Match)      //moved inside case switch due to case 7
                        {
                            OnDebugMessage(string.Format("ByteSum check failed for header record {0} in SEL CEV file: {1}", headerRecordNumber, fileIdentifier));
                        }

                        if (headerFields.Length < 2)
                        {
                            OnDebugMessage(string.Format("Processing SEL CEV  header record 2 for SEL CEV file: {0}  Expected at least 2 fields and {1} were found.", fileIdentifier, headerFields.Length));
                        }

                        if (!headerFields[0].Contains("SEL"))
                        {
                            OnDebugMessage(string.Format("Processing field 0 for header record 2 for file {0}  Expected string to contain 'SEL' and '{1}' was found.", fileIdentifier, headerFields[0]));
                            OnDebugMessage(string.Format("Processing TERMINATED for SEL CEV file: {0}", fileIdentifier));
                            return(null);
                        }

                        if (headerFields.Length > 2)
                        {
                            commaSeparatedEventReport.Firmware.ID = headerFields[1].Trim();
                        }

                        headerRecordNumber++;
                        //lastHeaderFields = headerFields;
                        break;

                    case 3:
                        //The headers for the date data
                        byteSum.Check(inString, fileIdentifier);

                        if (!byteSum.Match)      //moved inside case switch due to case 7
                        {
                            OnDebugMessage(string.Format("ByteSum check failed for header record {0} in SEL CEV file: {1}", headerRecordNumber, fileIdentifier));
                        }

                        string[] expectedFieldNames3 = { "MONTH", "DAY", "YEAR", "HOUR", "MIN", "SEC", "MSEC" };

                        if (!StringParser.ExpectedFieldNamesMatch(expectedFieldNames3, headerFields, true, 6))
                        {
                            OnDebugMessage("Processing SEL CEV header record 3, field names for date header. The expected values for the date labels did not match.");
                            OnDebugMessage(string.Format("Processing TERMINATED for SEL CEV file: {0}", fileIdentifier));
                            return(null);
                        }

                        headerRecordNumber++;
                        //lastHeaderFields = headerFields;
                        break;

                    case 4:
                        //The file date values
                        byteSum.Check(inString, fileIdentifier);

                        if (!byteSum.Match)      //moved inside case switch due to case 7
                        {
                            OnDebugMessage(string.Format("ByteSum check failed for header record {0} in SEL CEV file: {1}", headerRecordNumber, fileIdentifier));
                        }

                        if (headerFields.Length < 6)
                        {
                            OnDebugMessage(string.Format("Processing SEL CEV header record 4 for SEL CEV file: {0}  Expected at least 6 fields and {1} were found.", fileIdentifier, headerFields.Length));
                        }
                        else
                        {
                            int[] values;

                            if (!TryConvertInt32(headerFields, out values, headerFields.Length - 1))
                            {
                                OnDebugMessage(string.Format("One or more date fields in header record 4 did not parse to integers.  Event time not set in SEL CEV File: {0}", fileIdentifier));
                            }
                            else
                            {
                                commaSeparatedEventReport.Header.EventTime = new DateTime(values[2], values[0], values[1], values[3], values[4], values[5]);
                                if (commaSeparatedEventReport.Header.EventTime.CompareTo(Convert.ToDateTime("01/01/2000")) < 0)
                                {
                                    OnDebugMessage(string.Format("The event time of {0} is prior to January 1, 2000.", commaSeparatedEventReport.Header.EventTime.ToShortDateString()));
                                }
                            }
                        }

                        headerRecordNumber++;
                        //lastHeaderFields = headerFields;
                        break;

                    case 5:
                        //The headers for the summary data - fields can appear in any order
                        byteSum.Check(inString, fileIdentifier);

                        if (!byteSum.Match)      //moved inside case switch due to case 7
                        {
                            OnDebugMessage(string.Format("ByteSum check failed for header record {0} in SEL CEV file: {1}", headerRecordNumber, fileIdentifier));
                        }

                        if (StringParser.FindIndex("FREQ", headerFields) < 0 || StringParser.FindIndex("EVENT", headerFields) < 0)      //just check a couple
                        {
                            OnDebugMessage("Processing header record 5 and the minimum expected values of 'FREQ' and 'EVENT' were not found.");
                            OnDebugMessage(string.Format("Processing TERMINATED for SEL CEV file: {0}", fileIdentifier));
                            return(null);
                        }

                        headerRecordNumber++;
                        lastHeaderFields = headerFields;
                        break;

                    case 6:
                        //The summary data  - no try-parse data tests performed since there is confirmation of the availability of specific fields
                        byteSum.Check(inString, fileIdentifier);

                        if (!byteSum.Match)      //moved inside case switch due to case 7
                        {
                            OnDebugMessage(string.Format("ByteSum check failed for header record {0} in SEL CEV file: {1}", headerRecordNumber, fileIdentifier));
                        }

                        if (headerFields.Length != lastHeaderFields.Length)
                        {
                            OnDebugMessage(string.Format("Processing header record 6 -- expected {0} values and {1} were found", lastHeaderFields.Length, headerFields.Length));
                            OnDebugMessage(string.Format("Processing TERMINATED for SEL CEV file: {0}", fileIdentifier));
                            return(null);
                        }

                        //For completeness, not needed
                        commaSeparatedEventReport.Header.SerialNumber = 0;
                        commaSeparatedEventReport.Header.RelayID      = "";
                        commaSeparatedEventReport.Header.StationID    = "";

                        //set key class properties
                        //nominal frequency is based on average found.

                        commaSeparatedEventReport.FrequencyAverage = Convert.ToDouble(headerFields[StringParser.FindIndex("FREQ", lastHeaderFields)]);
                        if (commaSeparatedEventReport.FrequencyAverage > 48D && commaSeparatedEventReport.FrequencyAverage < 52D)
                        {
                            commaSeparatedEventReport.FrequencyNominal = 50D;
                        }
                        else
                        {
                            commaSeparatedEventReport.FrequencyNominal = 60D;
                        }
                        commaSeparatedEventReport.Event = headerFields[StringParser.FindIndex("EVENT", lastHeaderFields)];

                        int labelIndex = StringParser.FindIndex("SAM/CYC_A", lastHeaderFields);
                        if (labelIndex > 0)
                        {
                            commaSeparatedEventReport.SamplesPerCycleAnalog = Convert.ToDouble(headerFields[labelIndex]);
                        }
                        labelIndex = StringParser.FindIndex("SAM/CYC_D", lastHeaderFields);
                        if (labelIndex > 0)
                        {
                            commaSeparatedEventReport.SamplesPerCycleDigital = Convert.ToDouble(headerFields[labelIndex]);
                        }
                        labelIndex = StringParser.FindIndex("NUM_OF_CYC", lastHeaderFields);
                        if (labelIndex > 0)
                        {
                            commaSeparatedEventReport.NumberOfCycles = Convert.ToDouble(headerFields[labelIndex]);
                        }
                        labelIndex = StringParser.FindIndex("NUM_CH_A", lastHeaderFields);
                        if (labelIndex > 0)
                        {
                            commaSeparatedEventReport.ExpectedAnalogCount = Convert.ToInt32(headerFields[labelIndex]);
                        }
                        labelIndex = StringParser.FindIndex("NUM_CH_D", lastHeaderFields);
                        if (labelIndex > 0)
                        {
                            commaSeparatedEventReport.ExpectedDigitalCount = Convert.ToInt32(headerFields[labelIndex]);
                        }

                        headerRecordNumber++;
                        lastHeaderFields = headerFields;
                        break;

                    case 7:
                        //The header the data records - assume valid if all three phase currents are present
                        //Note for some files, this record spans multiple lines, will keep reading lines until the quotes match.

                        StringBuilder sb = new StringBuilder(lines[lineIndex]);

                        //find all the data field names - spanning multiple lines
                        while (sb.ToString().CharCount('\"') % 2 == 1)
                        {
                            sb.Append(lines[++lineIndex]);
                            if (lineIndex >= lines.Length)
                            {
                                OnDebugMessage(string.Format("Only partial CEV header data found. Processing of SEL CEV file: {0} aborted at line {1}", fileIdentifier, lineIndex.ToString()));
                                return(null);
                            }
                        }

                        headerFields = StringParser.ParseStandardCSV(sb.ToString().Trim());
                        byteSum.Check(inString, fileIdentifier);

                        if (!byteSum.Match)      //moved inside case switch due to case 7
                        {
                            OnDebugMessage(string.Format("ByteSum check failed for header record {0} in SEL CEV file: {1}", headerRecordNumber, fileIdentifier));
                        }

                        if (StringParser.FindIndex("IA", headerFields, false, true) < 0 || StringParser.FindIndex("IB", headerFields, false, true) < 0 ||
                            StringParser.FindIndex("IC", headerFields, false, true) < 0)
                        {
                            OnDebugMessage("Processing header record 7, the field names for the data records, and did not find the minimum set of 'IA', 'IB' and 'IC'");
                            OnDebugMessage(string.Format("Processing TERMINATED for SEL CEV file: {0}", fileIdentifier));
                            return(null);
                        }

                        headerRecordNumber++;
                        lastHeaderFields = headerFields;
                        break;

                    default:
                        break;
                    }
                }
                else
                {
                    OnDebugMessage(string.Format("Unexpected empty header reader in advance of header record number {0} for SEL CEV file: {1}", headerRecordNumber, fileIdentifier));
                }

                lineIndex++;
                if (lineIndex >= lines.Length)
                {
                    OnDebugMessage(string.Format("Only partial CEV header data found. Processing of SEL CEV file: {0} aborted at line {1}", fileIdentifier, lineIndex.ToString()));
                    return(null);
                }
                //else
                //    OnDebugMessage(string.Format("Successfully parsed header record {0} in SEL CEV file: {1}", headerRecordNumber - 1, fileIdentifier));
            }

            commaSeparatedEventReport.InitialReadingIndex = lineIndex;

            //determine the number of analog data fields based on the position of "TRIG" (the trigger field name) and setup up Analog Section
            int triggerFieldPosition = Array.FindIndex(headerFields, x => x.ToUpper().Contains(m_triggerFieldName));

            if (triggerFieldPosition < 0)  //not found
            {
                OnDebugMessage(string.Format("Processing header record 8, the field names for data, searching for {0} as the analog/digital data field separator.  It was not found within the values of {1}.",
                                             m_triggerFieldName, headerFields.ToString()));

                OnDebugMessage(string.Format("Processing TERMINATED for SEL CEV file: {0}", fileIdentifier));
                return(null);
            }

            if (headerFields.Length < triggerFieldPosition + 2)  //too few field names past separator
            {
                OnDebugMessage(string.Format("Processing header record 8, the field names for data, too few field names found past {0} (the analog/digital data field separator) within the values of {1}.",
                                             m_triggerFieldName, headerFields.ToString()));

                OnDebugMessage(string.Format("Processing TERMINATED for SEL CEV file: {0}", fileIdentifier));
                return(null);
            }

            commaSeparatedEventReport.AnalogSection = new AnalogSection();

            //loop through the expected analog fields, add all the fields but "TRIG" (the trigger field name)

            if (commaSeparatedEventReport.ExpectedAnalogCount <= 0)
            {
                commaSeparatedEventReport.ExpectedAnalogCount = triggerFieldPosition;
            }

            //expected value count = analogs + trigger + digitals + bytesum (analogs plus 3)
            commaSeparatedEventReport.ExpectedDataRecordValueCount = commaSeparatedEventReport.ExpectedAnalogCount + 3;

            //for speed, the scaling factors, if any are used, are pre-positioned
            double[] scalingFactors  = new double[commaSeparatedEventReport.ExpectedAnalogCount];
            bool     scalingRequired = false;

            for (int fieldIndex = 0; fieldIndex < triggerFieldPosition; fieldIndex++)
            {
                commaSeparatedEventReport.AnalogSection.AnalogChannels.Add(new Channel <double>());
                commaSeparatedEventReport.AnalogSection.AnalogChannels[fieldIndex].Name = headerFields[fieldIndex];
                if (headerFields[fieldIndex].ToUpper().Contains("KV"))
                {
                    scalingFactors[fieldIndex] = 1000D;
                    scalingRequired            = true;
                }
                else
                {
                    scalingFactors[fieldIndex] = 1D;
                }
            }

            //loop through the digital channels
            int digitalChannelCount = 0;

            foreach (string channel in headerFields[triggerFieldPosition + 1].QuoteUnwrap().RemoveDuplicateWhiteSpace().Trim().Split(' '))
            {
                commaSeparatedEventReport.AnalogSection.DigitalChannels.Add(new Channel <bool?>());
                commaSeparatedEventReport.AnalogSection.DigitalChannels[commaSeparatedEventReport.AnalogSection.DigitalChannels.Count - 1].Name = channel;
                digitalChannelCount++;
            }

            if (commaSeparatedEventReport.ExpectedDigitalCount <= 0)
            {
                commaSeparatedEventReport.ExpectedDigitalCount = digitalChannelCount;
            }
            else if (digitalChannelCount != commaSeparatedEventReport.ExpectedDigitalCount)
            {
                OnDebugMessage(string.Format("Processing SEL CEV header record 8, the field names for data, the {0} digital channel names found does not match the expected number of {1}",
                                             commaSeparatedEventReport.ExpectedDigitalCount, digitalChannelCount));
            }

            //find the trigger record within the data section, Carry on if none found.
            int triggerIndexRelative = 0;   //relative to the first data line

            for (lineIndex = commaSeparatedEventReport.InitialReadingIndex; lineIndex < lines.Length; lineIndex++)
            {
                if (string.IsNullOrEmpty(lines[lineIndex]) || lines[lineIndex].Trim().Length == 0)
                {
                    OnDebugMessage(string.Format("Null or empty data record was found at line {0} in file {1} and was skipped in the determination of the trigger record time.", lineIndex, fileIdentifier));
                    //this condition logged at Info level later
                    continue;  //skip this line to be consistent with data parsing logic.
                }

                string[] s = lines[lineIndex].Split(',');                 //use the split function for speed
                if (s.Length > triggerFieldPosition && s[triggerFieldPosition].Trim().Length > 0)
                {
                    commaSeparatedEventReport.TriggerIndex = triggerIndexRelative;
                    break;
                }
                if (s.Length > 0 && s[0].ToUpper().Contains("SETTINGS"))  //we're done with data and no trigger was found.
                {
                    triggerIndexRelative = 0;
                    OnDebugMessage(string.Format("No trigger index found in SEL CEV file: {0}", fileIdentifier));
                    break;
                }
                ++triggerIndexRelative;
            }

            if (lineIndex >= lines.Length)  //we've looped through all the lines, no trigger && no SETTINGS
            {
                OnDebugMessage(string.Format("No SETTINGS line terminator and No trigger index found in SEL CEV file: {0}", fileIdentifier));
                //this condition logged at the Info level later
                triggerIndexRelative = 0;
            }

            //Log significant info about the file
            //OnDebugMessage(string.Format("Found {0} analog channels and {1} digital channels to process within the SEL CEV file: {2} with an event time of {3} and a relative trigger index of {4}",
            //   commaSeparatedEventReport.AnalogSection.AnalogChannels.Count, commaSeparatedEventReport.AnalogSection.DigitalChannels.Count, fileIdentifier,
            //   commaSeparatedEventReport.Header.EventTime.ToLongDateString(), triggerIndexRelative.ToString()));

            int timeStepTicks = Convert.ToInt32(Math.Round(10000000.0 / commaSeparatedEventReport.FrequencyNominal / commaSeparatedEventReport.SamplesPerCycleAnalog));
            //Time (in ticks) is relative to the trigger line (record).
            //Negative in advance of the trigger record, Zero at the trigger record, Positive following the trigger recored.
            int lineTicks = -1 * triggerIndexRelative * timeStepTicks;
            //Log significant time-based info
            //OnDebugMessage(string.Format("Starting line tics: {0} Incremental tics per record: {1}", lineTicks, timeStepTicks));


            //set data record limit
            int dataRecordLimit = (int)Math.Round(maxFileDuration * commaSeparatedEventReport.FrequencyNominal * commaSeparatedEventReport.SamplesPerCycleAnalog);

            if (dataRecordLimit > 0)
            {
                dataRecordLimit = ((lines.Length - commaSeparatedEventReport.InitialReadingIndex) > dataRecordLimit) ? dataRecordLimit : lines.Length - commaSeparatedEventReport.InitialReadingIndex;
            }
            else
            {
                dataRecordLimit = lines.Length - commaSeparatedEventReport.InitialReadingIndex;
            }

            //------------------------------------------------  THE DATA --------------------------------------------------------
            //Now loop through the lines to get the data
            //Empty lines are ignored (i.e., time is not incremented) [OnDebugMessage]
            //For radically malformed lines time is incremented and all analogs are set to NaN and digitals set to null [OnDebugMessage]
            //Data field order and type are set by the header and do not vary with the data region

            int dataRecordCount = 0;

            for (lineIndex = commaSeparatedEventReport.InitialReadingIndex; lineIndex < commaSeparatedEventReport.InitialReadingIndex + dataRecordLimit; lineIndex++)
            {
                string[] data = StringParser.ParseStandardCSV(lines[lineIndex]);
                dataRecordCount++;

                if (data == null || data.Length == 0)
                {
                    OnDebugMessage(string.Format("Data record {0} in SEL CEV file: {1} was empty and was skipped.", dataRecordCount, fileIdentifier));
                    continue; //get next line
                }

                if (data.Length > 0 && data[0].ToUpper().Contains("SETTINGS"))  //we're done with the data
                {
                    break;
                }

                //increment time
                commaSeparatedEventReport.AnalogSection.TimeChannel.Samples.Add(commaSeparatedEventReport.Header.EventTime.AddTicks(lineTicks));
                lineTicks += timeStepTicks;

                if (data.Length != commaSeparatedEventReport.ExpectedDataRecordValueCount)
                {
                    OnDebugMessage(string.Format("Data record {0} in SEL CEV file: {1} did not contain the anticipated values.", dataRecordCount, fileIdentifier));
                    //OnDebugMessage(string.Format("Data record {0} in SEL CEV file: {1} did not contain the anticipated {2) values.  Setting all analog and digital values to NaN or null and continuing.",
                    //    dataRecordCount.ToString(), fileIdentifier, Convert.ToString(commaSeparatedEventReport.ExpetedDataRecordValueCount)));
                    //let's try to survive it.
                    foreach (var analogChannel in commaSeparatedEventReport.AnalogSection.AnalogChannels)
                    {
                        analogChannel.Samples.Add(Double.NaN);    //what are the consequences here??
                    }
                    foreach (Channel <bool?> channel in commaSeparatedEventReport.AnalogSection.DigitalChannels)
                    {
                        channel.Samples.Add(null);
                    }
                    continue;  //get next line
                }

                //check bytesum
                ByteSum byteSum = new ByteSum();

                byteSum.Check(lines[lineIndex], fileIdentifier);

                if (!byteSum.Match)
                {
                    //todo: Append AnalogSection to include data quality
                    OnDebugMessage(string.Format("Byte sum does not match for data record {0} in SEL CEV file {1}. This record processed as if it is valid.", dataRecordCount, fileIdentifier));
                }

                //LOAD ANALOG DATA (overall record tests above are sufficient to verify expected number of values)
                int channelIndex = 0;
                foreach (var analogChannel in commaSeparatedEventReport.AnalogSection.AnalogChannels)
                {
                    double value = 0D;
                    if (!double.TryParse(data[channelIndex], out value))
                    {
                        analogChannel.Samples.Add(Double.NaN);
                    }
                    else if (scalingRequired)
                    {
                        analogChannel.Samples.Add(Convert.ToDouble(data[channelIndex]) * scalingFactors[channelIndex]);
                    }
                    else
                    {
                        analogChannel.Samples.Add(Convert.ToDouble(data[channelIndex]));
                    }

                    channelIndex++;

                    //analogChannel.Samples.Add(Convert.ToDouble(lines[lineIndex].Split(',')[channelIndex]) * (lineFields[channelIndex++].ToUpper().Contains("KV") ? 1000 : 1));
                }

                //if (dataRecordCount == 1)  //log the first recored for debug
                //    OnDebugMessage("Data record 1: " + lines[lineIndex]);

                //LOAD DIGITAL DATA
                char[] hexDigitals = data[commaSeparatedEventReport.AnalogSection.AnalogChannels.Count + 1].QuoteUnwrap().Trim().ToCharArray(); //digitals always on the other side of "TRIG"

                if (hexDigitals.Length == 0)
                {
                    continue;
                }

                if (hexDigitals.Length * 4 < commaSeparatedEventReport.AnalogSection.DigitalChannels.Count)
                {
                    OnDebugMessage(string.Format("The expected {0} digital channels were not found for data record {1} in SEL CEV file {2}.  {3} were found.  Setting digitals to null and continuing.",
                                                 hexDigitals.Length * 4, dataRecordCount, fileIdentifier, commaSeparatedEventReport.AnalogSection.DigitalChannels.Count));
                    foreach (Channel <bool?> channel in commaSeparatedEventReport.AnalogSection.DigitalChannels)
                    {
                        channel.Samples.Add(null);
                    }
                    continue;  //get next line
                }

                channelIndex = 0;
                int hexCharIndex = 0;
                if (commaSeparatedEventReport.AnalogSection.DigitalChannels.Count > 0)
                {
                    foreach (Channel <bool?> channel in commaSeparatedEventReport.AnalogSection.DigitalChannels)  //loop through the channels and add the values
                    {
                        hexCharIndex = channelIndex / 4;
                        if (hexDigitals[hexCharIndex].IsHex())
                        {
                            BitArray ba = hexDigitals[hexCharIndex].ConvertHexToBitArray();
                            //OnDebugMessage(string.Format("dig channel:{0} hex:{1}, position:{2}, value:{3}", channelIndex, hexDigitals[hexCharIndex], channelIndex % 4, ba[channelIndex % 4].ToString()));  //validation of correct digital logic
                            channel.Samples.Add(ba[channelIndex % 4]);
                        }
                        else
                        {
                            channel.Samples.Add(null);
                        }

                        channelIndex++;
                    }
                }
            }

            //------------------------------  END DATA SECTION -----------------------------------------

            if (lineIndex >= commaSeparatedEventReport.InitialReadingIndex + dataRecordLimit)  //we've looped through all the lines, no trigger && no SETTINGS
            {
                OnDebugMessage(string.Format("Reached data record limit of {0} prior to finding SETTINGS as data section terminator in SEL CEV file: {1}", dataRecordLimit, fileIdentifier));
            }

            commaSeparatedEventReport.ExpectedSampleCount = dataRecordCount;
            //OnDebugMessage(string.Format("Successfully processed {0} data records in SEL CEV file: {1}", dataRecordCount, fileIdentifier));

            if (lineIndex >= lines.Length)
            {
                return(commaSeparatedEventReport);  //we're done.
            }
            //advance to 'SETTINGS' if we're not there already
            if (!lines[lineIndex].Contains("SETTINGS"))
            {
                while (lineIndex < lines.Length)
                {
                    string[] temp = StringParser.ParseStandardCSV(lines[lineIndex]);
                    if (temp != null && string.Equals(temp[0].ToUpper(), "SETTINGS"))
                    {
                        break;
                    }
                    lineIndex++;
                }
            }

            if (lineIndex >= lines.Length)  //we've looped through all the lines no settings found to add
            {
                OnDebugMessage(string.Format("No settings were found following the SETTINGS line terminator was found at end of data section in SEL CEV file: {0}", fileIdentifier));
                //we're done
                return(commaSeparatedEventReport);
            }

            //advance to the first non-null settings line
            lineIndex++;
            while (lineIndex < lines.Length && (lines[lineIndex].Trim().Length == 0 || lines[lineIndex].Trim() == "\""))
            {
                lineIndex++;
            }

            List <SectionDefinition> settingsRegions = new List <SectionDefinition>();
            string sectionName = "Settings";  //always first
            int    startLine   = lineIndex;

            while (lineIndex < lines.Length)
            {
                string test = lines[lineIndex].ToUpper().Trim();
                if (test.Contains("SETTINGS") || test.Contains("VARIABLES") || test.Contains("EQUATIONS"))
                {
                    settingsRegions.Add(new SectionDefinition(sectionName, startLine, lineIndex - startLine));
                    int p = lines[lineIndex].IndexOf(':');
                    if (p < 0)
                    {
                        sectionName      = lines[lineIndex].RemoveCharacters(char.IsWhiteSpace); //no terminating colon, assume just the name.
                        lines[lineIndex] = string.Empty;
                    }
                    else
                    {
                        int q = lines[lineIndex].IndexOf(":=");
                        if (q < 0)
                        {
                            q = lines[lineIndex].IndexOf('=');
                        }

                        if (q < 0) //no data this line.
                        {
                            sectionName      = lines[lineIndex].Substring(0, p).RemoveCharacters(char.IsWhiteSpace);
                            lines[lineIndex] = string.Empty;
                        }
                        else
                        {
                            lines[lineIndex] = lines[lineIndex].Substring(p + 1);
                        }
                    }
                    startLine = lineIndex;
                }
                lineIndex++;
            }
            settingsRegions.Add(new SectionDefinition(sectionName, startLine, lines.Length - startLine));   //handle the last one.

            //Now build the dictionary of settings.
            //The key for settings takes the form "RegionName:Settings" -- with all white spaces removed from RegionNames

            string[] regions = new string[settingsRegions.Count];
            Dictionary <string, string> settingValues = new Dictionary <string, string>();

            int i = 0;

            foreach (SectionDefinition sd in settingsRegions)
            {
                for (int j = sd.StartLine; j < sd.StartLine + sd.Length; ++j)
                {
                    string test = lines[j].Trim().RemoveDuplicateWhiteSpace();
                    if (test.Length == 0)
                    {
                        continue; //get the next line
                    }
                    while (test.Contains("="))
                    {
                        int    p, q, r, s = 0;
                        string value = string.Empty;

                        p = test.IndexOf(":=");
                        q = p + 2;
                        if (p < 0)
                        {
                            p = test.IndexOf("=");
                            q = p + 1;
                        }

                        string key = string.Concat(sd.Name, ":", test.Substring(0, p).Trim());

                        if (q < test.Length)
                        {
                            r = test.IndexOf(":=", q);   //find the next one
                            if (r < 0)
                            {
                                r = test.IndexOf('=', q);
                            }

                            if (r > 0)
                            {
                                s = test.IndexOfPrevious(char.IsWhiteSpace, r - 2);  //go back past the space
                                if (s < q)
                                {
                                    value = "0";
                                }
                                else
                                {
                                    value = test.Substring(q, s - q).Trim();
                                }
                            }
                            else //we're at the end of the line
                            {
                                value = test.Substring(q).Trim();
                                test  = "";
                            }

                            if (settingValues.ContainsKey(key))
                            {
                                OnDebugMessage(string.Format("Settings already contains key:{0}", key));
                            }
                            else
                            {
                                settingValues.Add(key, value);
                            }
                        }

                        test = test.Substring(s);
                    }
                }
                regions[i++] = sd.Name;
            }

            //OnDebugMessage(string.Format("Successfully found {0} settings groups and a total of {1} settings.", regions.Length, settingValues.Count));

            commaSeparatedEventReport.SettingsRegions = regions;
            commaSeparatedEventReport.Settings        = settingValues;

            return(commaSeparatedEventReport);
        }
Example #2
0
        // Static Methods

        /// <summary>
        /// Parses CEV files.
        /// </summary>
        /// <param name="lines">The string array of SEL.cev lines to process</param>
        /// <param name="processSettings">Set to TRUE to process settings block</param>
        /// <param name="processDigitals">Set to TRUE to process digital values block</param>
        /// <param name="fileIdentifier">For error logging, an identifier of the file being processed -- typically the filename.</param>
        /// <param name="maxFileDuration">Set to a positive value limit the number of data records processed.</param>
        /// <remarks>Removed lineIndex since file must be processed in sequence. </remarks>
        /// <returns>Data model representing the comma separated event report.</returns>
        public static CommaSeparatedEventReport Parse(string[] lines, bool processDigitals = true, bool processSettings = false, string fileIdentifier = "", double maxFileDuration = 0.0D)
        {
            //OnDebugMessage(string.Format("Parsing SEL CEV file: {0}", fileIdentifier));

            if (lines == null || lines.Length == 0)
            {
                OnDebugMessage($"SEL CEV Parse aborted.  Nothing to do. Sent line array from file {fileIdentifier} is null or empty.");
                return(null);
            }

            CommaSeparatedEventReport cSER = new CommaSeparatedEventReport();

            cSER.Firmware = new Firmware();
            cSER.Header   = new Header();

            cSER.ProcessSettings = processSettings;
            cSER.ProcessDigitals = processDigitals;

            int    lineIndex = 0;   //relative to the first line in the file
            string inString;
            int    headerRecordNumber = 1;

            string[] headerFields     = null;
            string[] lastHeaderFields = null;

            //------------------------------------------------  THE HEADER BLOCK --------------------------------------------------------

            //Header Section -- 7 records expected
            //It's reasonable to assume that for a file to be valid it must contain the correct number of headers in the proper order
            //Returns null if header is significantly malformed.
            //However, it will try to survive bad bytesum checks

            while (lineIndex < lines.Length)
            {
                headerFields = StringParser.ParseStandardCSV(lines[lineIndex]);
                if (headerFields != null && headerFields[0].ToUpper().Contains("FID"))
                {
                    break;
                }
                lineIndex++;
            }
            if (lineIndex >= lines.Length)
            {
                OnDebugMessage($"No SEL CEV data found. Nothing to do processing file {fileIdentifier} of length {lines.Length.ToString()}");
                return(null);
            }

            while (headerRecordNumber < 8)
            {
                inString = lines[lineIndex].Trim();

                if (!string.IsNullOrEmpty(inString))
                {
                    ByteSum byteSum = new ByteSum();

                    headerFields = StringParser.ParseStandardCSV(inString);

                    switch (headerRecordNumber)
                    {
                    case 1:
                        //field names for headerRecord 2 -- already verified that field 0 contains 'FID'
                        byteSum.Check(inString, fileIdentifier);

                        if (!byteSum.Match)      //moved inside case switch due to case 7
                        {
                            OnDebugMessage($"ByteSum check failed for header record {headerRecordNumber} in SEL CEV file: {fileIdentifier}");
                        }

                        if (headerFields.Length < 2)
                        {
                            OnDebugMessage($"Processing SEL CEV header record 1 for SEL CEV file: {fileIdentifier}  Expected at least 2 fields and {headerFields.Length} were found.");
                        }

                        headerRecordNumber++;
                        //lastHeaderFields = headerFields;
                        break;

                    case 2:
                        //The FID and Firmware Version Number
                        byteSum.Check(inString, fileIdentifier);

                        if (!byteSum.Match)      //moved inside case switch due to case 7
                        {
                            OnDebugMessage($"ByteSum check failed for header record {headerRecordNumber} in SEL CEV file: {fileIdentifier}");
                        }

                        if (headerFields.Length < 2)
                        {
                            OnDebugMessage($"Processing SEL CEV  header record 2 for SEL CEV file: {fileIdentifier}  Expected at least 2 fields and {headerFields.Length} were found.");
                        }

                        if (!headerFields[0].Contains("SEL"))
                        {
                            OnDebugMessage($"Processing field 0 for header record 2 for file {fileIdentifier}  Expected string to contain 'SEL' and '{headerFields[0]}' was found.");
                            OnDebugMessage($"Processing TERMINATED for SEL CEV file: {fileIdentifier}");
                            return(null);
                        }

                        if (headerFields.Length > 2)
                        {
                            cSER.Firmware.ID = headerFields[1].Trim();
                        }

                        headerRecordNumber++;
                        //lastHeaderFields = headerFields;
                        break;

                    case 3:
                        //The headers for the date data
                        byteSum.Check(inString, fileIdentifier);

                        if (!byteSum.Match)      //moved inside case switch due to case 7
                        {
                            OnDebugMessage($"ByteSum check failed for header record {headerRecordNumber} in SEL CEV file: {fileIdentifier}");
                        }

                        string[] expectedFieldNames3 = { "MONTH", "DAY", "YEAR", "HOUR", "MIN", "SEC", "MSEC" };

                        if (!StringParser.ExpectedFieldNamesMatch(expectedFieldNames3, headerFields, true, 6))
                        {
                            OnDebugMessage("Processing SEL CEV header record 3, field names for date header. The expected values for the date labels did not match.");
                            OnDebugMessage($"Processing TERMINATED for SEL CEV file: {fileIdentifier}");
                            return(null);
                        }

                        headerRecordNumber++;
                        //lastHeaderFields = headerFields;
                        break;

                    case 4:
                        //The file date values
                        byteSum.Check(inString, fileIdentifier);

                        if (!byteSum.Match)      //moved inside case switch due to case 7
                        {
                            OnDebugMessage($"ByteSum check failed for header record {headerRecordNumber} in SEL CEV file: {fileIdentifier}");
                        }

                        if (headerFields.Length < 6)
                        {
                            OnDebugMessage($"Processing SEL CEV header record 4 for SEL CEV file: {fileIdentifier}  Expected at least 6 fields and {headerFields.Length} were found.");
                        }
                        else
                        {
                            if (!TryConvertInt32(headerFields, out int[] values, headerFields.Length - 1))