// Copyright (C) Stichting Deltares 2017. 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.Collections.ObjectModel; using System.IO; using System.Xml; using System.Xml.Linq; using System.Xml.Schema; using System.Xml.XPath; using Core.Common.Base.Geometry; using Ringtoets.Common.IO.Exceptions; using Ringtoets.Common.IO.Properties; namespace Ringtoets.Common.IO.SoilProfile { /// /// This class is responsible for reading an array of bytes and interpret this as an XML document, /// which contains information about the geometry of a soil layer. /// internal class SoilLayer2DGeometryReader { private const string outerLoopElementName = "OuterLoop"; private const string innerLoopsElementName = "InnerLoops"; private const string innerLoopElementName = "GeometryLoop"; private const string endPointElementName = "EndPoint"; private const string headPointElementName = "HeadPoint"; private const string geometryCurveElementName = "GeometryCurve"; private const string xElementName = "X"; private const string zElementName = "Z"; private readonly XmlSchemaSet schema; /// /// Creates a new instance of . /// public SoilLayer2DGeometryReader() { schema = LoadXmlSchema(); } /// /// Reads a new using the as the source of the /// geometry for a soil layer. /// /// An of which contains the information /// of a soil layer in an XML document. /// A new with information taken from the XML document. /// Thrown when is null. /// Thrown when: /// /// is not valid XML. /// does not pass schema validation. /// /// /// public SoilLayer2D Read(byte[] geometry) { if (geometry == null) { throw new ArgumentNullException(nameof(geometry), Resources.SoilLayer2DGeometryReader_Geometry_is_null); } try { using (var stream = new MemoryStream(geometry)) { return Read(XDocument.Load(stream)); } } catch (XmlException e) { throw new SoilLayerConversionException(Resources.SoilLayer2DGeometryReader_Geometry_contains_no_valid_xml, e); } } /// /// Reads a new using the . /// /// An which contains the information /// of a soil layer in an XML document. /// A new with information taken from the XML document. /// Thrown when is null. /// Thrown when: /// /// is not valid XML. /// does not pass schema validation. /// /// /// public SoilLayer2D Read(XDocument geometry) { if (geometry == null) { throw new ArgumentNullException(nameof(geometry)); } ValidateToSchema(geometry); return ParseLayer(geometry); } /// /// Validates the to the . /// /// The to validate. /// Thrown when the validation failed. private void ValidateToSchema(XDocument document) { try { document.Validate(schema, null); } catch (XmlSchemaValidationException e) { throw new SoilLayerConversionException(Resources.SoilLayer2DGeometryReader_Geometry_contains_no_valid_xml, e); } } private static XmlSchemaSet LoadXmlSchema() { var xmlSchemaSet = new XmlSchemaSet(); xmlSchemaSet.Add(XmlSchema.Read(new StringReader(Resources.XmlGeometrySchema), null)); return xmlSchemaSet; } /// /// Parses the XML element to create a 2D soil layer. /// /// The geometry. /// XML for inner or outer geometry loops is invalid. private static SoilLayer2D ParseLayer(XDocument geometry) { var soilLayer = new SoilLayer2D(); XElement xmlOuterLoop = geometry.XPathSelectElement($"//{outerLoopElementName}"); IEnumerable xmlInnerLoops = geometry.XPathSelectElements($"//{innerLoopsElementName}//{innerLoopElementName}"); if (xmlOuterLoop != null) { soilLayer.OuterLoop = ParseGeometryLoop(xmlOuterLoop); } foreach (XElement loop in xmlInnerLoops) { soilLayer.AddInnerLoop(ParseGeometryLoop(loop)); } return soilLayer; } /// /// Parses the XML element to create a collection of describing /// a geometric loop. /// /// The geometric loop element. /// XML for any geometry curve is invalid. private static IEnumerable ParseGeometryLoop(XElement loop) { var loops = new Collection(); IEnumerable curves = loop.XPathSelectElements($".//{geometryCurveElementName}"); foreach (XElement curve in curves) { loops.Add(ParseGeometryCurve(curve)); } return loops; } /// /// Parses the XML element to create a . /// /// The geometry curve element. /// XML for geometry curve is invalid. private static Segment2D ParseGeometryCurve(XElement curve) { XElement headDefinition = curve.Element(headPointElementName); XElement endDefinition = curve.Element(endPointElementName); if (headDefinition == null || endDefinition == null) { throw new SoilLayerConversionException(Resources.SoilLayer2DGeometryReader_Geometry_contains_no_valid_xml); } return new Segment2D(ParsePoint(headDefinition), ParsePoint(endDefinition)); } /// /// Parses the XML element to create a . /// /// The 2D point element. /// Thrown when any of the following occurs: /// /// A coordinate value cannot be parsed. /// XML for 2D point is invalid. /// private static Point2D ParsePoint(XElement point) { XElement xElement = point.Element(xElementName); XElement yElement = point.Element(zElementName); if (xElement == null || yElement == null) { throw new SoilLayerConversionException(Resources.SoilLayer2DGeometryReader_Geometry_contains_no_valid_xml); } try { double x = XmlConvert.ToDouble(xElement.Value); double y = XmlConvert.ToDouble(yElement.Value); return new Point2D(x, y); } catch (SystemException e) when (e is ArgumentNullException || e is FormatException || e is OverflowException) { throw new SoilLayerConversionException(Resources.SoilLayer2DGeometryReader_Could_not_parse_point_location, e); } } } }