// Copyright (C) Stichting Deltares 2016. All rights reserved. // // This file is part of Ringtoets. // // Ringtoets is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program. If not, see . // // All names, logos, and references to "Deltares" are registered trademarks of // Stichting Deltares and remain full property of Stichting Deltares at all times. // All rights reserved. using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Xml; using System.Xml.Linq; using System.Xml.Schema; using Core.Common.IO.Exceptions; using Core.Common.Utils; using Core.Common.Utils.Builders; using Ringtoets.Common.IO.Properties; using Ringtoets.Common.IO.Schema; using CoreCommonUtilsResources = Core.Common.Utils.Properties.Resources; namespace Ringtoets.Common.IO.Readers { /// /// Base class for reading a configuration from XML and creating a collection of corresponding /// , typically containing one or more . /// /// The type of calculation items read from XML. public abstract class ConfigurationReader where TReadCalculation : IReadConfigurationItem { private readonly XDocument xmlDocument; /// /// Creates a new instance of . /// /// The file path to the XML file. /// A string representing an XML Schema Definition (XSD). /// Thrown when is invalid. /// Thrown when: /// /// points to a file that does not exist. /// points to a file that does not contain valid XML. /// points to a file that does not pass the schema validation. /// /// protected ConfigurationReader(string xmlFilePath, string schemaString) { IOUtils.ValidateFilePath(xmlFilePath); ValidateFileExists(xmlFilePath); xmlDocument = LoadDocument(xmlFilePath); ValidateToSchema(xmlDocument, schemaString, xmlFilePath); ValidateNotEmpty(xmlDocument, xmlFilePath); } /// /// Reads the configuration from the XML and creates a collection of corresponding . /// /// A collection of read . public IEnumerable Read() { return ParseElements(xmlDocument.Root?.Elements()); } /// /// Parses a read calculation element. /// /// The read calculation element to parse. /// A parsed . protected abstract TReadCalculation ParseCalculationElement(XElement calculationElement); /// /// Validates whether a file exists at the provided . /// /// The file path to validate. /// Thrown when no existing file is found. private static void ValidateFileExists(string xmlFilePath) { if (!File.Exists(xmlFilePath)) { string message = new FileReaderErrorMessageBuilder(xmlFilePath) .Build(CoreCommonUtilsResources.Error_File_does_not_exist); throw new CriticalFileReadException(message); } } /// /// Loads an XML document from the provided . /// /// The file path to load the XML document from. /// Thrown when the XML document cannot be loaded. private static XDocument LoadDocument(string xmlFilePath) { try { return XDocument.Load(xmlFilePath, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo | LoadOptions.SetBaseUri); } catch (Exception exception) when (exception is InvalidOperationException || exception is XmlException || exception is IOException) { string message = new FileReaderErrorMessageBuilder(xmlFilePath) .Build(CoreCommonUtilsResources.Error_General_IO_Import_ErrorMessage); throw new CriticalFileReadException(message, exception); } } /// /// Validates the provided XML document based on the provided XML Schema Definition (XSD). /// /// The XML document to validate. /// A string representing the XML Schema Definition (XSD) to use for the validation. /// The file path the XML document is loaded from. /// Thrown when the provided XML document does not match the provided XML Schema Definition (XSD). private static void ValidateToSchema(XDocument document, string schemaString, string xmlFilePath) { var xmlSchemaSet = new XmlSchemaSet(); xmlSchemaSet.Add(XmlSchema.Read(new StringReader(schemaString), null)); try { document.Validate(xmlSchemaSet, null); } catch (XmlSchemaValidationException exception) { string message = string.Format(Resources.ConfigurationReader_Configuration_contains_no_valid_xml_line_0_position_1_reason_2, exception.LineNumber, exception.LinePosition, exception.Message); throw new CriticalFileReadException(new FileReaderErrorMessageBuilder(xmlFilePath).Build(message), exception); } } /// /// Validates whether or not the provided XML document is empty. /// /// The XML document to validate. /// The file path the XML document is loaded from. /// Thrown when the provided XML document does not contain configuration items. private static void ValidateNotEmpty(XDocument document, string xmlFilePath) { if (!document.Descendants() .Any(d => d.Name == ConfigurationSchemaIdentifiers.CalculationElement || d.Name == ConfigurationSchemaIdentifiers.FolderElement)) { string message = new FileReaderErrorMessageBuilder(xmlFilePath) .Build(Resources.ConfigurationReader_No_configuration_items_found); throw new CriticalFileReadException(message); } } private IEnumerable ParseElements(IEnumerable elements) { foreach (XElement element in elements) { if (element.Name == ConfigurationSchemaIdentifiers.CalculationElement) { yield return ParseCalculationElement(element); } if (element.Name == ConfigurationSchemaIdentifiers.FolderElement) { yield return ParseFolderElement(element); } } } private ReadCalculationGroup ParseFolderElement(XElement folderElement) { return new ReadCalculationGroup(folderElement.Attribute(ConfigurationSchemaIdentifiers.NameAttribute)?.Value, ParseElements(folderElement.Elements())); } } }