diff --git a/XGenerate/src/main/java/com/xbreeze/xgenerate/generator/Generator.java b/XGenerate/src/main/java/com/xbreeze/xgenerate/generator/Generator.java index 1073ffe..6888411 100644 --- a/XGenerate/src/main/java/com/xbreeze/xgenerate/generator/Generator.java +++ b/XGenerate/src/main/java/com/xbreeze/xgenerate/generator/Generator.java @@ -117,10 +117,7 @@ public GenerationResult generateFromFiles(URI modelFileUri, URI templateFileUri, // Create the model object from the model file. Model model; try { - boolean namespaceAware = false; - if (xGenConfig.getModelConfig() != null) - namespaceAware = xGenConfig.getModelConfig().isNamespaceAware(); - model = Model.fromFile(modelFileUri, namespaceAware); + model = Model.fromFile(modelFileUri, xGenConfig.getModelConfig()); } catch (ModelException me) { throw new GeneratorException(me); } diff --git a/XGenerate/src/main/java/com/xbreeze/xgenerate/generator/XGenerateStarter.java b/XGenerate/src/main/java/com/xbreeze/xgenerate/generator/XGenerateStarter.java index 7b05815..61b358b 100644 --- a/XGenerate/src/main/java/com/xbreeze/xgenerate/generator/XGenerateStarter.java +++ b/XGenerate/src/main/java/com/xbreeze/xgenerate/generator/XGenerateStarter.java @@ -242,7 +242,12 @@ else if (fileLogLevel != null) { * @param debugMode Debug mode indicator. */ private void startGenerator(XGenAppConfig appConfig, ArrayList modelTemplateConfigCombinations, boolean debugMode) { - try { + try + { + // Configure Java XML to use Saxon as the DOM and XPath implementation. + System.setProperty("javax.xml.parsers.DocumentBuilderFactory", "net.sf.saxon.om.DocumentBuilderFactoryImpl"); + System.setProperty("javax.xml.xpath.XPathFactory", "net.sf.saxon.xpath.XPathFactoryImpl"); + // Notify the generation observers the generation is starting. this.notifyGenerationStarting(modelTemplateConfigCombinations.size(), LocalDateTime.now()); Generator generator = new Generator(); diff --git a/XGenerate/src/main/java/com/xbreeze/xgenerate/model/Model.java b/XGenerate/src/main/java/com/xbreeze/xgenerate/model/Model.java index 9b945e3..6c56134 100644 --- a/XGenerate/src/main/java/com/xbreeze/xgenerate/model/Model.java +++ b/XGenerate/src/main/java/com/xbreeze/xgenerate/model/Model.java @@ -24,15 +24,23 @@ *******************************************************************************/ package com.xbreeze.xgenerate.model; +import java.io.ByteArrayInputStream; import java.io.IOException; import java.net.URI; import java.nio.file.Paths; -import java.util.HashMap; import java.util.logging.Logger; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; + +import org.w3c.dom.Document; +import org.xml.sax.SAXException; + +import com.xbreeze.xgenerate.config.model.ModelConfig; import com.xbreeze.xgenerate.generator.GeneratorException; import com.xbreeze.xgenerate.utils.FileUtils; -import com.xbreeze.xgenerate.utils.XMLUtils; +import com.xbreeze.xgenerate.utils.SaxonXMLUtils; import com.xbreeze.xgenerate.utils.XmlException; public class Model { @@ -79,7 +87,7 @@ public String getModelFileContent() { * @return The Model object. * @throws GeneratorException */ - public static Model fromFile(URI modelFileUri, boolean namespaceAware) throws ModelException { + public static Model fromFile(URI modelFileUri, ModelConfig modelConfig) throws ModelException { logger.fine(String.format("Creating Model object from '%s'", modelFileUri)); // Read the model file content into a String. @@ -90,22 +98,42 @@ public static Model fromFile(URI modelFileUri, boolean namespaceAware) throws Mo throw new ModelException(String.format("Couldn't read the model file (%s): %s", modelFileUri, e.getMessage())); } - return fromString(modelFileContent, modelFileUri, namespaceAware); + return fromString(modelFileContent, modelFileUri, modelConfig); } /** - * Statis function to construct a Model object from a string. + * Static function to construct a Model object from a string. * @param modelFileUri * @param modelFileContents * @return * @throws ModelException */ - public static Model fromString(String modelFileContents, URI modelFileUri, boolean namespaceAware) throws ModelException { + public static Model fromString(String modelFileContents, URI modelFileUri, ModelConfig modelConfig) throws ModelException { String resolvedModelFileContents; try { - // Before constructing the model object, resolve any includes first - HashMap resolvedIncludes = new HashMap<>(); - resolvedModelFileContents = XMLUtils.getXmlWithResolvedIncludes(modelFileContents, modelFileUri, 0, resolvedIncludes, namespaceAware); + // Parse the XML document using Saxon. + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + if (modelConfig != null) + factory.setNamespaceAware(modelConfig.isNamespaceAware()); + factory.setXIncludeAware(true); + DocumentBuilder builder; + + try { + builder = factory.newDocumentBuilder(); + } catch (ParserConfigurationException exc) { + throw new ModelException( + String.format("Error while reading model XML file: %s", exc.getMessage())); + } + Document doc; + + try { + // Set the system-id to the location of the model file, so it can resolve includes, if needed. + doc = builder.parse(new ByteArrayInputStream(modelFileContents.getBytes()), modelFileUri.toString()); + } catch(SAXException | IOException exc) { + throw new ModelException(String.format("Error while reading model XML file: %s", exc.getMessage()), exc.getCause()); + } + + resolvedModelFileContents = SaxonXMLUtils.XmlDocumentToString(doc); } catch (XmlException xec) { throw new ModelException(String.format("Error while reading model: %s", xec.getMessage()), xec); } diff --git a/XGenerate/src/test/java/com/xbreeze/xgenerate/test/steps/XGenerateLibTestSteps.java b/XGenerate/src/test/java/com/xbreeze/xgenerate/test/steps/XGenerateLibTestSteps.java index 1c0050e..311bba2 100644 --- a/XGenerate/src/test/java/com/xbreeze/xgenerate/test/steps/XGenerateLibTestSteps.java +++ b/XGenerate/src/test/java/com/xbreeze/xgenerate/test/steps/XGenerateLibTestSteps.java @@ -11,7 +11,6 @@ import java.io.OutputStream; import java.io.PrintStream; import java.net.URI; -import java.nio.file.Path; import java.nio.file.Paths; import java.util.logging.ConsoleHandler; import java.util.logging.Filter; @@ -122,7 +121,7 @@ public boolean isLoggable(LogRecord record) { // Set the feature support file location using the scenario location. // The support file location is the same as the feature file location, but without the .feature extension and the directory 'features' is replaced with 'feature-support-files'. - String derivedFeatureSupportFileLocation = featureFileLocation.replaceFirst("features", "feature-support-files").replace(".feature", ""); + String derivedFeatureSupportFileLocation = featureFileLocation.replaceFirst("features", "feature-support-files").replace(".feature", "/"); logger.info(String.format("The feature-support-file location will be set to '%s'.", derivedFeatureSupportFileLocation)); _featureSupportFilesLocation = URI.create(derivedFeatureSupportFileLocation); @@ -149,11 +148,8 @@ private void RestoreSystemStreams() { @Given("^I have the following model:$") public void iHaveTheFollowingModel(String modelContent) throws Throwable { - boolean namespaceAware = false; - if (_xGenConfig != null) - namespaceAware = _xGenConfig.getModelConfig().isNamespaceAware(); try { - this._model = Model.fromString(modelContent, this._featureSupportFilesLocation, namespaceAware); + this._model = Model.fromString(modelContent, this._featureSupportFilesLocation.resolve("inline-model.xml"), (_xGenConfig != null) ? _xGenConfig.getModelConfig() : null); } catch (ModelException mex) { this.generatorException = mex; } @@ -204,7 +200,7 @@ else if (this._modelFileUri != null && this._templateFileUri != null && this._co _generationResults = this._generator.generateFromFiles(this._modelFileUri, _templateFileUri, this._configFileUri, this._outputFolderUri, ""); } else { - throw new GeneratorException ("Model ,template and config should all be specified as either content or file(URI) in the feature."); + throw new GeneratorException ("Model, template and config should all be specified as either content or file(URI) in the feature."); } if (_generationResults.getStatus().equals(GenerationStatus.ERROR)) { @@ -279,7 +275,7 @@ private void compareActualAndExpectedOutput(String outputName, String expectedRe } private URI resolveSupportFile(String relativeFileLocation) { - return Path.of(this._featureSupportFilesLocation).resolve(relativeFileLocation).toUri(); + return this._featureSupportFilesLocation.resolve(relativeFileLocation); } private String uriEncode(String uriPart) { diff --git a/XGenerate/src/test/resources/features/unit/config/Unit_Config_Xml_Include.feature b/XGenerate/src/test/resources/features/unit/config/Unit_Config_Xml_Include.feature index eef4fc2..107fa94 100644 --- a/XGenerate/src/test/resources/features/unit/config/Unit_Config_Xml_Include.feature +++ b/XGenerate/src/test/resources/features/unit/config/Unit_Config_Xml_Include.feature @@ -76,5 +76,5 @@ Feature: Unit_Config_Xml_Include """ Then I expect the following error message: """ - com.xbreeze.xgenerate.utils.XmlException: XML include cycle detected at level 3, file {{support-file-location}}/entityBindingWithIncludeNested.xml is already included previously + com.xbreeze.xgenerate.utils.XmlException: XML include cycle detected at level 3, file {{support-file-location}}entityBindingWithIncludeNested.xml is already included previously """ diff --git a/XGenerate/src/test/resources/features/unit/model/Unit_Model_Xml_Include.feature b/XGenerate/src/test/resources/features/unit/model/Unit_Model_Xml_Include.feature index 1683556..14b99d3 100644 --- a/XGenerate/src/test/resources/features/unit/model/Unit_Model_Xml_Include.feature +++ b/XGenerate/src/test/resources/features/unit/model/Unit_Model_Xml_Include.feature @@ -6,7 +6,7 @@ Feature: Unit_Model_Xml_Include Given the following config: """ - + @@ -43,22 +43,24 @@ Feature: Unit_Model_Xml_Include Then I expect 1 generation result And an output named "Unit_Config_Reuse_Partials.txt" with content: """ - A - B + """ Examples: - | Scenario | NamespaceAware | Namespace | - | Namespace aware with namespace | true | xmlns:xi="http://www.w3.org/2001/XInclude" | - | Namespace unaware with namespace | false | xmlns:xi="http://www.w3.org/2001/XInclude" | - | Namespace unaware without namespace | false | | + | Scenario | NamespaceAware | Namespace | expected-result | + # When namespace aware is enabled and the namespace is in the model file, includes are resolved. + | Namespace aware with namespace | true | xmlns:xi="http://www.w3.org/2001/XInclude" | A\nB | + # When the namespace is not in the model file, model includes are not resolved. + | Namespace unaware with namespace | false | xmlns:xi="http://www.w3.org/2001/XInclude" | A | + # When parsing namespace unaware and the namespace is not in the the model file, includes are not resolved. + | Namespace unaware without namespace | false | | A | Scenario Outline: Xml include in model file Given the following config: """ - + @@ -87,11 +89,9 @@ Feature: Unit_Model_Xml_Include """ Then I expect the following error message: """ - Error while reading model: Error while parsing file as XML document: Name space qualification Exception: Element not qualified - - Line Number: 5 Offset: 4. + """ Examples: - | Scenario | NamespaceAware | Namespace | - | Namespace aware without namespace | true | | + | Scenario | NamespaceAware | Namespace | ErrorMessage | + | Namespace aware without namespace | true | | Error while reading model XML file: The prefix "xi" for element "xi:include" is not bound. |