// 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.Base.IO; 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 CalculationConfigurationReader where TReadCalculation : IReadConfigurationItem { private const string defaultSchemaName = "ConfiguratieSchema.xsd"; private readonly XDocument xmlDocument; /// /// Creates a new instance of . /// /// The file path to the XML file. /// A string representing the main schema definition. /// A containing /// zero to more nested schema definitions. The keys should represent unique file names by which /// the schema definitions can be referenced from ; the /// values should represent their corresponding schema definition string. /// Thrown when /// is null. /// Thrown when: /// /// is invalid. /// is invalid. /// contains invalid schema definition values. /// , all together with its referenced /// , contains an invalid schema definition. /// contains schema definitions that are not /// referenced by . /// does not reference the default schema definition /// ConfiguratieSchema.xsd. /// /// /// 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. /// points to a file that does not contain configuration elements. /// /// protected CalculationConfigurationReader(string xmlFilePath, string mainSchemaDefinition, IDictionary nestedSchemaDefinitions) { IOUtils.ValidateFilePath(xmlFilePath); ValidateFileExists(xmlFilePath); xmlDocument = LoadDocument(xmlFilePath); ValidateToSchema(xmlDocument, xmlFilePath, mainSchemaDefinition, nestedSchemaDefinitions); 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 schema definitions. /// /// The XML document to validate. /// The file path the XML document is loaded from. /// A string representing the main schema definition. /// A containing /// zero to more nested schema definitions /// Thrown when the provided XML document does not match /// the provided schema definitions. /// Thrown when does not /// reference the default schema definition ConfiguratieSchema.xsd. private static void ValidateToSchema(XDocument document, string xmlFilePath, string mainSchemaDefinition, IDictionary nestedSchemaDefinitions) { if (!mainSchemaDefinition.Contains(defaultSchemaName)) { throw new ArgumentException($"'{nameof(mainSchemaDefinition)}' does not reference the default schema '{defaultSchemaName}'."); } var combinedXmlSchemaDefinition = new CombinedXmlSchemaDefinition( mainSchemaDefinition, nestedSchemaDefinitions.Concat(new[] { new KeyValuePair(defaultSchemaName, Resources.ConfiguratieSchema) }) .ToDictionary(kv => kv.Key, kv => kv.Value)); try { combinedXmlSchemaDefinition.Validate(document); } catch (XmlSchemaValidationException exception) { string message = string.Format(Resources.CalculationConfigurationReader_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 the provided XML document is not 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.CalculationConfigurationReader_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())); } } }