Пример #1
0
        /// <summary>
        /// This method reads the given XML file and stores each of the timeseries described
        /// therein to the database.  For each timeseries that it stores, it adds an item to
        /// list 'tsImportList', which records metadata of the timeseries that is not processed
        /// directly by TimeSeriesLibrary.  Therefore, the process that calls TimeSeriesLibrary
        /// can process tsImportList to complete the importation of the timeseries.
        /// </summary>
        /// <param name="xmlFileName">Name of the file that will be read.  If xmlText is null,
        /// then this parameter must be non-null, and vice-versa.</param>
        /// <param name="xmlText">The text of an XML file that will be read.  If xmlFileName is null,
        /// then this parameter must be non-null, and vice-versa.</param>
        /// <param name="tsImportList">List of TSImport objects that this function records for
        /// each series that is imported.  This method appends the list.</param>
        /// <param name="shouldStoreToDatabase">If true, then this method will write the timeseries from
        /// the XML file to database.  If false, then this method does not write to database.</param>
        /// <param name="shouldRecordDetails">If true, then this method stores the BLOB and detailed
        /// elements to the list of TSImport objects.  If false, then this method does not store
        /// the BLOB to the TSImport object, and all fields that TimeSeriesLibrary does not process
        /// are stored to the TSImport object's UnprocessedElements field.</param>
        /// <returns>The number of time series records that were successfully stored</returns>
        public int ReadAndStore(
            String xmlFileName, String xmlText,
            List <TSImport> tsImportList,
            Boolean shouldStoreToDatabase, Boolean shouldRecordDetails)
        {
            String s;                                                                               // ephemeral String object
            int    numTs = 0;                                                                       // The # of time series successfuly processed by this method

            TSDateCalculator.TimeStepUnitCode TimeStepUnit = TSDateCalculator.TimeStepUnitCode.Day; // to be read from XML
            short    TimeStepQuantity = 1;                                                          // to be read from XML
            DateTime StartDate        = _defaultTime;                                               // to be read from XML

            double[] valueArray   = null;                                                           // to be read from XML
            var      elementNames = new List <String> {
                "TimeSeries", "Pattern"
            };

            // Flags will indicate if the XML is missing any data
            Boolean foundTimeStepUnit, foundTimeStepQuantity, foundStartDate, foundValueArray;

            // Error checks
            if (xmlFileName == null && xmlText == null)
            {
                throw new TSLibraryException(ErrCode.Enum.Xml_Memory_File_Exclusion,
                                             "The method's xmlFileName and xmlText parameters can not both be null.");
            }
            if (xmlFileName != null && xmlText != null)
            {
                throw new TSLibraryException(ErrCode.Enum.Xml_Memory_File_Exclusion,
                                             "The method's xmlFileName and xmlText parameters can not both be non-null.");
            }
            if (shouldStoreToDatabase && TSConnection == null)
            {
                throw new TSLibraryException(ErrCode.Enum.Xml_Connection_Not_Initialized,
                                             "The method is directed to store results to database, " +
                                             "but a database connection has not been assigned in the constructor.");
            }

            // Initialize a Stream object for the XmlReader object to read from.  This method can
            // be called with either the file name of an XML file, or with a string containing the
            // complete text of the XML to be parsed.  The type of Stream object that we initialize
            // depends on which parameter the method was called with.
            Stream xmlStream;

            if (xmlFileName == null)
            {
                xmlStream        = new MemoryStream(Encoding.ASCII.GetBytes(xmlText));
                ReportedFileName = "The given XML text ";
            }
            else
            {
                xmlStream        = new FileStream(xmlFileName, FileMode.Open);
                ReportedFileName = "The XML file '" + xmlFileName + "' ";
            }

            try
            {
                // Loop once for "TimeSeries" and once for "Pattern".  Note that the approach of looping
                // through the entire file twice seems undesirable, but no better option was evident because
                // methods of XmlReader such as 'ReadToNextSibling' do not have an equivalent that could
                // seek *either* "TimeSeries" or "Pattern".
                foreach (String elementName in elementNames)
                {
                    Boolean isPattern = elementName == "Pattern";
                    // Start at the beginning of the XML file
                    xmlStream.Seek(0, SeekOrigin.Begin);
                    // This XmlReader object opens the XML file and parses it for us.  The 'using'
                    // statement ensures that the XmlReader's resources are properly disposed.
                    using (XmlReader xmlReader = XmlReader.Create(xmlStream))
                    {
                        // All of the data that we'll read is contained inside an element named 'Import'
                        try
                        {
                            if (!xmlReader.ReadToFollowing("Import"))
                            {
                                throw new TSLibraryException(ErrCode.Enum.Xml_File_Empty,
                                                             ReportedFileName + "does not contain an <Import> element.");
                            }
                        }
                        catch
                        {
                            throw new TSLibraryException(ErrCode.Enum.Xml_File_Empty,
                                                         ReportedFileName + "does not contain an <Import> element.");
                        }
                        // The file must contain at least one element named 'TimeSeries'.  Move to the first
                        // such element now.
                        if (!xmlReader.ReadToDescendant(elementName))
                        {
                            // if no such element is found then there is nothing to process in this iteration
                            // of the loop.  After the loop is finished, an exception is thrown if no elements
                            // were read.
                            continue;
                        }
                        // do-while loop through all elements named 'TimeSeries'.  There will be one iteration
                        // of this loop for each timeseries in the XML file.
                        do
                        {
                            // Get a new XmlReader object that can not read outside
                            // of the current 'TimeSeries' element.
                            XmlReader oneSeriesXmlReader = xmlReader.ReadSubtree();
                            // A new TSImport object will store properties of this time series
                            // that the TimeSeriesLibrary is not designed to handle.
                            TSImport tsImport = new TSImport(shouldRecordDetails)
                            {
                                IsPattern = isPattern
                            };
                            // Flags will indicate if the XML is missing any data
                            foundTimeStepUnit = foundTimeStepQuantity = foundStartDate = foundValueArray = false;
                            // This default trace number will be used if the "Trace" attribute is not used on a <Data>
                            // element. The default trace value may be reassigned if a <TraceNumber> element is read.
                            int defaultTraceNumber = 1;
                            // Collection of Strings that hold the unparsed DataSeries.  The key of the
                            // Dictionary is the trace number of the DataSeries
                            Dictionary <int, String> DataStrings = new Dictionary <int, String>();

                            // advance the reader past the outer element
                            oneSeriesXmlReader.ReadStartElement();
                            // Read one timeseries from XML
                            while (oneSeriesXmlReader.Read())
                            {
                                Boolean binaryEncoded = false;
                                // If the current position of the reader is on an element's start tag (e.g. <Name>)
                                if (oneSeriesXmlReader.NodeType == XmlNodeType.Element)
                                {
                                    // Note that XML standard is case sensitive
                                    switch (oneSeriesXmlReader.Name)
                                    {
                                    case "Name":
                                        // <Name> is not processed by TimeSeriesLibrary.  Record it on a list
                                        // so another module can process the <Name> field.  Presumably,
                                        // the process will distinguish timeseries based on the <Name> field.
                                        tsImport.Name = oneSeriesXmlReader.ReadElementContentAsString();
                                        break;

                                    case "StartDate":
                                        // TimeSeriesLibrary will store <StartDate> to the data table
                                        s = oneSeriesXmlReader.ReadElementContentAsString();
                                        if (!TimeExtensions.TryParse(s, out StartDate, _defaultTime))
                                        {
                                            throw new TSLibraryException(ErrCode.Enum.Xml_File_StartDate_Unreadable,
                                                                         ReportedFileName
                                                                         + "contains unreadable StartDate element value " + s);
                                        }
                                        foundStartDate = true;
                                        break;

                                    case "TimeStepUnit":
                                        // TimeSeriesLibrary will store <TimeStepUnit> to the data table
                                        s                 = oneSeriesXmlReader.ReadElementContentAsString();
                                        TimeStepUnit      = ParseTimeStepUnit(s);
                                        foundTimeStepUnit = true;
                                        // If it is an irregular time series
                                        if (TimeStepUnit == TSDateCalculator.TimeStepUnitCode.Irregular)
                                        {
                                            // <TimeStepQuantity> and <StartDate> are unnecessary
                                            // and irrelevant to irregular time series
                                            foundTimeStepQuantity = true;
                                            foundStartDate        = true;
                                        }
                                        break;

                                    case "TimeStepQuantity":
                                        // TimeSeriesLibrary will store <TimeStepQuantity> to the data table
                                        s = oneSeriesXmlReader.ReadElementContentAsString();
                                        TimeStepQuantity      = ParseTimeStepQuantity(s);
                                        foundTimeStepQuantity = true;
                                        break;

                                    case "Data":
                                        // <Data> may have a TraceNumber attribute
                                        int traceNumber = 0;
                                        s = oneSeriesXmlReader.GetAttribute("Trace");
                                        if (int.TryParse(s, out traceNumber) == false)
                                        {
                                            traceNumber = 0;
                                        }
                                        if (DataStrings.ContainsKey(traceNumber))
                                        {
                                            throw new TSLibraryException(ErrCode.Enum.Xml_File_Inconsistent,
                                                                         ReportedFileName + "contains a time series with more than "
                                                                         + "one trace number " + traceNumber.ToString());
                                        }
                                        // <Data> contains a whitespace-deliminted string of values
                                        // that comprise the time series
                                        s = oneSeriesXmlReader.ReadElementContentAsString();
                                        // add the unparsed string to a dictionary
                                        // where the trace number is the dictionary key
                                        DataStrings.Add(traceNumber, s);
                                        foundValueArray = true;
                                        break;

                                    case "Encoding":
                                        // The default is that <Data> contains decimal text.
                                        // However, the <Encoding> element may specify that it is
                                        // Base64 encoded.
                                        s = oneSeriesXmlReader.ReadElementContentAsString();
                                        if (s == "Base64" || s == "base64")
                                        {
                                            binaryEncoded = true;
                                        }
                                        break;

                                    case "Apart":     // <Apart> contains the A part of record name from a HECDSS file
                                        tsImport.SetAPart(oneSeriesXmlReader); break;

                                    case "Bpart":     // <Bpart> contains the B part of record name from a HECDSS file
                                        tsImport.SetBPart(oneSeriesXmlReader); break;

                                    case "Cpart":     // <Cpart> contains the C part of record name from a HECDSS file
                                        tsImport.SetCPart(oneSeriesXmlReader); break;

                                    case "Epart":     // <Epart> contains the E part of record name from a HECDSS file
                                        tsImport.SetEPart(oneSeriesXmlReader); break;

                                    case "Units":     // <Units> contains the name of the units of measurement for the time series values
                                        tsImport.SetUnits(oneSeriesXmlReader); break;

                                    case "TimeSeriesType":
                                        // <TimeSeriesType> contains the text name of the time series type,
                                        // [PER-AVER | PER-CUM | INST-VAL | INST-CUM]
                                        tsImport.SetTimeSeriesType(oneSeriesXmlReader); break;

                                    case "TraceNumber":     // <TraceNumber> contains the trace number for an ensemble
                                        defaultTraceNumber = tsImport.GetTraceNumber(oneSeriesXmlReader); break;

                                    case "MultiplicationFactor":
                                        tsImport.SetMultiplicationFactor(oneSeriesXmlReader); break;

                                    default:
                                        // Any other tags are simply copied to the String object
                                        // 'UnprocessedElements'. Here they are stored with
                                        // the enclosing tags (e.g. "<Units>CFS</Units>").
                                        tsImport.AddUnprocessedElement(oneSeriesXmlReader.ReadOuterXml());
                                        break;
                                    }
                                }
                            }
                            // This XmlReader object was created with the ReadSubtree() method so that it would only
                            // be able to read the current time series element.  We have now reached the end of the
                            // time series element, so the XmlReader should be closed.
                            oneSeriesXmlReader.Close();
                            // The record can not be saved to the table if information for some of the fields is missing.
                            // These flags indicate whether each of the required fields was found in the XML file.
                            if (!(foundTimeStepUnit && foundTimeStepQuantity && foundStartDate && foundValueArray))
                            {
                                // One or more required fields were missing, so we'll throw an exception.
                                String errorList, nameString;
                                if (tsImport.Name == "")
                                {
                                    nameString = "unnamed time series";
                                }
                                else
                                {
                                    nameString = "time series named '" + tsImport.Name + "'";
                                }
                                errorList = "Some required subelements were missing from " + nameString
                                            + " in " + ReportedFileName + "\n";
                                if (!foundStartDate)
                                {
                                    errorList += "\n<StartDate> was not found";
                                }
                                if (!foundTimeStepUnit)
                                {
                                    errorList += "\n<TimeStepUnit> was not found";
                                }
                                if (!foundTimeStepQuantity)
                                {
                                    errorList += "\n<TimeStepQuantity> was not found";
                                }
                                if (!foundValueArray)
                                {
                                    errorList += "\n<Data> was not found";
                                }
                                throw new TSLibraryException(ErrCode.Enum.Xml_File_Incomplete, errorList);
                            }
                            // Now that we've established that all fields have been read, we can parse the
                            // string of timeseries values into an array, and save the array to the database.
                            if (TimeStepUnit == TSDateCalculator.TimeStepUnitCode.Irregular)
                            {
                                // IRREGULAR TIME SERIES

                                // The TS object is used to save one record to the database table
                                TS ts = new TS(TSConnection, TableName, TraceTableName);
                                foreach (KeyValuePair <int, String> keyValuePair in DataStrings)
                                {
                                    int traceNumber = keyValuePair.Key;
                                    if (traceNumber == 0)
                                    {
                                        traceNumber = defaultTraceNumber;
                                    }
                                    // Split the big data string into an array of strings.
                                    // The date/time/value triplets will be all collated together.
                                    String[] stringArray = keyValuePair.Value
                                                           .Split(new char[] { }, StringSplitOptions.RemoveEmptyEntries);
                                    // We'll use this date/value structure to build each item of the date/value array
                                    TSDateValueStruct tsv = new TSDateValueStruct();
                                    // allocate the array of date/value pairs
                                    TSDateValueStruct[] dateValueArray = new TSDateValueStruct[stringArray.Length / 3];
                                    // Loop through the array of strings, 3 elements at a time
                                    for (int i = 2; i < stringArray.Length; i += 3)
                                    {
                                        s                     = stringArray[i - 2] + " " + stringArray[i - 1];
                                        tsv.Date              = DateTime.Parse(s);
                                        tsv.Value             = double.Parse(stringArray[i]);
                                        dateValueArray[i / 3] = tsv;
                                    }
                                    if (tsImport.TraceList.Count == 0)
                                    {
                                        // Ignore whatever was entered in the StartDate element, since it
                                        // might conflict with the date/value entries
                                        StartDate = dateValueArray[0].Date;
                                        // Write parameters to the database and record values in the TSImport object
                                        ts.WriteParametersIrregular(shouldStoreToDatabase, tsImport,
                                                                    dateValueArray.Count(), StartDate, dateValueArray.Last().Date,
                                                                    null, null);
                                    }
                                    else
                                    {
                                        if (StartDate != ts.BlobStartDate)
                                        {
                                            throw new TSLibraryException(ErrCode.Enum.Xml_File_Inconsistent,
                                                                         ReportedFileName + "contains a time series "
                                                                         + "with traces that do not overlay each other.");
                                        }
                                    }
                                    ts.WriteTraceIrregular(0, shouldStoreToDatabase, tsImport,
                                                           traceNumber, dateValueArray);
                                }
                                tsImport.RecordFromTS(ts);
                                // Done with the TS object.
                                ts = null;
                            }
                            else
                            {
                                // REGULAR TIME SERIES

                                // The TS object is used to save one record to the database table
                                TS ts = new TS(TSConnection, TableName, TraceTableName);
                                foreach (KeyValuePair <int, String> keyValuePair in DataStrings)
                                {
                                    int traceNumber = keyValuePair.Key;
                                    if (traceNumber == 0)
                                    {
                                        traceNumber = defaultTraceNumber;
                                    }
                                    // Fancy LINQ statement turns the String object into an array of double[]
                                    valueArray = keyValuePair.Value
                                                 .Split(new char[] { }, StringSplitOptions.RemoveEmptyEntries)
                                                 .Select(z => double.Parse(z)).ToArray();
                                    if (tsImport.TraceList.Count == 0)
                                    {
                                        // Write to the database and record values in the TSImport object
                                        ts.WriteParametersRegular(shouldStoreToDatabase, tsImport,
                                                                  (short)TimeStepUnit, TimeStepQuantity, valueArray.Count(),
                                                                  StartDate, null, null);
                                    }
                                    ts.WriteTraceRegular(0, shouldStoreToDatabase, tsImport,
                                                         traceNumber, valueArray);
                                }
                                tsImport.RecordFromTS(ts);
                                // Done with the TS object.
                                ts = null;
                            }

                            // the TSImport object contains data for this timeseries that TSLibrary does not process.
                            // Add the TSImport object to a list that the calling process can read and use.
                            tsImportList.Add(tsImport);
                            numTs++;
                        } while (xmlReader.ReadToNextSibling(elementName));
                    }
                }
                if (numTs == 0)
                {
                    throw new TSLibraryException(ErrCode.Enum.Xml_File_Empty,
                                                 ReportedFileName + " does not contain any "
                                                 + elementNames.Select(ss => "<" + ss + ">").ToStringWithConjunc("or")
                                                 + " element".Pluralize(elementNames.Count) + ".");
                }
            }
            catch (XmlException e)
            {
                // An XmlException was caught somewhere in the lifetime of the object xmlReader,
                // so we can presumably say there was an error in how the XML file was formatted.
                // The information from the XmlException object is included in the error message
                // that we throw here, and the XmlException is included as an inner exception.
                throw new TSLibraryException(ErrCode.Enum.Xml_File_Malformed,
                                             ReportedFileName + "is malformed.\n\n" + e.Message, e);
            }
            finally
            {
                // Disposing the Stream object ensures that the file is closed, if applicable.
                // This is put into the 'finally' clause so that we can ensure that the Stream
                // is closed no matter how we exit this method.
                xmlStream.Dispose();
            }

            return(numTs);
        }