private void preprocessAndValidateDocument(DOMDocument xmlDocument) {
        if (xmlDocument.getTextDocument() == null || xmlDocument.getTextDocument().getUri() == null) {
            logger.warning("Could not get document URI. Preprocessing will not be performed.");
            return;
        }
        URI documentURI = URI.create(xmlDocument.getTextDocument().getUri());
        File applicationDir = new File(documentURI).getParentFile();

        // The settings for the preprocessor are currently set arbitrarily.
        // They do not affect much of the diagnostics.
        // With more intelligent LSP from preprocessing a user could set these values somewhere 
        // and preview the modified XML.
        XmlPreProcessor preProcessor = new XmlPreProcessor(
            applicationDir,
            new StringReader(xmlDocument.getTextDocument().getText()),
            InstanceName.defaultName(),
            Environment.dev,
            RegionName.defaultName(),
            CloudName.DEFAULT,
            Tags.empty()
        );

        try {
            Document preprocessed = preProcessor.run();
            schemaValidationDriver.validate(new InputSource(new StringReader(documentAsString(preprocessed, false))));
        } catch (SAXException saxException) {
            // Should we check if it is instance of SAXParseException?
            validationErrorSet.add(saxException.getMessage().trim());
        } catch (Exception ex) {
            // Most exceptions are processed by CustomErrorHandler
            logger.warning("Exception occured during Vespa XML validation: " + ex.getMessage() + ", TYPE: " + ex.getClass().toString());
            return;
        }
    }
DiagnosticsParticipant that performs validation after Vespa Preprocessing.
Due to Lemminx' implementation of the DOM and parser, it seems impossible to run 
preprocessing on a parsed DOM. This is sad because it means we cannot connect preprocessing 
errors to their locations in the document as it was prior to preprocessing.

For now the workaround is to remove existing diagnostics (from Lemminx validation) that no longer
exist after the preprocessing step. To identify equal diagnostics, string hashing of the 
error messages is used, because that is the only information left from the {@link SAXParseException}
objects after Lemminx has validated.

This strategy makes the validation "nicer" than it should. Some errors will be removed
even though they are legit due to either string mismatch or because the choice of deployment 
settings is arbitrary at the moment. This is at the benefit of not showing errors on valid services.xml.
integration/schema-language-server/lemminx-vespa/src/main/java/ai/vespa/lemminx/participants/DiagnosticsParticipant.java