/// <summary>
        /// Locates files to attach using the appropriate file location strategy.
        /// </summary>
        /// <param name="objectSelector">The selector being executed.</param>
        /// <param name="xmlFile">The XML file being imported.</param>
        /// <param name="matchingElement">The current node, the context of which will be used to locate files if appropriate.</param>
        /// <param name="xmlNamespaceManager">Manager used to hold any XML namespace prefixes that are in use.</param>
        /// <returns>The files that should be attached to the object (or attached to the new object).</returns>
        public SourceObjectFiles FindFilesToAttach(
            ObjectSelector objectSelector,
            FileInfo xmlFile,
            XNode matchingElement,
            XmlNamespaceManager xmlNamespaceManager
            )
        {
            // Create a collection for the attached files.
            var sourceObjectFiles = new SourceObjectFiles();

            // Identify the file(s) to attach using the FileLocationStrategy.
            switch (objectSelector.AttachedFileConfiguration.FileLocationStrategy)
            {
            // "Invoice1.xml" should use "Invoice1.pdf".
            case FileLocationStrategy.LookForFileWithSameName:
            {
                // Attempt to find the file.
                var attachedFile = new FileInfo(Path.Combine(
                                                    xmlFile.DirectoryName,
                                                    xmlFile.Name.Substring(0, xmlFile.Name.Length - xmlFile.Extension.Length)
                                                    + objectSelector.AttachedFileConfiguration?.ExpectedFileExtension ?? ".pdf"
                                                    ));

                // Attach it if it exists.
                if (attachedFile.Exists)
                {
                    sourceObjectFiles.Add(-1, new SourceObjectFile()
                        {
                            Extension      = attachedFile.Extension.Substring(1),
                            SourceFilePath = attachedFile.FullName,
                            Title          = attachedFile.Name.Substring(0, attachedFile.Name.IndexOf(attachedFile.Extension))
                        });
                }
            }
            break;

            // Use XPath to find an element or attribute that contains the file name.
            case FileLocationStrategy.UseXPathQueryToLocateFileName:
            {
                // Evaluate the XPath query
                List <string> fileNames = new List <string>();
                var           results   = matchingElement
                                          .XPathEvaluate(objectSelector.AttachedFileConfiguration.XPathQueryToLocateFile, xmlNamespaceManager);
                if (results == null)
                {
                    break;
                }
                if (results is IEnumerable)
                {
                    // Iterate over the items returned.
                    foreach (var item in ((IEnumerable)results).Cast <XObject>())
                    {
                        // If it's an attribute then add the value.
                        if (item is XAttribute)
                        {
                            fileNames.Add(((XAttribute)item).Value);
                        }
                        else if (item is XElement)
                        {
                            fileNames.Add(((XElement)item).Value);
                        }
                        else
                        {
                            throw new InvalidOperationException(
                                      $"The XPath expression {objectSelector.AttachedFileConfiguration.XPathQueryToLocateFile} returned something other than an element or attribute.");
                        }
                    }
                }
                else
                {
                    // It is a simple value; retrieve it as a string.
                    fileNames.Add(results.ToString());
                }

                // Iterate over the file names that match the XPath query.
                foreach (var fileName in fileNames)
                {
                    FileInfo attachedFile = null;
                    // Find the file on disk.
                    if (System.IO.Path.IsPathRooted(fileName))
                    {
                        // It is a full path.
                        attachedFile = new FileInfo(fileName);
                    }
                    else
                    {
                        // It is relative.
                        attachedFile = new FileInfo(System.IO.Path.Combine(
                                                        xmlFile.DirectoryName,
                                                        fileName));
                    }

                    // Attach it if it exists.
                    if (attachedFile.Exists)
                    {
                        sourceObjectFiles.Add(-1, new SourceObjectFile()
                            {
                                Extension      = attachedFile.Extension.Substring(1),
                                SourceFilePath = attachedFile.FullName,
                                Title          = attachedFile.Name.Substring(0, attachedFile.Name.IndexOf(attachedFile.Extension))
                            });
                    }
                }
            }
            break;
            }

            // Return the source files.
            return(sourceObjectFiles);
        }
        /// <summary>
        /// Executes the <see cref="ObjectSelector"/> rule against the <see cref="XNode"/>,
        /// importing matching objects.
        /// </summary>
        /// <param name="vault">The vault reference to use for processing the import.</param>
        /// <param name="node">The node to import data from.</param>
        /// <param name="objectSelector">The selector to execute.</param>
        /// <param name="xmlFile">The information about the XML file being imported.</param>
        /// <param name="parent">A parent object to create a relationship to, if appropriate.</param>
        /// <param name="xmlNamespaceManager">A namespace manager for using XML prefixes in XPath statements.</param>
        /// <returns>A list of files which were attached to the object, for deletion.</returns>
        public List <FileInfo> ImportXmlFile(
            Vault vault,
            XNode node,
            ObjectSelector objectSelector,
            FileInfo xmlFile = null,
            ObjVer parent    = null,
            XmlNamespaceManager xmlNamespaceManager = null)
        {
            // Sanity.
            if (vault == null)
            {
                throw new ArgumentNullException(nameof(vault));
            }
            if (null == node)
            {
                throw new ArgumentNullException(nameof(node));
            }
            if (null == objectSelector)
            {
                throw new ArgumentNullException(nameof(objectSelector));
            }
            if (string.IsNullOrWhiteSpace(objectSelector.XPathQuery))
            {
                throw new ArgumentException("The XPath query for the object was empty", nameof(objectSelector));
            }
            if (null == objectSelector.PropertySelectors)
            {
                throw new ArgumentException("The object selector contained no property selectors.", nameof(objectSelector));
            }
            if (false == objectSelector.ObjectType.IsResolved)
            {
                throw new InvalidOperationException("The object selector object type is not resolved");
            }
            if (false == objectSelector.Class.IsResolved)
            {
                throw new InvalidOperationException("The object selector class is not resolved");
            }

            // Create a list of attached files (which can then be deleted later).
            var attachedFilesToDelete = new List <FileInfo>();

            // Create the namespace manager.
            if (null != xmlNamespaceManager)
            {
                // Copy data from the other manager (so we don't accidentally affect other queries).
                var xmlNamespaceManager2 = new XmlNamespaceManager(new NameTable());
                foreach (string prefix in xmlNamespaceManager)
                {
                    // Don't add default.
                    if (string.IsNullOrWhiteSpace(prefix))
                    {
                        continue;
                    }
                    if (prefix == "xsi")
                    {
                        continue;
                    }
                    if (prefix == "xmlns")
                    {
                        continue;
                    }

                    // Add.
                    xmlNamespaceManager2.AddNamespace(prefix, xmlNamespaceManager.LookupNamespace(prefix));
                }
                xmlNamespaceManager = xmlNamespaceManager2;
            }
            else
            {
                xmlNamespaceManager = new XmlNamespaceManager(new NameTable());
            }

            // Populate the namespace manager.
            if (null != objectSelector.XmlNamespaces)
            {
                foreach (var ns in objectSelector.XmlNamespaces)
                {
                    // If the namespace manager already contains a prefix then remove it.
                    string existingPrefix = xmlNamespaceManager.LookupPrefix(ns.Uri);
                    if (false == string.IsNullOrEmpty(existingPrefix))
                    {
                        xmlNamespaceManager.RemoveNamespace(existingPrefix, ns.Uri);
                    }

                    xmlNamespaceManager.AddNamespace(ns.Prefix, ns.Uri);
                }
            }

            // Find matching nodes.
            foreach (var matchingElement in node.XPathSelectElements(objectSelector.XPathQuery, xmlNamespaceManager))
            {
                // Hold all the properties being read.
                var propertyValuesBuilder = new MFPropertyValuesBuilder(vault);

                // Add the class property value.
                propertyValuesBuilder.SetClass(objectSelector.Class.ID);

                // Retrieve the properties.
                foreach (var propertySelector in objectSelector.PropertySelectors)
                {
                    // Sanity.
                    if (string.IsNullOrWhiteSpace(propertySelector.XPathQuery))
                    {
                        throw new ArgumentException("The object selector contained no property selectors.", nameof(objectSelector));
                    }
                    if (false == propertySelector.PropertyDef.IsResolved)
                    {
                        throw new InvalidOperationException("The property value selector property definition is not resolved");
                    }

                    // Retrieve the element for the property value.
                    // var matchingPropertyElement = matchingElement
                    //	.XPathSelectElement(propertySelector.XPathQuery, xmlNamespaceManager);
                    //if (null == matchingPropertyElement)
                    //	continue;

                    // Find the property definition type.
                    var propertyDefType = vault
                                          .PropertyDefOperations
                                          .GetPropertyDef(propertySelector.PropertyDef.ID)
                                          .DataType;

                    // Check if it's lookup or multilookup
                    var isLookup = ((propertyDefType == MFDataType.MFDatatypeMultiSelectLookup) || (propertyDefType == MFDataType.MFDatatypeLookup));

                    #region itterate XAttributes from XPath
                    if (propertySelector.XPathQuery.Contains("@"))
                    {
                        List <int>  listLookup = new List <int>();
                        IEnumerable matchingPropertyAttributes =
                            (IEnumerable)matchingElement.XPathEvaluate(propertySelector.XPathQuery);
                        foreach (System.Xml.Linq.XAttribute matchingPropertyAttribute in matchingPropertyAttributes)
                        {
                            string szValue = matchingPropertyAttribute.Value;

                            if (propertyDefType == MFDataType.MFDatatypeBoolean)
                            {
                                propertyValuesBuilder.Add(
                                    propertySelector.PropertyDef.ID,
                                    propertyDefType,
                                    CastToBool(szValue));
                            }
                            else if (propertyDefType == MFDataType.MFDatatypeDate)
                            {
                                szValue = $"{szValue} 00:00:00";
                                propertyValuesBuilder.Add(
                                    propertySelector.PropertyDef.ID,
                                    propertyDefType,
                                    szValue);
                            }
                            else if (isLookup)
                            {
                                var iLookupDef = (propertySelector.LookupOrValuelistStrategy == LookupOrValuelistStrategy.SearchLookup ?
                                                  propertySelector.LookupObjectDef.ID :
                                                  propertySelector.LookupValueListDef.ID);

                                var iLookupItem = LookupRef(vault,
                                                            iLookupDef,
                                                            propertyDefType,
                                                            propertySelector.SearchByLookupID,
                                                            szValue,
                                                            propertySelector.LookupOrValuelistStrategy);

                                if (iLookupItem != -1)
                                {
                                    listLookup.Add(iLookupItem);
                                }
                            }
                            else
                            {
                                propertyValuesBuilder.Add(
                                    propertySelector.PropertyDef.ID,
                                    propertyDefType,
                                    szValue);
                            }
                        }

                        // Lookup or MultiSelectLookup and found something
                        if ((isLookup) && (listLookup.Count != 0))
                        {
                            int[] arrLookupIDs = listLookup.ToArray();
                            propertyValuesBuilder.Add(
                                propertySelector.PropertyDef.ID,
                                propertyDefType,
                                arrLookupIDs);
                        }
                    }
                    #endregion
                    else
                    #region itterate XElements from XPath
                    {
                        List <int> listLookup = new List <int>();
                        var        matchingPropertyElements =
                            matchingElement.XPathSelectElements(propertySelector.XPathQuery);
                        if (null == matchingPropertyElements)
                        {
                            continue;
                        }

                        // iterate found XElements
                        foreach (var matchingPropertyElement in matchingPropertyElements)
                        {
                            if (null == matchingPropertyElement)
                            {
                                continue;
                            }

                            string szValue = matchingPropertyElement.Value;

                            if (propertyDefType == MFDataType.MFDatatypeBoolean)
                            {
                                propertyValuesBuilder.Add(
                                    propertySelector.PropertyDef.ID,
                                    propertyDefType,
                                    CastToBool(szValue));
                            }
                            else if (propertyDefType == MFDataType.MFDatatypeDate)
                            {
                                szValue = $"{szValue} 00:00:00";
                                propertyValuesBuilder.Add(
                                    propertySelector.PropertyDef.ID,
                                    propertyDefType,
                                    szValue);
                            }
                            else if (isLookup)
                            {
                                var iLookupDef = (propertySelector.LookupOrValuelistStrategy == LookupOrValuelistStrategy.SearchLookup ?
                                                  propertySelector.LookupObjectDef.ID :
                                                  propertySelector.LookupValueListDef.ID);

                                var iLookupItem = LookupRef(vault,
                                                            iLookupDef,
                                                            propertyDefType,
                                                            propertySelector.SearchByLookupID,
                                                            szValue,
                                                            propertySelector.LookupOrValuelistStrategy);

                                if (iLookupItem != -1)
                                {
                                    listLookup.Add(iLookupItem);
                                }
                                propertyValuesBuilder.AddLookup(
                                    propertySelector.PropertyDef.ID,
                                    szValue);
                            }
                            else
                            {
                                propertyValuesBuilder.Add(
                                    propertySelector.PropertyDef.ID,
                                    propertyDefType,
                                    szValue);
                            }
                        }
                        // Lookup or MultiSelectLookup and found something
                        if ((isLookup) && (listLookup.Count != 0))
                        {
                            int[] arrLookupIDs = listLookup.ToArray();
                            propertyValuesBuilder.Add(
                                propertySelector.PropertyDef.ID,
                                propertyDefType,
                                arrLookupIDs);
                        }
                    }
                    #endregion

                    // Add the property to the builder.
                    //propertyValuesBuilder.Add(
                    //propertySelector.PropertyDef.ID,
                    //propertyDefType,
                    //matchingPropertyElement.Value);
                }

                // Set the static values
                foreach (var staticPropertyValue in objectSelector.StaticPropertyValues ?? new List <StaticPropertyValue>())
                {
                    // Sanity.
                    if (false == staticPropertyValue.PropertyDef.IsResolved)
                    {
                        throw new InvalidOperationException("The property value selector property definition is not resolved");
                    }

                    // Find the property definition type.
                    var propertyDefType = vault
                                          .PropertyDefOperations
                                          .GetPropertyDef(staticPropertyValue.PropertyDef.ID)
                                          .DataType;

                    // Add the property to the builder.
                    propertyValuesBuilder.Add(
                        staticPropertyValue.PropertyDef.ID,
                        propertyDefType,
                        staticPropertyValue.Value);
                }


                // Create a reference to the parent?
                if (null != parent)
                {
                    // If the property definition to use was configured then use that.
                    if (true == objectSelector.ParentRelationshipPropertyDef?.IsResolved)
                    {
                        // Check that this property is a list and is for the correct object type.
                        var parentRelationshipPropertyDef = vault
                                                            .PropertyDefOperations
                                                            .GetPropertyDef(objectSelector.ParentRelationshipPropertyDef.ID);
                        if (false == parentRelationshipPropertyDef.BasedOnValueList ||
                            parentRelationshipPropertyDef.ValueList != parent.Type)
                        {
                            throw new InvalidOperationException(
                                      $"The property def {parentRelationshipPropertyDef.Name} ({parentRelationshipPropertyDef.ID}) is not based on value list {parent.Type}.");
                        }

                        // Use the configured property definition.
                        propertyValuesBuilder.Add(
                            parentRelationshipPropertyDef.ID,
                            parentRelationshipPropertyDef.DataType,
                            parent.ID);
                    }
                    else
                    {
                        // Retrieve data about the parent object type.
                        var parentObjectType = vault
                                               .ObjectTypeOperations
                                               .GetObjectType(parent.Type);

                        // Retrieve data about the child object type.
                        var childObjectType = vault
                                              .ObjectTypeOperations
                                              .GetObjectType(objectSelector.ObjectType.ID);

                        // Is there an owner for this child type?
                        if (childObjectType.HasOwnerType)
                        {
                            // Use the "owner" property definition.
                            propertyValuesBuilder.Add(
                                parentObjectType.OwnerPropertyDef,
                                MFDataType.MFDatatypeLookup,
                                parent.ID);
                        }
                        else
                        {
                            // Use the default property definition.
                            propertyValuesBuilder.Add(
                                parentObjectType.DefaultPropertyDef,
                                MFDataType.MFDatatypeMultiSelectLookup,
                                parent.ID);
                        }
                    }
                }

                // Create a container for any attached files.
                var sourceObjectFiles = new SourceObjectFiles();

                // Should we attach the file to this object?
                if (objectSelector.AttachFileToThisObject)
                {
                    // Locate the files to retrieve.
                    sourceObjectFiles = this.FindFilesToAttach(objectSelector, xmlFile, matchingElement, xmlNamespaceManager);

                    // If we were supposed to attach a file but no files were found then throw an exception.
                    if (objectSelector.AttachedFileConfiguration?.FileNotFoundHandlingStrategy == FileNotFoundHandlingStrategy.Fail &&
                        0 == sourceObjectFiles.Count)
                    {
                        throw new InvalidOperationException("Attached file expected but not found.");
                    }

                    if (objectSelector.AttachedFileConfiguration?.AttachedFileHandlingStrategy
                        == AttachedFileHandlingStrategy.AttachToCurrentObject)
                    {
                        // Retrieve information about the object type from the vault.
                        var objectType = vault
                                         .ObjectTypeOperations
                                         .GetObjectType(objectSelector.ObjectType.ID);

                        // If the object type cannot have files but we are meant to attach a file, then fail.
                        if (false == objectType.CanHaveFiles)
                        {
                            throw new InvalidOperationException(
                                      $"The object type {objectType.NameSingular} cannot have files, but the configuration states to attach a file.");
                        }
                    }
                }

                // Which source object files should we use for the new object?
                var sourceObjectFilesForNewObject = objectSelector.AttachedFileConfiguration?.AttachedFileHandlingStrategy
                                                    == AttachedFileHandlingStrategy.AttachToCurrentObject
                                        ? sourceObjectFiles
                                        : new SourceObjectFiles();

                // Add the object to the vault.
                var createdObject = vault
                                    .ObjectOperations
                                    .CreateNewObjectEx(
                    objectSelector.ObjectType.ID,
                    propertyValuesBuilder.Values,
                    sourceObjectFilesForNewObject,
                    SFD: objectSelector.ObjectType.ID == (int)MFBuiltInObjectType.MFBuiltInObjectTypeDocument &&
                    sourceObjectFilesForNewObject.Count == 1
                    );

                // The files which need to be deleted.
                attachedFilesToDelete.AddRange(
                    sourceObjectFiles
                    .Cast <SourceObjectFile>()
                    .Select(sof => new FileInfo(sof.SourceFilePath))
                    );

                // Are there any related objects (e.g. children) to create?
                foreach (var childObjectSelector in objectSelector.ChildObjectSelectors)
                {
                    attachedFilesToDelete.AddRange(this.ImportXmlFile(vault,
                                                                      matchingElement,
                                                                      childObjectSelector,
                                                                      xmlFile: xmlFile,
                                                                      parent: createdObject.ObjVer,
                                                                      xmlNamespaceManager: xmlNamespaceManager));
                }

                // Clean up the collections we were using.
                propertyValuesBuilder = new MFPropertyValuesBuilder(vault);

                // Handle creating a new object for the file.
                if (
                    objectSelector.AttachFileToThisObject &&
                    objectSelector.AttachedFileConfiguration?.AttachedFileHandlingStrategy
                    == AttachedFileHandlingStrategy.CreateNewObject)
                {
                    // Set the static values
                    foreach (var staticPropertyValue in objectSelector.AttachedFileConfiguration?.StaticPropertyValues ?? new List <StaticPropertyValue>())
                    {
                        // Sanity.
                        if (false == staticPropertyValue.PropertyDef.IsResolved)
                        {
                            throw new InvalidOperationException("The property value selector property definition is not resolved");
                        }

                        // Find the property definition type.
                        var propertyDefType = vault
                                              .PropertyDefOperations
                                              .GetPropertyDef(staticPropertyValue.PropertyDef.ID)
                                              .DataType;

                        // Add the property to the builder.
                        propertyValuesBuilder.Add(
                            staticPropertyValue.PropertyDef.ID,
                            propertyDefType,
                            staticPropertyValue.Value);
                    }

                    // Add the class property value.
                    propertyValuesBuilder.SetClass(objectSelector.AttachedFileConfiguration.Class.ID);

                    // Add a reference from this new object to the one we created earlier.
                    {
                        // Retrieve data about the parent object type.
                        var parentObjectType = vault
                                               .ObjectTypeOperations
                                               .GetObjectType(createdObject.ObjVer.Type);

                        // Set the relationship.
                        propertyValuesBuilder.Add(
                            parentObjectType.DefaultPropertyDef,
                            MFDataType.MFDatatypeMultiSelectLookup,
                            createdObject.ObjVer.ID);
                    }

                    // Add the object to the vault.
                    var createdDocumentObject = vault
                                                .ObjectOperations
                                                .CreateNewObjectEx(
                        objectSelector.AttachedFileConfiguration.ObjectType.ID,
                        propertyValuesBuilder.Values,
                        sourceObjectFiles,
                        SFD: objectSelector.AttachedFileConfiguration.ObjectType.ID == (int)MFBuiltInObjectType.MFBuiltInObjectTypeDocument &&
                        sourceObjectFiles.Count == 1
                        );
                }
            }

            // Return the files to remove.
            return(attachedFilesToDelete);
        }