2022-08-10 16:03:37 +00:00
// Copyright Epic Games, Inc. All Rights Reserved.
using System.Diagnostics.CodeAnalysis ;
using System.Xml ;
using System.Xml.Schema ;
using EpicGames.Core ;
using Microsoft.Extensions.Logging ;
namespace UnrealBuildTool
{
/// <summary>
/// Implementation of XmlDocument which preserves line numbers for its elements
/// </summary>
class XmlConfigFile : XmlDocument
{
/// <summary>
/// Root element for the XML document
/// </summary>
public const string RootElementName = "Configuration" ;
/// <summary>
/// Namespace for the XML schema
/// </summary>
public const string SchemaNamespaceURI = "https://www.unrealengine.com/BuildConfiguration" ;
/// <summary>
/// The file being read
/// </summary>
2023-12-21 18:56:31 -05:00
readonly FileReference _file ;
2022-08-10 16:03:37 +00:00
/// <summary>
/// Interface to the LineInfo on the active XmlReader
/// </summary>
2023-12-21 18:56:31 -05:00
IXmlLineInfo _lineInfo = null ! ;
2022-08-10 16:03:37 +00:00
/// <summary>
/// Set to true if the reader encounters an error
/// </summary>
2023-12-21 18:56:31 -05:00
bool _bHasErrors ;
2022-08-10 16:03:37 +00:00
/// <summary>
/// Private constructor. Use XmlConfigFile.TryRead to read an XML config file.
/// </summary>
2023-12-21 18:56:31 -05:00
private XmlConfigFile ( FileReference inFile )
2022-08-10 16:03:37 +00:00
{
2023-12-21 18:56:31 -05:00
_file = inFile ;
2022-08-10 16:03:37 +00:00
}
/// <summary>
/// Overrides XmlDocument.CreateElement() to construct ScriptElements rather than XmlElements
/// </summary>
2023-12-21 18:56:31 -05:00
public override XmlElement CreateElement ( string? prefix , string localName , string? namespaceUri ) = > new XmlConfigFileElement ( _file , _lineInfo . LineNumber , prefix ! , localName , namespaceUri , this ) ;
2022-08-10 16:03:37 +00:00
/// <summary>
/// Loads a script document from the given file
/// </summary>
2023-12-21 18:56:31 -05:00
/// <param name="file">The file to load</param>
/// <param name="schema">The schema to validate against</param>
/// <param name="logger">Logger for output</param>
/// <param name="outConfigFile">If successful, the document that was read</param>
2022-08-10 16:03:37 +00:00
/// <returns>True if the document could be read, false otherwise</returns>
2023-12-21 18:56:31 -05:00
public static bool TryRead ( FileReference file , XmlSchema schema , ILogger logger , [ NotNullWhen ( true ) ] out XmlConfigFile ? outConfigFile )
2022-08-10 16:03:37 +00:00
{
2023-12-21 18:56:31 -05:00
XmlConfigFile configFile = new XmlConfigFile ( file ) ;
2022-08-10 16:03:37 +00:00
2023-12-21 18:56:31 -05:00
XmlReaderSettings settings = new XmlReaderSettings ( ) ;
settings . Schemas . Add ( schema ) ;
settings . ValidationType = ValidationType . Schema ;
settings . ValidationEventHandler + = configFile . ValidationEvent ;
2022-08-10 16:03:37 +00:00
2023-12-21 18:56:31 -05:00
using ( XmlReader reader = XmlReader . Create ( file . FullName , settings ) )
2022-08-10 16:03:37 +00:00
{
// Read the document
2023-12-21 18:56:31 -05:00
configFile . _lineInfo = ( IXmlLineInfo ) reader ;
2022-08-10 16:03:37 +00:00
try
{
2023-12-21 18:56:31 -05:00
configFile . Load ( reader ) ;
2022-08-10 16:03:37 +00:00
}
2023-12-21 18:56:31 -05:00
catch ( XmlException ex )
2022-08-10 16:03:37 +00:00
{
2023-12-21 18:56:31 -05:00
if ( ! configFile . _bHasErrors )
2022-08-10 16:03:37 +00:00
{
2023-12-21 18:56:31 -05:00
Log . TraceErrorTask ( file , ex . LineNumber , "{0}" , ex . Message ) ;
configFile . _bHasErrors = true ;
2022-08-10 16:03:37 +00:00
}
}
// If we hit any errors while parsing
2023-12-21 18:56:31 -05:00
if ( configFile . _bHasErrors )
2022-08-10 16:03:37 +00:00
{
2023-12-21 18:56:31 -05:00
outConfigFile = null ;
2022-08-10 16:03:37 +00:00
return false ;
}
// Check that the root element is valid. If not, we didn't actually validate against the schema.
2023-12-21 18:56:31 -05:00
if ( configFile . DocumentElement ? . Name ! = RootElementName )
2022-08-10 16:03:37 +00:00
{
2023-12-21 18:56:31 -05:00
logger . LogError ( "Script does not have a root element called '{RootElementName}'" , RootElementName ) ;
outConfigFile = null ;
2022-08-10 16:03:37 +00:00
return false ;
}
2023-12-21 18:56:31 -05:00
if ( configFile . DocumentElement . NamespaceURI ! = SchemaNamespaceURI )
2022-08-10 16:03:37 +00:00
{
2023-12-21 18:56:31 -05:00
logger . LogError ( "Script root element is not in the '{NamespaceUri}' namespace (add the xmlns=\"{NamespaceUri2}\" attribute)" , SchemaNamespaceURI , SchemaNamespaceURI ) ;
outConfigFile = null ;
2022-08-10 16:03:37 +00:00
return false ;
}
}
2023-12-21 18:56:31 -05:00
outConfigFile = configFile ;
2022-08-10 16:03:37 +00:00
return true ;
}
/// <summary>
/// Callback for validation errors in the document
/// </summary>
2023-12-21 18:56:31 -05:00
/// <param name="sender">Standard argument for ValidationEventHandler</param>
/// <param name="args">Standard argument for ValidationEventHandler</param>
void ValidationEvent ( object? sender , ValidationEventArgs args ) = > Log . TraceWarningTask ( _file , args . Exception . LineNumber , "{0}" , args . Message ) ;
2022-08-10 16:03:37 +00:00
}
/// <summary>
/// Implementation of XmlElement which preserves line numbers
/// </summary>
class XmlConfigFileElement : XmlElement
{
/// <summary>
/// The file containing this element
/// </summary>
2023-12-21 18:56:31 -05:00
public FileReference File { get ; init ; }
2022-08-10 16:03:37 +00:00
/// <summary>
/// The line number containing this element
/// </summary>
2023-12-21 18:56:31 -05:00
public int LineNumber { get ; init ; }
2022-08-10 16:03:37 +00:00
/// <summary>
/// Constructor
/// </summary>
2023-12-21 18:56:31 -05:00
public XmlConfigFileElement ( FileReference inFile , int inLineNumber , string prefix , string localName , string? namespaceUri , XmlConfigFile configFile )
: base ( prefix , localName , namespaceUri , configFile )
2022-08-10 16:03:37 +00:00
{
2023-12-21 18:56:31 -05:00
File = inFile ;
LineNumber = inLineNumber ;
2022-08-10 16:03:37 +00:00
}
}
}