From 73846b3951c716b79f8ce34f3e774719fbb4ac73 Mon Sep 17 00:00:00 2001 From: Willem Otten Date: Mon, 7 Oct 2024 22:10:17 +0200 Subject: [PATCH 1/8] Implement model attribute injection with saxon parser --- .../xgenerate/model/ModelPreprocessor.java | 305 ++++++++++++------ 1 file changed, 200 insertions(+), 105 deletions(-) diff --git a/XGenerate/src/main/java/com/xbreeze/xgenerate/model/ModelPreprocessor.java b/XGenerate/src/main/java/com/xbreeze/xgenerate/model/ModelPreprocessor.java index adf18013..63731b75 100644 --- a/XGenerate/src/main/java/com/xbreeze/xgenerate/model/ModelPreprocessor.java +++ b/XGenerate/src/main/java/com/xbreeze/xgenerate/model/ModelPreprocessor.java @@ -24,15 +24,35 @@ *******************************************************************************/ package com.xbreeze.xgenerate.model; +import java.io.ByteArrayInputStream; import java.io.IOException; import java.util.ArrayList; -import java.util.List; import java.util.logging.Logger; -import java.util.stream.Collectors; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerException; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamResult; +import javax.xml.xpath.XPathConstants; +import javax.xml.xpath.XPathExpressionException; +import net.sf.saxon.xpath.XPathFactoryImpl; +import net.sf.saxon.sxpath.XPathDynamicContext; +import net.sf.saxon.sxpath.XPathExpression; +import net.sf.saxon.trans.XPathException; +import net.sf.saxon.xpath.XPathEvaluator; +import net.sf.saxon.xpath.XPathExpressionImpl; + +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import org.xml.sax.SAXException; import com.xbreeze.xgenerate.config.NamespaceConfig; import com.xbreeze.xgenerate.config.model.ModelAttributeInjection; -import com.xbreeze.xgenerate.config.model.ModelAttributeInjectionValueMapping; import com.xbreeze.xgenerate.config.model.ModelConfig; import com.xbreeze.xgenerate.config.model.ModelNodeRemoval; import com.xbreeze.xgenerate.generator.GeneratorException; @@ -46,9 +66,11 @@ import com.ximpleware.XMLModifier; import com.ximpleware.XPathEvalException; import com.ximpleware.XPathParseException; +import java.io.StringWriter; /** * The model preprocessor. + * * @author Harmen */ public class ModelPreprocessor { @@ -56,45 +78,85 @@ public class ModelPreprocessor { /** * Pre-process the model. + * * @param model * @param modelConfig - * @throws ModelPreprocessorException + * @throws ModelPreprocessorException */ public static void preprocessModel(Model model, ModelConfig modelConfig) throws ModelPreprocessorException { logger.info("Starting model preprocessing"); - /// Store the pre-processed model. - String preprocessedModel = model.getModelFileContent(); + // Load the preprocessed model into memory + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + DocumentBuilder builder; + try { + builder = factory.newDocumentBuilder(); + } catch (ParserConfigurationException exc) { + // TODO Auto-generated catch block + throw new ModelPreprocessorException( + String.format("Error while reading model XML file: %s", exc.getMessage())); + } + Document doc; + try { + doc = builder.parse(new ByteArrayInputStream(model.getModelFileContent().getBytes())); + } catch(SAXException | IOException exc) { + throw new ModelPreprocessorException( + String.format("Error while reading model XML file: %s", exc.getMessage())); + } // ModelAttributeInjections - // First do attribute injection, in case attributes are used as source that are removed in the next step - if (modelConfig != null && modelConfig.getModelAttributeInjections() != null && modelConfig.getModelAttributeInjections().size() > 0) { - preprocessedModel = performModelAttributeInjections(preprocessedModel, modelConfig.getModelAttributeInjections(), modelConfig.getNamespaces()); + // First do attribute injection, in case attributes are used as source that are + // removed in the next step + if (modelConfig != null && modelConfig.getModelAttributeInjections() != null + && modelConfig.getModelAttributeInjections().size() > 0) { + doc = performModelAttributeInjections(doc, + modelConfig.getModelAttributeInjections(), modelConfig.getNamespaces()); } + /* // ModelNodeRemovals - if (modelConfig != null && modelConfig.getModelNodeRemovals() != null && modelConfig.getModelNodeRemovals().size() > 0) { - preprocessedModel = performModelNodeRemovals(preprocessedModel, modelConfig.getModelNodeRemovals(), modelConfig.getNamespaces()); + if (modelConfig != null && modelConfig.getModelNodeRemovals() != null + && modelConfig.getModelNodeRemovals().size() > 0) { + preprocessedModel = performModelNodeRemovals(preprocessedModel, modelConfig.getModelNodeRemovals(), + modelConfig.getNamespaces()); } - - // Store the pre-processed model in the Model object. + */ + String preprocessedModel = null; + DOMSource domSource = new DOMSource(doc); + StringWriter writer = new StringWriter(); + StreamResult result = new StreamResult(writer); + TransformerFactory tf = TransformerFactory.newInstance(); + Transformer transformer; + try { + transformer = tf.newTransformer(); + transformer.transform(domSource, result); + } catch (TransformerException e) { + throw new ModelPreprocessorException( + String.format("Error transforming model XML document to string: %s", e.getMessage()) + ); + } + preprocessedModel = writer.toString(); + // Store the pre-processed model as string in the Model object. model.setPreprocessedModel(preprocessedModel); - + logger.info("End model preprocessing"); } - - private static String performModelNodeRemovals(String preprocessedModel, ArrayList modelModelNodeRemovals, ArrayList namespaces) throws ModelPreprocessorException { + + private static String performModelNodeRemovals(String preprocessedModel, + ArrayList modelModelNodeRemovals, ArrayList namespaces) + throws ModelPreprocessorException { logger.fine("Performing model node removals."); - + // Execute the model XPath on the Document. VTDNav nv; try { nv = XMLUtils.getVTDNav(preprocessedModel, (namespaces != null && namespaces.size() > 0)); } catch (GeneratorException e) { - throw new ModelPreprocessorException(String.format("Error while reading model before pre-processing: %s", e.getMessage()), e); + throw new ModelPreprocessorException( + String.format("Error while reading model before pre-processing: %s", e.getMessage()), e); } - + // Create a modifier to modify the model document. XMLModifier xm; try { @@ -102,42 +164,46 @@ private static String performModelNodeRemovals(String preprocessedModel, ArrayLi } catch (ModifyException e) { throw new ModelPreprocessorException(e); } - + // Loop through the model node removals and process them. for (ModelNodeRemoval mnr : modelModelNodeRemovals) { // Create an AutoPilot for querying the document. AutoPilot ap = new AutoPilot(nv); - + // If namespaces are defined, register then on the auto pilot. if (namespaces != null && namespaces.size() > 0) { // Register the declared namespaces. for (NamespaceConfig namespace : namespaces) { - ap.declareXPathNameSpace(namespace.getPrefix(), namespace.getNamespace()); + ap.declareXPathNameSpace(namespace.getPrefix(), namespace.getNamespace()); } } - + try { // Set the XPath expression from the config. ap.selectXPath(mnr.getModelXPath()); - + // Execute the XPath expression and loop through the results. - while ((ap.evalXPath()) != -1) { - // Remove the node. - xm.remove(); - } - - // Output and re-parse the document for the next injection. - // This is necessary, otherwise exceptions will be thrown when injecting attributes for the same element. - nv = xm.outputAndReparse(); - // Reset and bind the modifier to the new VTDNav object. - xm.reset(); - xm.bind(nv); - - } catch (XPathParseException | XPathEvalException | NavException | ModifyException | ParseException | TranscodeException | IOException e) { - throw new ModelPreprocessorException(String.format("Error while processing model node removal for XPath ´%s´: %s", mnr.getModelXPath(), XMLUtils.getAutopilotExceptionMessage(mnr.getModelXPath(), e))); + while ((ap.evalXPath()) != -1) { + // Remove the node. + xm.remove(); + } + + // Output and re-parse the document for the next injection. + // This is necessary, otherwise exceptions will be thrown when injecting + // attributes for the same element. + nv = xm.outputAndReparse(); + // Reset and bind the modifier to the new VTDNav object. + xm.reset(); + xm.bind(nv); + + } catch (XPathParseException | XPathEvalException | NavException | ModifyException | ParseException + | TranscodeException | IOException e) { + throw new ModelPreprocessorException( + String.format("Error while processing model node removal for XPath ´%s´: %s", + mnr.getModelXPath(), XMLUtils.getAutopilotExceptionMessage(mnr.getModelXPath(), e))); } } - + // Return the modified XML document. try { return XMLUtils.getResultingXml(xm); @@ -145,51 +211,63 @@ private static String performModelNodeRemovals(String preprocessedModel, ArrayLi throw new ModelPreprocessorException(e); } } - - private static String performModelAttributeInjections(String preprocessedModel, ArrayList modelAttributeInjections, ArrayList namespaces) throws ModelPreprocessorException { + + private static Document performModelAttributeInjections(Document modelDoc, + ArrayList modelAttributeInjections, ArrayList namespaces) + throws ModelPreprocessorException { logger.fine("Performing model attribute injections."); - - // Execute the model XPath on the Document. - VTDNav nv; - try { - // Set the VTD nav to namespace aware if the namespaces are defined. - nv = XMLUtils.getVTDNav(preprocessedModel, (namespaces != null && namespaces.size() > 0)); - } catch (GeneratorException e) { - throw new ModelPreprocessorException(String.format("Error while reading model before performing model attribute injections: %s", e.getMessage()), e); - } - - // Create a modifier to modify the model document. - XMLModifier xm; - try { - xm = new XMLModifier(nv); - } catch (ModifyException e) { - throw new ModelPreprocessorException(e); - } - + XPathFactoryImpl xPathfactory = new XPathFactoryImpl(); + XPathEvaluator xpath = (XPathEvaluator) xPathfactory.newXPath(); + // Loop through the model attribute injections and process them. for (ModelAttributeInjection mai : modelAttributeInjections) { - // Create an AutoPilot for querying the document. - AutoPilot ap = new AutoPilot(nv); - + try { + XPathExpressionImpl expr = (XPathExpressionImpl)xpath.compile(mai.getModelXPath()); + + NodeList result = (NodeList) expr.evaluate(modelDoc, XPathConstants.NODESET); + for (int i = 0; i < result.getLength(); i++) { + Node node = result.item(i); + String targetValue = null; + //If a target xpath is set, evaluate it to get a value + if (mai.getTargetXPath() !=null) { + try { + XPathExpressionImpl targetExpression = (XPathExpressionImpl)xpath.compile(mai.getTargetXPath()); + targetValue = (String)targetExpression.evaluate(node, XPathConstants.STRING); + } catch (XPathExpressionException e) { + throw new ModelPreprocessorException(String.format("Error evaluating XPATH expression for targetvalue %s, %s", mai.getTargetXPath(), e.getMessage())); + } + } else if (mai.getTargetValue() != null) { + targetValue = mai.getTargetValue(); + }//TODO value mappings nog omzetten + + if (targetValue != null && mai.getTargetAttribute() != null) { + ((Element)node).setAttribute(mai.getTargetAttribute(), targetValue); + } + } + } catch (XPathExpressionException e) { + throw new ModelPreprocessorException(String.format("Error evaluating XPATH expression for model selection %s, %s", mai.getModelXPath(), e.getMessage())); + } // If namespaces are defined, register then on the auto pilot. - if (namespaces != null && namespaces.size() > 0) { + /*if (namespaces != null && namespaces.size() > 0) { // Register the declared namespaces. for (NamespaceConfig namespace : namespaces) { - ap.declareXPathNameSpace(namespace.getPrefix(), namespace.getNamespace()); + ap.declareXPathNameSpace(namespace.getPrefix(), namespace.getNamespace()); } } - - try { - + */ + /* + * try { + + // Set the XPath expression from the config. ap.selectXPath(mai.getModelXPath()); - + // Execute the XPath expression and loop through the results. - while ((ap.evalXPath()) != -1) { - - String targetValue = null; - + while ((ap.evalXPath()) != -1) { + + String targetValue = null; + // Set the attribute value, either from XPath or constant value if (mai.getTargetXPath() != null) { // Execute the XPath on the current node. @@ -197,69 +275,86 @@ private static String performModelAttributeInjections(String preprocessedModel, AutoPilot ap_targetValue = new AutoPilot(nv); ap_targetValue.selectXPath(mai.getTargetXPath()); targetValue = ap_targetValue.evalXPathToString(); - logger.info(String.format("Target XPath defined for attribute injection, value: ´%s´ => ´%s´", mai.getTargetXPath(), targetValue)); - } - catch (XPathParseException e) { - throw new ModelPreprocessorException(String.format("Error while processing model attribute injection for target XPath ´%s´: %s", mai.getTargetXPath(), XMLUtils.getAutopilotExceptionMessage(mai.getTargetXPath(), e))); + logger.info( + String.format("Target XPath defined for attribute injection, value: ´%s´ => ´%s´", + mai.getTargetXPath(), targetValue)); + } catch (XPathParseException e) { + throw new ModelPreprocessorException(String.format( + "Error while processing model attribute injection for target XPath ´%s´: %s", + mai.getTargetXPath(), + XMLUtils.getAutopilotExceptionMessage(mai.getTargetXPath(), e))); } } - + // When the target value is set, write the value. else if (mai.getTargetValue() != null) { targetValue = mai.getTargetValue(); } - + // When the value mappings are set, process them. else if (mai.getValueMappings() != null) { try { AutoPilot ap_targetValue = new AutoPilot(nv); - // Get the value of the input node. + // Get the value of the input node. ap_targetValue.selectXPath(mai.getValueMappings().getInputNode()); String inputNodeValue = ap_targetValue.evalXPathToString(); // Using the input node value, find the output value from the value mapping. - List foundValueMappings = mai.getValueMappings().getModelAttributeInjectionValueMappings().stream().filter(vm -> vm.getInputValue().equals(inputNodeValue)).collect(Collectors.toList()); + List foundValueMappings = mai.getValueMappings() + .getModelAttributeInjectionValueMappings().stream() + .filter(vm -> vm.getInputValue().equals(inputNodeValue)) + .collect(Collectors.toList()); // There should only be 1 found value mapping. if (foundValueMappings.size() == 1) { targetValue = foundValueMappings.get(0).getOutputValue(); - logger.info(String.format("Value mappings defined for attribute injection, input node value: ´%s´, target value: ´%s´", inputNodeValue, targetValue)); + logger.info(String.format( + "Value mappings defined for attribute injection, input node value: ´%s´, target value: ´%s´", + inputNodeValue, targetValue)); } // If multiple value mappings are found, throw an exception. else if (foundValueMappings.size() > 1) { - throw new ModelPreprocessorException(String.format("%d value mappings found for input node value ´%s´, expected exactly 1!", foundValueMappings.size(), inputNodeValue)); + throw new ModelPreprocessorException(String.format( + "%d value mappings found for input node value ´%s´, expected exactly 1!", + foundValueMappings.size(), inputNodeValue)); } // If no value mappings are found, print a warning. else { - logger.warning(String.format("%d value mappings found for input node value ´%s´.", foundValueMappings.size(), inputNodeValue)); + logger.warning(String.format("%d value mappings found for input node value ´%s´.", + foundValueMappings.size(), inputNodeValue)); } - } - catch (XPathParseException e) { - throw new ModelPreprocessorException(String.format("Error while processing model attribute injection for target XPath ´%s´: %s", mai.getTargetXPath(), XMLUtils.getAutopilotExceptionMessage(mai.getTargetXPath(), e))); + } catch (XPathParseException e) { + throw new ModelPreprocessorException(String.format( + "Error while processing model attribute injection for target XPath ´%s´: %s", + mai.getTargetXPath(), + XMLUtils.getAutopilotExceptionMessage(mai.getTargetXPath(), e))); } } - - // Append the attribute (if it is not null, this can happen if a value mapping returns no value). + + // Append the attribute (if it is not null, this can happen if a value mapping + // returns no value). if (targetValue != null) XMLUtils.appendAttribute(nv, xm, mai.getTargetAttribute(), targetValue); - } - - // Output and re-parse the document for the next injection. - // This is necessary, otherwise exceptions will be thrown when injecting attributes for the same element. - nv = xm.outputAndReparse(); - // Reset and bind the modifier to the new VTDNav object. - xm.reset(); - xm.bind(nv); - - } catch (XPathParseException | XPathEvalException | NavException | ModifyException | ParseException | TranscodeException | IOException | GeneratorException e) { + } + + // Output and re-parse the document for the next injection. + // This is necessary, otherwise exceptions will be thrown when injecting + // attributes for the same element. + nv = xm.outputAndReparse(); + // Reset and bind the modifier to the new VTDNav object. + xm.reset(); + xm.bind(nv); + + } catch (XPathParseException | XPathEvalException | NavException | ModifyException | ParseException + | TranscodeException | IOException | GeneratorException e) { // Throw a new exception. - throw new ModelPreprocessorException(String.format("Error while processing model attribute injection for model XPath ´%s´: %s", mai.getModelXPath(), XMLUtils.getAutopilotExceptionMessage(mai.getModelXPath(), e))); + throw new ModelPreprocessorException( + String.format("Error while processing model attribute injection for model XPath ´%s´: %s", + mai.getModelXPath(), XMLUtils.getAutopilotExceptionMessage(mai.getModelXPath(), e))); } + */ } - + // Return the modified XML document. - try { - return XMLUtils.getResultingXml(xm); - } catch (GeneratorException e) { - throw new ModelPreprocessorException(e); - } + return modelDoc; + } } From 73200175b200e111064806afa46e770ca0b26982 Mon Sep 17 00:00:00 2001 From: Willem Otten Date: Mon, 7 Oct 2024 22:24:11 +0200 Subject: [PATCH 2/8] Implement value mappings in attribute injection --- .../xgenerate/model/ModelPreprocessor.java | 36 ++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/XGenerate/src/main/java/com/xbreeze/xgenerate/model/ModelPreprocessor.java b/XGenerate/src/main/java/com/xbreeze/xgenerate/model/ModelPreprocessor.java index 63731b75..a7f95bb9 100644 --- a/XGenerate/src/main/java/com/xbreeze/xgenerate/model/ModelPreprocessor.java +++ b/XGenerate/src/main/java/com/xbreeze/xgenerate/model/ModelPreprocessor.java @@ -27,7 +27,10 @@ import java.io.ByteArrayInputStream; import java.io.IOException; import java.util.ArrayList; +import java.util.List; import java.util.logging.Logger; +import java.util.stream.Collectors; + import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; @@ -55,6 +58,7 @@ import com.xbreeze.xgenerate.config.model.ModelAttributeInjection; import com.xbreeze.xgenerate.config.model.ModelConfig; import com.xbreeze.xgenerate.config.model.ModelNodeRemoval; +import com.xbreeze.xgenerate.config.model.ModelAttributeInjectionValueMapping; import com.xbreeze.xgenerate.generator.GeneratorException; import com.xbreeze.xgenerate.utils.XMLUtils; import com.ximpleware.AutoPilot; @@ -239,7 +243,37 @@ private static Document performModelAttributeInjections(Document modelDoc, } } else if (mai.getTargetValue() != null) { targetValue = mai.getTargetValue(); - }//TODO value mappings nog omzetten + } else if (mai.getValueMappings() != null ) { + //Get the input node value to use for finding the mapped output value + try { + XPathExpressionImpl valueMappingExpression = (XPathExpressionImpl)xpath.compile(mai.getValueMappings().getInputNode()); + String inputNodeValue = (String)valueMappingExpression.evaluate(node, XPathConstants.STRING); + List foundValueMappings = mai.getValueMappings() + .getModelAttributeInjectionValueMappings().stream() + .filter(vm -> vm.getInputValue().equals(inputNodeValue)) + .collect(Collectors.toList()); + // There should only be 1 found value mapping. + if (foundValueMappings.size() == 1) { + targetValue = foundValueMappings.get(0).getOutputValue(); + logger.info(String.format( + "Value mappings defined for attribute injection, input node value: ´%s´, target value: ´%s´", + inputNodeValue, targetValue)); + } + // If multiple value mappings are found, throw an exception. + else if (foundValueMappings.size() > 1) { + throw new ModelPreprocessorException(String.format( + "%d value mappings found for input node value ´%s´, expected exactly 1!", + foundValueMappings.size(), inputNodeValue)); + } + // If no value mappings are found, print a warning. + else { + logger.warning(String.format("%d value mappings found for input node value ´%s´.", + foundValueMappings.size(), inputNodeValue)); + } + } catch (XPathExpressionException e) { + throw new ModelPreprocessorException(String.format("Error evaluating XPATH expression for value mapping %s, %s", mai.getValueMappings().getInputNode(), e.getMessage())); + } + } if (targetValue != null && mai.getTargetAttribute() != null) { ((Element)node).setAttribute(mai.getTargetAttribute(), targetValue); From 3d1b46838fad918764da346a9e2a555e7b5ab59c Mon Sep 17 00:00:00 2001 From: Willem Otten Date: Mon, 7 Oct 2024 22:53:59 +0200 Subject: [PATCH 3/8] Noderemoval implemented --- .../xgenerate/model/ModelPreprocessor.java | 200 ++++-------------- 1 file changed, 39 insertions(+), 161 deletions(-) diff --git a/XGenerate/src/main/java/com/xbreeze/xgenerate/model/ModelPreprocessor.java b/XGenerate/src/main/java/com/xbreeze/xgenerate/model/ModelPreprocessor.java index a7f95bb9..e516f60c 100644 --- a/XGenerate/src/main/java/com/xbreeze/xgenerate/model/ModelPreprocessor.java +++ b/XGenerate/src/main/java/com/xbreeze/xgenerate/model/ModelPreprocessor.java @@ -50,6 +50,7 @@ import org.w3c.dom.Document; import org.w3c.dom.Element; +import org.w3c.dom.Attr; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.xml.sax.SAXException; @@ -118,14 +119,14 @@ public static void preprocessModel(Model model, ModelConfig modelConfig) throws modelConfig.getModelAttributeInjections(), modelConfig.getNamespaces()); } - /* + // ModelNodeRemovals if (modelConfig != null && modelConfig.getModelNodeRemovals() != null && modelConfig.getModelNodeRemovals().size() > 0) { - preprocessedModel = performModelNodeRemovals(preprocessedModel, modelConfig.getModelNodeRemovals(), + doc = performModelNodeRemovals(doc, modelConfig.getModelNodeRemovals(), modelConfig.getNamespaces()); } - */ + String preprocessedModel = null; DOMSource domSource = new DOMSource(doc); StringWriter writer = new StringWriter(); @@ -147,73 +148,45 @@ public static void preprocessModel(Model model, ModelConfig modelConfig) throws logger.info("End model preprocessing"); } - private static String performModelNodeRemovals(String preprocessedModel, + private static Document performModelNodeRemovals(Document modelDoc, ArrayList modelModelNodeRemovals, ArrayList namespaces) throws ModelPreprocessorException { - logger.fine("Performing model node removals."); - - // Execute the model XPath on the Document. - VTDNav nv; - try { - nv = XMLUtils.getVTDNav(preprocessedModel, (namespaces != null && namespaces.size() > 0)); - } catch (GeneratorException e) { - throw new ModelPreprocessorException( - String.format("Error while reading model before pre-processing: %s", e.getMessage()), e); - } - - // Create a modifier to modify the model document. - XMLModifier xm; - try { - xm = new XMLModifier(nv); - } catch (ModifyException e) { - throw new ModelPreprocessorException(e); - } - // Loop through the model node removals and process them. - for (ModelNodeRemoval mnr : modelModelNodeRemovals) { - // Create an AutoPilot for querying the document. - AutoPilot ap = new AutoPilot(nv); + logger.fine("Performing model node removals."); - // If namespaces are defined, register then on the auto pilot. - if (namespaces != null && namespaces.size() > 0) { + XPathFactoryImpl xPathfactory = new XPathFactoryImpl(); + XPathEvaluator xpath = (XPathEvaluator) xPathfactory.newXPath(); + //TODO do we need namespace support here? + // If namespaces are defined, register then on the auto pilot. + /* + * if (namespaces != null && namespaces.size() > 0) { + // Register the declared namespaces. for (NamespaceConfig namespace : namespaces) { ap.declareXPathNameSpace(namespace.getPrefix(), namespace.getNamespace()); } } + */ + // Loop through the model node removals and process them. + for (ModelNodeRemoval mnr : modelModelNodeRemovals) { try { - // Set the XPath expression from the config. - ap.selectXPath(mnr.getModelXPath()); - - // Execute the XPath expression and loop through the results. - while ((ap.evalXPath()) != -1) { - // Remove the node. - xm.remove(); - } - - // Output and re-parse the document for the next injection. - // This is necessary, otherwise exceptions will be thrown when injecting - // attributes for the same element. - nv = xm.outputAndReparse(); - // Reset and bind the modifier to the new VTDNav object. - xm.reset(); - xm.bind(nv); - - } catch (XPathParseException | XPathEvalException | NavException | ModifyException | ParseException - | TranscodeException | IOException e) { - throw new ModelPreprocessorException( - String.format("Error while processing model node removal for XPath ´%s´: %s", - mnr.getModelXPath(), XMLUtils.getAutopilotExceptionMessage(mnr.getModelXPath(), e))); + XPathExpressionImpl expr = (XPathExpressionImpl)xpath.compile(mnr.getModelXPath()); + NodeList result = (NodeList) expr.evaluate(modelDoc, XPathConstants.NODESET); + for (int i = 0; i < result.getLength(); i++) { + Node node = result.item(i); + if (node.getNodeType() == Node.ELEMENT_NODE) { + node.getParentNode().removeChild(node); + } else if (node.getNodeType() == Node.ATTRIBUTE_NODE) { + ((Attr)node).getOwnerElement().removeAttributeNode((Attr)node); + } + } + } catch (XPathExpressionException e) { + throw new ModelPreprocessorException(String.format("Error processing XPATH expression for node removal %s, %s", mnr.getModelXPath(), e.getMessage())); } } - // Return the modified XML document. - try { - return XMLUtils.getResultingXml(xm); - } catch (GeneratorException e) { - throw new ModelPreprocessorException(e); - } + return modelDoc; } private static Document performModelAttributeInjections(Document modelDoc, @@ -223,6 +196,16 @@ private static Document performModelAttributeInjections(Document modelDoc, XPathFactoryImpl xPathfactory = new XPathFactoryImpl(); XPathEvaluator xpath = (XPathEvaluator) xPathfactory.newXPath(); + + // TODO: Not sure if we need to implement namespace support here? + // If namespaces are defined, register then on the auto pilot. + /*if (namespaces != null && namespaces.size() > 0) { + // Register the declared namespaces. + for (NamespaceConfig namespace : namespaces) { + ap.declareXPathNameSpace(namespace.getPrefix(), namespace.getNamespace()); + } + } + */ // Loop through the model attribute injections and process them. for (ModelAttributeInjection mai : modelAttributeInjections) { @@ -282,113 +265,8 @@ else if (foundValueMappings.size() > 1) { } catch (XPathExpressionException e) { throw new ModelPreprocessorException(String.format("Error evaluating XPATH expression for model selection %s, %s", mai.getModelXPath(), e.getMessage())); } - // If namespaces are defined, register then on the auto pilot. - /*if (namespaces != null && namespaces.size() > 0) { - // Register the declared namespaces. - for (NamespaceConfig namespace : namespaces) { - ap.declareXPathNameSpace(namespace.getPrefix(), namespace.getNamespace()); - } - } - */ - /* - * try { - - - // Set the XPath expression from the config. - ap.selectXPath(mai.getModelXPath()); - - // Execute the XPath expression and loop through the results. - while ((ap.evalXPath()) != -1) { - - String targetValue = null; - - // Set the attribute value, either from XPath or constant value - if (mai.getTargetXPath() != null) { - // Execute the XPath on the current node. - try { - AutoPilot ap_targetValue = new AutoPilot(nv); - ap_targetValue.selectXPath(mai.getTargetXPath()); - targetValue = ap_targetValue.evalXPathToString(); - logger.info( - String.format("Target XPath defined for attribute injection, value: ´%s´ => ´%s´", - mai.getTargetXPath(), targetValue)); - } catch (XPathParseException e) { - throw new ModelPreprocessorException(String.format( - "Error while processing model attribute injection for target XPath ´%s´: %s", - mai.getTargetXPath(), - XMLUtils.getAutopilotExceptionMessage(mai.getTargetXPath(), e))); - } - } - - // When the target value is set, write the value. - else if (mai.getTargetValue() != null) { - targetValue = mai.getTargetValue(); - } - - // When the value mappings are set, process them. - else if (mai.getValueMappings() != null) { - try { - AutoPilot ap_targetValue = new AutoPilot(nv); - // Get the value of the input node. - ap_targetValue.selectXPath(mai.getValueMappings().getInputNode()); - String inputNodeValue = ap_targetValue.evalXPathToString(); - // Using the input node value, find the output value from the value mapping. - List foundValueMappings = mai.getValueMappings() - .getModelAttributeInjectionValueMappings().stream() - .filter(vm -> vm.getInputValue().equals(inputNodeValue)) - .collect(Collectors.toList()); - // There should only be 1 found value mapping. - if (foundValueMappings.size() == 1) { - targetValue = foundValueMappings.get(0).getOutputValue(); - logger.info(String.format( - "Value mappings defined for attribute injection, input node value: ´%s´, target value: ´%s´", - inputNodeValue, targetValue)); - } - // If multiple value mappings are found, throw an exception. - else if (foundValueMappings.size() > 1) { - throw new ModelPreprocessorException(String.format( - "%d value mappings found for input node value ´%s´, expected exactly 1!", - foundValueMappings.size(), inputNodeValue)); - } - // If no value mappings are found, print a warning. - else { - logger.warning(String.format("%d value mappings found for input node value ´%s´.", - foundValueMappings.size(), inputNodeValue)); - } - } catch (XPathParseException e) { - throw new ModelPreprocessorException(String.format( - "Error while processing model attribute injection for target XPath ´%s´: %s", - mai.getTargetXPath(), - XMLUtils.getAutopilotExceptionMessage(mai.getTargetXPath(), e))); - } - } - - // Append the attribute (if it is not null, this can happen if a value mapping - // returns no value). - if (targetValue != null) - XMLUtils.appendAttribute(nv, xm, mai.getTargetAttribute(), targetValue); - } - - // Output and re-parse the document for the next injection. - // This is necessary, otherwise exceptions will be thrown when injecting - // attributes for the same element. - nv = xm.outputAndReparse(); - // Reset and bind the modifier to the new VTDNav object. - xm.reset(); - xm.bind(nv); - - } catch (XPathParseException | XPathEvalException | NavException | ModifyException | ParseException - | TranscodeException | IOException | GeneratorException e) { - // Throw a new exception. - throw new ModelPreprocessorException( - String.format("Error while processing model attribute injection for model XPath ´%s´: %s", - mai.getModelXPath(), XMLUtils.getAutopilotExceptionMessage(mai.getModelXPath(), e))); - } - */ } - // Return the modified XML document. return modelDoc; - } } From 768a62e6714d4a0214c52337c3e38cf7a3e35e1a Mon Sep 17 00:00:00 2001 From: Willem Otten Date: Fri, 11 Oct 2024 21:40:59 +0200 Subject: [PATCH 4/8] implement namespace support --- .../xgenerate/model/ModelPreprocessor.java | 104 ++++++++++-------- ...nfig_Model_ModelAttributeInjection.feature | 4 +- 2 files changed, 63 insertions(+), 45 deletions(-) diff --git a/XGenerate/src/main/java/com/xbreeze/xgenerate/model/ModelPreprocessor.java b/XGenerate/src/main/java/com/xbreeze/xgenerate/model/ModelPreprocessor.java index e516f60c..dac3aecc 100644 --- a/XGenerate/src/main/java/com/xbreeze/xgenerate/model/ModelPreprocessor.java +++ b/XGenerate/src/main/java/com/xbreeze/xgenerate/model/ModelPreprocessor.java @@ -27,10 +27,12 @@ import java.io.ByteArrayInputStream; import java.io.IOException; import java.util.ArrayList; +import java.util.Iterator; import java.util.List; import java.util.logging.Logger; import java.util.stream.Collectors; +import javax.xml.namespace.NamespaceContext; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; @@ -42,12 +44,10 @@ import javax.xml.xpath.XPathConstants; import javax.xml.xpath.XPathExpressionException; import net.sf.saxon.xpath.XPathFactoryImpl; -import net.sf.saxon.sxpath.XPathDynamicContext; -import net.sf.saxon.sxpath.XPathExpression; -import net.sf.saxon.trans.XPathException; import net.sf.saxon.xpath.XPathEvaluator; import net.sf.saxon.xpath.XPathExpressionImpl; + import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Attr; @@ -60,17 +60,6 @@ import com.xbreeze.xgenerate.config.model.ModelConfig; import com.xbreeze.xgenerate.config.model.ModelNodeRemoval; import com.xbreeze.xgenerate.config.model.ModelAttributeInjectionValueMapping; -import com.xbreeze.xgenerate.generator.GeneratorException; -import com.xbreeze.xgenerate.utils.XMLUtils; -import com.ximpleware.AutoPilot; -import com.ximpleware.ModifyException; -import com.ximpleware.NavException; -import com.ximpleware.ParseException; -import com.ximpleware.TranscodeException; -import com.ximpleware.VTDNav; -import com.ximpleware.XMLModifier; -import com.ximpleware.XPathEvalException; -import com.ximpleware.XPathParseException; import java.io.StringWriter; /** @@ -96,8 +85,7 @@ public static void preprocessModel(Model model, ModelConfig modelConfig) throws DocumentBuilder builder; try { builder = factory.newDocumentBuilder(); - } catch (ParserConfigurationException exc) { - // TODO Auto-generated catch block + } catch (ParserConfigurationException exc) { throw new ModelPreprocessorException( String.format("Error while reading model XML file: %s", exc.getMessage())); } @@ -106,7 +94,7 @@ public static void preprocessModel(Model model, ModelConfig modelConfig) throws doc = builder.parse(new ByteArrayInputStream(model.getModelFileContent().getBytes())); } catch(SAXException | IOException exc) { throw new ModelPreprocessorException( - String.format("Error while reading model XML file: %s", exc.getMessage())); + String.format("Error while reading model XML file: %s", exc.getMessage())); } @@ -127,6 +115,7 @@ public static void preprocessModel(Model model, ModelConfig modelConfig) throws modelConfig.getNamespaces()); } + //Transform the preprocessed document to string and store it in the model object. String preprocessedModel = null; DOMSource domSource = new DOMSource(doc); StringWriter writer = new StringWriter(); @@ -155,23 +144,18 @@ private static Document performModelNodeRemovals(Document modelDoc, logger.fine("Performing model node removals."); XPathFactoryImpl xPathfactory = new XPathFactoryImpl(); - XPathEvaluator xpath = (XPathEvaluator) xPathfactory.newXPath(); - //TODO do we need namespace support here? - // If namespaces are defined, register then on the auto pilot. - /* - * if (namespaces != null && namespaces.size() > 0) { - - // Register the declared namespaces. - for (NamespaceConfig namespace : namespaces) { - ap.declareXPathNameSpace(namespace.getPrefix(), namespace.getNamespace()); - } - } - */ + XPathEvaluator xpath = (XPathEvaluator) xPathfactory.newXPath(); + // If namespaces are defined, create a namespace context object and set it on the xpath object. + if (namespaces != null && namespaces.size() > 0) { + ModelNamespaceContext nsContext = new ModelNamespaceContext(namespaces); + xpath.setNamespaceContext(nsContext); + } + // Loop through the model node removals and process them. for (ModelNodeRemoval mnr : modelModelNodeRemovals) { try { - XPathExpressionImpl expr = (XPathExpressionImpl)xpath.compile(mnr.getModelXPath()); + XPathExpressionImpl expr = (XPathExpressionImpl)xpath.compile(mnr.getModelXPath()); NodeList result = (NodeList) expr.evaluate(modelDoc, XPathConstants.NODESET); for (int i = 0; i < result.getLength(); i++) { Node node = result.item(i); @@ -197,15 +181,11 @@ private static Document performModelAttributeInjections(Document modelDoc, XPathFactoryImpl xPathfactory = new XPathFactoryImpl(); XPathEvaluator xpath = (XPathEvaluator) xPathfactory.newXPath(); - // TODO: Not sure if we need to implement namespace support here? - // If namespaces are defined, register then on the auto pilot. - /*if (namespaces != null && namespaces.size() > 0) { - // Register the declared namespaces. - for (NamespaceConfig namespace : namespaces) { - ap.declareXPathNameSpace(namespace.getPrefix(), namespace.getNamespace()); - } - } - */ + // If namespaces are defined, create a namespace context object and set it on the xpath object. + if (namespaces != null && namespaces.size() > 0) { + ModelNamespaceContext nsContext = new ModelNamespaceContext(namespaces); + xpath.setNamespaceContext(nsContext); + } // Loop through the model attribute injections and process them. for (ModelAttributeInjection mai : modelAttributeInjections) { @@ -219,15 +199,15 @@ private static Document performModelAttributeInjections(Document modelDoc, //If a target xpath is set, evaluate it to get a value if (mai.getTargetXPath() !=null) { try { - XPathExpressionImpl targetExpression = (XPathExpressionImpl)xpath.compile(mai.getTargetXPath()); + XPathExpressionImpl targetExpression = (XPathExpressionImpl)xpath.compile(mai.getTargetXPath()); targetValue = (String)targetExpression.evaluate(node, XPathConstants.STRING); } catch (XPathExpressionException e) { - throw new ModelPreprocessorException(String.format("Error evaluating XPATH expression for targetvalue %s, %s", mai.getTargetXPath(), e.getMessage())); + throw new ModelPreprocessorException(String.format("Error while processing model attribute injection for target XPath ´%s´: %s", mai.getTargetXPath(), e.getMessage())); } } else if (mai.getTargetValue() != null) { targetValue = mai.getTargetValue(); } else if (mai.getValueMappings() != null ) { - //Get the input node value to use for finding the mapped output value + //Get the input node value to use for finding the mapped output value try { XPathExpressionImpl valueMappingExpression = (XPathExpressionImpl)xpath.compile(mai.getValueMappings().getInputNode()); String inputNodeValue = (String)valueMappingExpression.evaluate(node, XPathConstants.STRING); @@ -263,10 +243,48 @@ else if (foundValueMappings.size() > 1) { } } } catch (XPathExpressionException e) { - throw new ModelPreprocessorException(String.format("Error evaluating XPATH expression for model selection %s, %s", mai.getModelXPath(), e.getMessage())); + throw new ModelPreprocessorException(String.format("Error while processing model attribute injection for model XPath ´%s´: %s", mai.getModelXPath(), e.getMessage())); } } // Return the modified XML document. return modelDoc; } + + // helper class to deal with namespace aware preprocessing + private static class ModelNamespaceContext implements NamespaceContext { + + private ArrayList namespaces; + + public ModelNamespaceContext (ArrayList namespaces) { + this.namespaces = namespaces; + } + + @Override + public String getNamespaceURI(String prefix) { + for (NamespaceConfig ns: this.namespaces) { + if (ns.getPrefix().equalsIgnoreCase(prefix)) { + return ns.getNamespace(); + } + } + return null; + } + + @Override + public String getPrefix(String namespaceURI) { + for (NamespaceConfig ns: this.namespaces) { + if (ns.getNamespace().equalsIgnoreCase(namespaceURI)) { + return ns.getPrefix(); + } + } + return null; + } + + @Override + public Iterator getPrefixes(String namespaceURI) { + // TODO Auto-generated method stub + return null; + } + + } } + diff --git a/XGenerate/src/test/resources/features/unit/config/model/Unit_Config_Model_ModelAttributeInjection.feature b/XGenerate/src/test/resources/features/unit/config/model/Unit_Config_Model_ModelAttributeInjection.feature index 92e52c16..769443dc 100644 --- a/XGenerate/src/test/resources/features/unit/config/model/Unit_Config_Model_ModelAttributeInjection.feature +++ b/XGenerate/src/test/resources/features/unit/config/model/Unit_Config_Model_ModelAttributeInjection.feature @@ -243,7 +243,7 @@ Feature: Unit_Config_Model_ModelAttributeInjection When I run the generator Then I expect the following error message: """ - com.xbreeze.xgenerate.model.ModelPreprocessorException: Error while processing model attribute injection for model XPath ´concats('test','test')´: Syntax error after or around the end of ´concats´ + com.xbreeze.xgenerate.model.ModelPreprocessorException: Error while processing model attribute injection for model XPath ´concats('test','test')´: net.sf.saxon.trans.XPathException: Cannot find a 2-argument function named Q{http://www.w3.org/2005/xpath-functions}concats() """ Scenario: Inject using incorrect targetXPath @@ -267,5 +267,5 @@ Feature: Unit_Config_Model_ModelAttributeInjection When I run the generator Then I expect the following error message: """ - com.xbreeze.xgenerate.model.ModelPreprocessorException: Error while processing model attribute injection for target XPath ´concats('The entity was ', @name)´: Syntax error after or around the end of ´concats´ + com.xbreeze.xgenerate.model.ModelPreprocessorException: Error while processing model attribute injection for target XPath ´concats('The entity was ', @name)´: net.sf.saxon.trans.XPathException: Cannot find a 2-argument function named Q{http://www.w3.org/2005/xpath-functions}concats() """ From 790246fab3db5f98581a8fcf5ef2f09b463ba04f Mon Sep 17 00:00:00 2001 From: Willem Otten Date: Fri, 11 Oct 2024 21:44:24 +0200 Subject: [PATCH 5/8] adjust testscenario --- .../model/Unit_Config_Model_ModelAttributeInjection.feature | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/XGenerate/src/test/resources/features/unit/config/model/Unit_Config_Model_ModelAttributeInjection.feature b/XGenerate/src/test/resources/features/unit/config/model/Unit_Config_Model_ModelAttributeInjection.feature index 769443dc..fac8a236 100644 --- a/XGenerate/src/test/resources/features/unit/config/model/Unit_Config_Model_ModelAttributeInjection.feature +++ b/XGenerate/src/test/resources/features/unit/config/model/Unit_Config_Model_ModelAttributeInjection.feature @@ -54,9 +54,7 @@ Feature: Unit_Config_Model_ModelAttributeInjection | filter XPath | //entity[@name='B'] | XPath | ./@name | | B | | | lower-case | //entity | XPath | lower-case(@name) | a | b | c | - @KnownIssue - # KnownIssue: replace function is not implemented in vtd-xml - Scenario Outline: Single attribute injection @KnownIssue + Scenario Outline: Single attribute injection Given the following config: """ @@ -86,7 +84,7 @@ Feature: Unit_Config_Model_ModelAttributeInjection Examples: | Scenario | modelXPath | targetType | targetValue | expectedResultA | expectedResultB | expectedResultC | - | replace XPath | //entity[@name='B'] | XPath | replace(./@name, 'B', 'b') | | B | | + | replace XPath | //entity[@name='B'] | XPath | replace(./@name, 'B', 'b') | | b | | Scenario Outline: Single quoted attribute injection Given I have the following model: From 0f41dfb77c3fe5723063466e3c933a760bfa8453 Mon Sep 17 00:00:00 2001 From: Willem Otten Date: Sun, 20 Oct 2024 21:10:08 +0200 Subject: [PATCH 6/8] Processed review comments --- .../xgenerate/model/ModelPreprocessor.java | 120 ++++-------------- .../xgenerate/utils/SaxonXMLUtils.java | 103 +++++++++++++++ 2 files changed, 128 insertions(+), 95 deletions(-) create mode 100644 XGenerate/src/main/java/com/xbreeze/xgenerate/utils/SaxonXMLUtils.java diff --git a/XGenerate/src/main/java/com/xbreeze/xgenerate/model/ModelPreprocessor.java b/XGenerate/src/main/java/com/xbreeze/xgenerate/model/ModelPreprocessor.java index dac3aecc..9c208a49 100644 --- a/XGenerate/src/main/java/com/xbreeze/xgenerate/model/ModelPreprocessor.java +++ b/XGenerate/src/main/java/com/xbreeze/xgenerate/model/ModelPreprocessor.java @@ -27,27 +27,17 @@ import java.io.ByteArrayInputStream; import java.io.IOException; import java.util.ArrayList; -import java.util.Iterator; import java.util.List; import java.util.logging.Logger; import java.util.stream.Collectors; -import javax.xml.namespace.NamespaceContext; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; -import javax.xml.transform.Transformer; -import javax.xml.transform.TransformerException; -import javax.xml.transform.TransformerFactory; -import javax.xml.transform.dom.DOMSource; -import javax.xml.transform.stream.StreamResult; import javax.xml.xpath.XPathConstants; import javax.xml.xpath.XPathExpressionException; -import net.sf.saxon.xpath.XPathFactoryImpl; -import net.sf.saxon.xpath.XPathEvaluator; import net.sf.saxon.xpath.XPathExpressionImpl; - import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Attr; @@ -55,12 +45,13 @@ import org.w3c.dom.NodeList; import org.xml.sax.SAXException; -import com.xbreeze.xgenerate.config.NamespaceConfig; import com.xbreeze.xgenerate.config.model.ModelAttributeInjection; import com.xbreeze.xgenerate.config.model.ModelConfig; import com.xbreeze.xgenerate.config.model.ModelNodeRemoval; import com.xbreeze.xgenerate.config.model.ModelAttributeInjectionValueMapping; -import java.io.StringWriter; +import com.xbreeze.xgenerate.utils.SaxonXMLUtils; +import com.xbreeze.xgenerate.utils.XmlException; + /** * The model preprocessor. @@ -83,6 +74,7 @@ public static void preprocessModel(Model model, ModelConfig modelConfig) throws // Load the preprocessed model into memory DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); DocumentBuilder builder; + try { builder = factory.newDocumentBuilder(); } catch (ParserConfigurationException exc) { @@ -90,6 +82,9 @@ public static void preprocessModel(Model model, ModelConfig modelConfig) throws String.format("Error while reading model XML file: %s", exc.getMessage())); } Document doc; + SaxonXMLUtils xmlHelper = new SaxonXMLUtils(); + xmlHelper.setNamespaces(modelConfig.getNamespaces()); + try { doc = builder.parse(new ByteArrayInputStream(model.getModelFileContent().getBytes())); } catch(SAXException | IOException exc) { @@ -103,59 +98,41 @@ public static void preprocessModel(Model model, ModelConfig modelConfig) throws // removed in the next step if (modelConfig != null && modelConfig.getModelAttributeInjections() != null && modelConfig.getModelAttributeInjections().size() > 0) { - doc = performModelAttributeInjections(doc, - modelConfig.getModelAttributeInjections(), modelConfig.getNamespaces()); + doc = performModelAttributeInjections(doc, xmlHelper, + modelConfig.getModelAttributeInjections()); } // ModelNodeRemovals if (modelConfig != null && modelConfig.getModelNodeRemovals() != null && modelConfig.getModelNodeRemovals().size() > 0) { - doc = performModelNodeRemovals(doc, modelConfig.getModelNodeRemovals(), - modelConfig.getNamespaces()); + doc = performModelNodeRemovals(doc, xmlHelper, modelConfig.getModelNodeRemovals()); } //Transform the preprocessed document to string and store it in the model object. - String preprocessedModel = null; - DOMSource domSource = new DOMSource(doc); - StringWriter writer = new StringWriter(); - StreamResult result = new StreamResult(writer); - TransformerFactory tf = TransformerFactory.newInstance(); - Transformer transformer; + String preprocessedModel; try { - transformer = tf.newTransformer(); - transformer.transform(domSource, result); - } catch (TransformerException e) { - throw new ModelPreprocessorException( - String.format("Error transforming model XML document to string: %s", e.getMessage()) - ); + preprocessedModel = SaxonXMLUtils.XmlDocumentToString(doc); + } catch (XmlException e) { + throw new ModelPreprocessorException(String.format("Error transforming preprocessed model to string: %s", e.getMessage())); } - preprocessedModel = writer.toString(); + + // Store the pre-processed model as string in the Model object. model.setPreprocessedModel(preprocessedModel); logger.info("End model preprocessing"); } - private static Document performModelNodeRemovals(Document modelDoc, - ArrayList modelModelNodeRemovals, ArrayList namespaces) + private static Document performModelNodeRemovals(Document modelDoc, SaxonXMLUtils xmlHelper, + ArrayList modelModelNodeRemovals) throws ModelPreprocessorException { logger.fine("Performing model node removals."); - - XPathFactoryImpl xPathfactory = new XPathFactoryImpl(); - XPathEvaluator xpath = (XPathEvaluator) xPathfactory.newXPath(); - // If namespaces are defined, create a namespace context object and set it on the xpath object. - if (namespaces != null && namespaces.size() > 0) { - ModelNamespaceContext nsContext = new ModelNamespaceContext(namespaces); - xpath.setNamespaceContext(nsContext); - } - - // Loop through the model node removals and process them. for (ModelNodeRemoval mnr : modelModelNodeRemovals) { try { - XPathExpressionImpl expr = (XPathExpressionImpl)xpath.compile(mnr.getModelXPath()); + XPathExpressionImpl expr = xmlHelper.getXPathExpression(mnr.getModelXPath()); NodeList result = (NodeList) expr.evaluate(modelDoc, XPathConstants.NODESET); for (int i = 0; i < result.getLength(); i++) { Node node = result.item(i); @@ -173,24 +150,15 @@ private static Document performModelNodeRemovals(Document modelDoc, return modelDoc; } - private static Document performModelAttributeInjections(Document modelDoc, - ArrayList modelAttributeInjections, ArrayList namespaces) + private static Document performModelAttributeInjections(Document modelDoc, SaxonXMLUtils xmlHelper, + ArrayList modelAttributeInjections) throws ModelPreprocessorException { logger.fine("Performing model attribute injections."); - - XPathFactoryImpl xPathfactory = new XPathFactoryImpl(); - XPathEvaluator xpath = (XPathEvaluator) xPathfactory.newXPath(); - - // If namespaces are defined, create a namespace context object and set it on the xpath object. - if (namespaces != null && namespaces.size() > 0) { - ModelNamespaceContext nsContext = new ModelNamespaceContext(namespaces); - xpath.setNamespaceContext(nsContext); - } // Loop through the model attribute injections and process them. for (ModelAttributeInjection mai : modelAttributeInjections) { try { - XPathExpressionImpl expr = (XPathExpressionImpl)xpath.compile(mai.getModelXPath()); + XPathExpressionImpl expr = xmlHelper.getXPathExpression(mai.getModelXPath()); NodeList result = (NodeList) expr.evaluate(modelDoc, XPathConstants.NODESET); for (int i = 0; i < result.getLength(); i++) { @@ -199,7 +167,7 @@ private static Document performModelAttributeInjections(Document modelDoc, //If a target xpath is set, evaluate it to get a value if (mai.getTargetXPath() !=null) { try { - XPathExpressionImpl targetExpression = (XPathExpressionImpl)xpath.compile(mai.getTargetXPath()); + XPathExpressionImpl targetExpression = xmlHelper.getXPathExpression(mai.getTargetXPath()); targetValue = (String)targetExpression.evaluate(node, XPathConstants.STRING); } catch (XPathExpressionException e) { throw new ModelPreprocessorException(String.format("Error while processing model attribute injection for target XPath ´%s´: %s", mai.getTargetXPath(), e.getMessage())); @@ -209,7 +177,7 @@ private static Document performModelAttributeInjections(Document modelDoc, } else if (mai.getValueMappings() != null ) { //Get the input node value to use for finding the mapped output value try { - XPathExpressionImpl valueMappingExpression = (XPathExpressionImpl)xpath.compile(mai.getValueMappings().getInputNode()); + XPathExpressionImpl valueMappingExpression = xmlHelper.getXPathExpression(mai.getValueMappings().getInputNode()); String inputNodeValue = (String)valueMappingExpression.evaluate(node, XPathConstants.STRING); List foundValueMappings = mai.getValueMappings() .getModelAttributeInjectionValueMappings().stream() @@ -249,42 +217,4 @@ else if (foundValueMappings.size() > 1) { // Return the modified XML document. return modelDoc; } - - // helper class to deal with namespace aware preprocessing - private static class ModelNamespaceContext implements NamespaceContext { - - private ArrayList namespaces; - - public ModelNamespaceContext (ArrayList namespaces) { - this.namespaces = namespaces; - } - - @Override - public String getNamespaceURI(String prefix) { - for (NamespaceConfig ns: this.namespaces) { - if (ns.getPrefix().equalsIgnoreCase(prefix)) { - return ns.getNamespace(); - } - } - return null; - } - - @Override - public String getPrefix(String namespaceURI) { - for (NamespaceConfig ns: this.namespaces) { - if (ns.getNamespace().equalsIgnoreCase(namespaceURI)) { - return ns.getPrefix(); - } - } - return null; - } - - @Override - public Iterator getPrefixes(String namespaceURI) { - // TODO Auto-generated method stub - return null; - } - - } -} - +} \ No newline at end of file diff --git a/XGenerate/src/main/java/com/xbreeze/xgenerate/utils/SaxonXMLUtils.java b/XGenerate/src/main/java/com/xbreeze/xgenerate/utils/SaxonXMLUtils.java new file mode 100644 index 00000000..8b8a1717 --- /dev/null +++ b/XGenerate/src/main/java/com/xbreeze/xgenerate/utils/SaxonXMLUtils.java @@ -0,0 +1,103 @@ +package com.xbreeze.xgenerate.utils; + +import java.io.StringWriter; +import java.util.ArrayList; +import java.util.Iterator; + +import org.w3c.dom.Document; + +import com.xbreeze.xgenerate.config.NamespaceConfig; + +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerException; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamResult; +import javax.xml.xpath.XPathExpressionException; +import javax.xml.namespace.NamespaceContext; + +import net.sf.saxon.xpath.XPathEvaluator; +import net.sf.saxon.xpath.XPathExpressionImpl; +import net.sf.saxon.xpath.XPathFactoryImpl; + +public class SaxonXMLUtils { + + private XPathEvaluator _xpath = null; + + public static String XmlDocumentToString(Document doc) throws XmlException { + //Transform the preprocessed document to string and store it in the model object. + DOMSource domSource = new DOMSource(doc); + StringWriter writer = new StringWriter(); + StreamResult result = new StreamResult(writer); + TransformerFactory tf = TransformerFactory.newInstance(); + Transformer transformer; + try { + transformer = tf.newTransformer(); + transformer.transform(domSource, result); + } catch (TransformerException e) { + throw new XmlException( + String.format("Error transforming document to string: %s", e.getMessage()) + ); + } + return writer.toString(); + } + + //Evaluate the xpath expression and return the result as an XPathExpressionImpl object + public XPathExpressionImpl getXPathExpression(String xPathExpression) throws XPathExpressionException { + return(XPathExpressionImpl)this.getXPathEvaluator().compile(xPathExpression); + } + + //Return the XPathEvaluator, if it is not yet initialized, create a new one + public XPathEvaluator getXPathEvaluator() { + if (this._xpath == null) { + XPathFactoryImpl xPathfactory = new XPathFactoryImpl(); + this._xpath = (XPathEvaluator) xPathfactory.newXPath(); + } + return this._xpath; + } + + public void setNamespaces(ArrayList namespaces) { + // If namespaces are defined, create a namespace context object and set it on the xpath object. + if (namespaces != null && namespaces.size() > 0) { + ModelNamespaceContext nsContext = new ModelNamespaceContext(namespaces); + this.getXPathEvaluator().setNamespaceContext(nsContext); + } + } + + // helper class to deal with namespace aware preprocessing + private static class ModelNamespaceContext implements NamespaceContext { + + private ArrayList namespaces; + + public ModelNamespaceContext (ArrayList namespaces) { + this.namespaces = namespaces; + } + + @Override + public String getNamespaceURI(String prefix) { + for (NamespaceConfig ns: this.namespaces) { + if (ns.getPrefix().equalsIgnoreCase(prefix)) { + return ns.getNamespace(); + } + } + return null; + } + + @Override + public String getPrefix(String namespaceURI) { + for (NamespaceConfig ns: this.namespaces) { + if (ns.getNamespace().equalsIgnoreCase(namespaceURI)) { + return ns.getPrefix(); + } + } + return null; + } + + @Override + public Iterator getPrefixes(String namespaceURI) { + // TODO Auto-generated method stub + return null; + } + + } +} From 8d9b2b17171e46635b1049acf7ee06a71786bf3a Mon Sep 17 00:00:00 2001 From: Harmen Wessels Date: Fri, 25 Oct 2024 17:30:17 +0200 Subject: [PATCH 7/8] Refactored ModelPreprocessor to throw with cause i.s.o. own formatted message. Override GetLocalizedMessage in ModelPreprocessorException to format the exception message to what we want. Updated two tests one errors. --- .../xgenerate/model/ModelPreprocessor.java | 20 +++++++++---------- .../model/ModelPreprocessorException.java | 13 ++++++++++++ ...nfig_Model_ModelAttributeInjection.feature | 4 ++-- 3 files changed, 25 insertions(+), 12 deletions(-) diff --git a/XGenerate/src/main/java/com/xbreeze/xgenerate/model/ModelPreprocessor.java b/XGenerate/src/main/java/com/xbreeze/xgenerate/model/ModelPreprocessor.java index 9c208a49..0aed5664 100644 --- a/XGenerate/src/main/java/com/xbreeze/xgenerate/model/ModelPreprocessor.java +++ b/XGenerate/src/main/java/com/xbreeze/xgenerate/model/ModelPreprocessor.java @@ -36,22 +36,23 @@ import javax.xml.parsers.ParserConfigurationException; import javax.xml.xpath.XPathConstants; import javax.xml.xpath.XPathExpressionException; -import net.sf.saxon.xpath.XPathExpressionImpl; +import org.w3c.dom.Attr; import org.w3c.dom.Document; import org.w3c.dom.Element; -import org.w3c.dom.Attr; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.xml.sax.SAXException; import com.xbreeze.xgenerate.config.model.ModelAttributeInjection; +import com.xbreeze.xgenerate.config.model.ModelAttributeInjectionValueMapping; import com.xbreeze.xgenerate.config.model.ModelConfig; import com.xbreeze.xgenerate.config.model.ModelNodeRemoval; -import com.xbreeze.xgenerate.config.model.ModelAttributeInjectionValueMapping; import com.xbreeze.xgenerate.utils.SaxonXMLUtils; import com.xbreeze.xgenerate.utils.XmlException; +import net.sf.saxon.xpath.XPathExpressionImpl; + /** * The model preprocessor. @@ -88,8 +89,7 @@ public static void preprocessModel(Model model, ModelConfig modelConfig) throws try { doc = builder.parse(new ByteArrayInputStream(model.getModelFileContent().getBytes())); } catch(SAXException | IOException exc) { - throw new ModelPreprocessorException( - String.format("Error while reading model XML file: %s", exc.getMessage())); + throw new ModelPreprocessorException("Error while reading model XML file", exc.getCause()); } @@ -114,7 +114,7 @@ public static void preprocessModel(Model model, ModelConfig modelConfig) throws try { preprocessedModel = SaxonXMLUtils.XmlDocumentToString(doc); } catch (XmlException e) { - throw new ModelPreprocessorException(String.format("Error transforming preprocessed model to string: %s", e.getMessage())); + throw new ModelPreprocessorException("Error transforming preprocessed model to string", e.getCause()); } @@ -143,7 +143,7 @@ private static Document performModelNodeRemovals(Document modelDoc, SaxonXMLUtil } } } catch (XPathExpressionException e) { - throw new ModelPreprocessorException(String.format("Error processing XPATH expression for node removal %s, %s", mnr.getModelXPath(), e.getMessage())); + throw new ModelPreprocessorException(String.format("Error processing XPath expression for node removal %s", mnr.getModelXPath()), e.getCause()); } } // Return the modified XML document. @@ -170,7 +170,7 @@ private static Document performModelAttributeInjections(Document modelDoc, Saxon XPathExpressionImpl targetExpression = xmlHelper.getXPathExpression(mai.getTargetXPath()); targetValue = (String)targetExpression.evaluate(node, XPathConstants.STRING); } catch (XPathExpressionException e) { - throw new ModelPreprocessorException(String.format("Error while processing model attribute injection for target XPath ´%s´: %s", mai.getTargetXPath(), e.getMessage())); + throw new ModelPreprocessorException(String.format("Error while processing model attribute injection for target XPath ´%s´", mai.getTargetXPath()), e.getCause()); } } else if (mai.getTargetValue() != null) { targetValue = mai.getTargetValue(); @@ -202,7 +202,7 @@ else if (foundValueMappings.size() > 1) { foundValueMappings.size(), inputNodeValue)); } } catch (XPathExpressionException e) { - throw new ModelPreprocessorException(String.format("Error evaluating XPATH expression for value mapping %s, %s", mai.getValueMappings().getInputNode(), e.getMessage())); + throw new ModelPreprocessorException(String.format("Error evaluating XPath expression for value mapping %s", mai.getValueMappings().getInputNode()), e.getCause()); } } @@ -211,7 +211,7 @@ else if (foundValueMappings.size() > 1) { } } } catch (XPathExpressionException e) { - throw new ModelPreprocessorException(String.format("Error while processing model attribute injection for model XPath ´%s´: %s", mai.getModelXPath(), e.getMessage())); + throw new ModelPreprocessorException(String.format("Error while processing model attribute injection for model XPath ´%s´", mai.getModelXPath()), e.getCause()); } } // Return the modified XML document. diff --git a/XGenerate/src/main/java/com/xbreeze/xgenerate/model/ModelPreprocessorException.java b/XGenerate/src/main/java/com/xbreeze/xgenerate/model/ModelPreprocessorException.java index 39bae62a..97caf648 100644 --- a/XGenerate/src/main/java/com/xbreeze/xgenerate/model/ModelPreprocessorException.java +++ b/XGenerate/src/main/java/com/xbreeze/xgenerate/model/ModelPreprocessorException.java @@ -55,4 +55,17 @@ public ModelPreprocessorException(Throwable throwable) { public ModelPreprocessorException(String message, Throwable throwable) { super(message, throwable); } + + /** + * We override the localized message so we can construct the message ourselves, without listing the cause class names. + */ + @Override + public String getLocalizedMessage() { + if (this.getCause() != null) { + return String.format("%s: %s", this.getMessage(), this.getCause().getMessage()); + } else { + return this.getMessage(); + } + } + } diff --git a/XGenerate/src/test/resources/features/unit/config/model/Unit_Config_Model_ModelAttributeInjection.feature b/XGenerate/src/test/resources/features/unit/config/model/Unit_Config_Model_ModelAttributeInjection.feature index fac8a236..799b6e44 100644 --- a/XGenerate/src/test/resources/features/unit/config/model/Unit_Config_Model_ModelAttributeInjection.feature +++ b/XGenerate/src/test/resources/features/unit/config/model/Unit_Config_Model_ModelAttributeInjection.feature @@ -241,7 +241,7 @@ Feature: Unit_Config_Model_ModelAttributeInjection When I run the generator Then I expect the following error message: """ - com.xbreeze.xgenerate.model.ModelPreprocessorException: Error while processing model attribute injection for model XPath ´concats('test','test')´: net.sf.saxon.trans.XPathException: Cannot find a 2-argument function named Q{http://www.w3.org/2005/xpath-functions}concats() + com.xbreeze.xgenerate.model.ModelPreprocessorException: Error while processing model attribute injection for model XPath ´concats('test','test')´: Cannot find a 2-argument function named Q{http://www.w3.org/2005/xpath-functions}concats() """ Scenario: Inject using incorrect targetXPath @@ -265,5 +265,5 @@ Feature: Unit_Config_Model_ModelAttributeInjection When I run the generator Then I expect the following error message: """ - com.xbreeze.xgenerate.model.ModelPreprocessorException: Error while processing model attribute injection for target XPath ´concats('The entity was ', @name)´: net.sf.saxon.trans.XPathException: Cannot find a 2-argument function named Q{http://www.w3.org/2005/xpath-functions}concats() + com.xbreeze.xgenerate.model.ModelPreprocessorException: Error while processing model attribute injection for target XPath ´concats('The entity was ', @name)´: Cannot find a 2-argument function named Q{http://www.w3.org/2005/xpath-functions}concats() """ From 575e8ff1326956d6b5b0701f3c55dd097f797bc9 Mon Sep 17 00:00:00 2001 From: Harmen Wessels Date: Fri, 25 Oct 2024 17:35:37 +0200 Subject: [PATCH 8/8] Updated TODO comment. --- .../main/java/com/xbreeze/xgenerate/utils/SaxonXMLUtils.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/XGenerate/src/main/java/com/xbreeze/xgenerate/utils/SaxonXMLUtils.java b/XGenerate/src/main/java/com/xbreeze/xgenerate/utils/SaxonXMLUtils.java index 8b8a1717..af05d18e 100644 --- a/XGenerate/src/main/java/com/xbreeze/xgenerate/utils/SaxonXMLUtils.java +++ b/XGenerate/src/main/java/com/xbreeze/xgenerate/utils/SaxonXMLUtils.java @@ -95,7 +95,7 @@ public String getPrefix(String namespaceURI) { @Override public Iterator getPrefixes(String namespaceURI) { - // TODO Auto-generated method stub + // TODO Not implemented/used at the moment. return null; }