Compare commits
	
		
			18 Commits
		
	
	
		
			4128196b93
			...
			Add_xslt_p
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 75db025d80 | |||
| d33ac7e9f8 | |||
| bbae61da42 | |||
| 1d5038ca51 | |||
| bdb807d3e0 | |||
| 16b5522ff5 | |||
| 04e37258bf | |||
| b7af1eb33a | |||
| ec49f496da | |||
| 9cb949b485 | |||
| 097af3ed3a | |||
| b9f26f07a8 | |||
| 6a70c3c9c9 | |||
| a0d759f58b | |||
| 9f64b83844 | |||
| 0d469db020 | |||
| 38b3942bde | |||
| e834229cae | 
| @@ -1,5 +1,3 @@ | ||||
| from typing import Any | ||||
|  | ||||
| from lxml import etree, html | ||||
| from io import BytesIO | ||||
|  | ||||
| @@ -81,11 +79,9 @@ def xpath(source: str, xpath: str) -> str: | ||||
|     else: | ||||
|         result_string = "" | ||||
|         for e in result: | ||||
|             if isinstance(e, etree._Element): | ||||
|                 result_string += etree.tostring(e, pretty_print=True).decode() + "\n" | ||||
|             else: | ||||
|                 result_string += str(e) + "\n" | ||||
|     return result_string, "node" | ||||
|             result_string += etree.tostring(e, pretty_print=True).decode() + "\n" | ||||
|         return result_string, "node" | ||||
|  | ||||
|  | ||||
|  | ||||
| def xsd(source: str, xsd: str) -> bool: | ||||
|   | ||||
| @@ -84,8 +84,14 @@ | ||||
|     <dependency> | ||||
|       <groupId>xalan</groupId> | ||||
|       <artifactId>xalan</artifactId> | ||||
|       <version>2.7.2</version> | ||||
|       <version>2.7.3</version> | ||||
|     </dependency> | ||||
|     <dependency> | ||||
|       <groupId>xerces</groupId> | ||||
|       <artifactId>xercesImpl</artifactId> | ||||
|       <version>2.12.2</version> | ||||
|     </dependency> | ||||
|  | ||||
|  | ||||
|     <!-- Logging --> | ||||
|     <dependency> | ||||
|   | ||||
| @@ -68,7 +68,7 @@ public class MultipleXMLController implements RestController { | ||||
|     private XMLResponseBody processData(MultipleXmlJob xmlJob) throws Exception { | ||||
|         XmlEngine engine = xmlJob.getEngine(); | ||||
|         XMLMultipleFilesBody requestBody = xmlJob.getRequestBody(); | ||||
|         String result = engine.processXSLT(requestBody.getData(), requestBody.getProcessorData()); | ||||
|         String result = engine.processXSLT(requestBody.getParams(), requestBody.getData(), requestBody.getProcessorData()); | ||||
|         return new XMLResponseBody(result, "OK", requestBody.getVersion()); | ||||
|     } | ||||
|  | ||||
|   | ||||
| @@ -1,17 +1,26 @@ | ||||
| package com.r11.tools.controller; | ||||
|  | ||||
| import com.google.gson.Gson; | ||||
| import com.r11.tools.controller.internal.*; | ||||
| import com.r11.tools.controller.internal.GlobalControllerManifest; | ||||
| import com.r11.tools.controller.internal.HandlerType; | ||||
| import com.r11.tools.controller.internal.RestController; | ||||
| import com.r11.tools.controller.internal.ScopedControllerManifest; | ||||
| import com.r11.tools.controller.internal.XmlJob; | ||||
| import com.r11.tools.controller.internal.XmlJobType; | ||||
| import com.r11.tools.model.XMLRequestBody; | ||||
| import com.r11.tools.model.XMLResponseBody; | ||||
| import com.r11.tools.model.XPathQueryResult; | ||||
| import com.r11.tools.model.XmlTools; | ||||
| import com.r11.tools.xml.XmlEngine; | ||||
| import org.apache.logging.log4j.Logger; | ||||
| import spark.Request; | ||||
| import spark.Response; | ||||
|  | ||||
| import java.util.Objects; | ||||
|  | ||||
| /** | ||||
|  * Controller used to handle XML tools: XPath, XSD validation, XQuery and XSLT | ||||
|  * | ||||
|  * @author Adam Bem | ||||
|  */ | ||||
| @GlobalControllerManifest | ||||
| @@ -22,12 +31,15 @@ public class XmlController implements RestController { | ||||
|  | ||||
|     private final XmlEngine saxon; | ||||
|     private final XmlEngine xalan; | ||||
|     private final XmlTools tools; | ||||
|  | ||||
|     public XmlController(Gson gson, Logger logger, XmlEngine saxon, XmlEngine xalan) { | ||||
|         this.gson = gson; | ||||
|         this.logger = logger; | ||||
|         this.saxon = saxon; | ||||
|         this.xalan = xalan; | ||||
|         this.tools = new XmlTools(); | ||||
|  | ||||
|     } | ||||
|  | ||||
|     @ScopedControllerManifest(method = HandlerType.POST, path = "/xpath") | ||||
| @@ -48,6 +60,7 @@ public class XmlController implements RestController { | ||||
|  | ||||
|     @ScopedControllerManifest(method = HandlerType.POST, path = "/xslt") | ||||
|     public void acceptRequestXslt(Request request, Response response) { | ||||
|         System.out.println("received xslt"); | ||||
|         acceptRequest(request, response, XmlJobType.XSLT); | ||||
|     } | ||||
|  | ||||
| @@ -91,7 +104,6 @@ public class XmlController implements RestController { | ||||
|             responseBody.setDuration(duration); | ||||
|  | ||||
|             xmlJob.getResponse().status(200); | ||||
|  | ||||
|             this.logger.info("Request (" + xmlJob.getXmlJobType() + ", " + | ||||
|                     xmlJob.getEngine().getVersion() + | ||||
|                     ") processed in " + duration + " ms."); | ||||
| @@ -130,7 +142,7 @@ public class XmlController implements RestController { | ||||
|         String result = null; | ||||
|         switch (xmlJob.getXmlJobType()) { | ||||
|             case XSLT: | ||||
|                 result = engine.processXSLT(requestBody.getData(), requestBody.getProcessorData()); | ||||
|                 result = engine.processXSLT(requestBody.getParams(),requestBody.getData(), requestBody.getProcessorData()); | ||||
|                 break; | ||||
|             case XSD: | ||||
|                 result = engine.validate(requestBody.getData(), requestBody.getProcessorData()).trim(); | ||||
| @@ -141,9 +153,10 @@ public class XmlController implements RestController { | ||||
|                         requestBody.getVersion()); | ||||
|                 break; | ||||
|         } | ||||
|         return new XMLResponseBody(result, "OK", requestBody.getVersion()); | ||||
|         return new XMLResponseBody(result, "OK", requestBody.getProcessorData()); | ||||
|     } | ||||
|  | ||||
|  | ||||
|     private XMLResponseBody processingErrorResponse(Exception ex, XmlJob xmlJob) { | ||||
|         XmlEngine engine = xmlJob.getEngine(); | ||||
|         XmlJobType xmlJobType = xmlJob.getXmlJobType(); | ||||
|   | ||||
| @@ -7,7 +7,7 @@ import spark.Response; | ||||
| public class XmlJob { | ||||
|     private final Response response; | ||||
|     private final XMLRequestBody requestBody; | ||||
|     private final XmlEngine engine; | ||||
|     private XmlEngine engine; | ||||
|     private final XmlJobType xmlJobType; | ||||
|  | ||||
|     public XmlJob(Response response, XMLRequestBody requestBody, XmlEngine engine, XmlJobType xmlJobType) { | ||||
| @@ -16,7 +16,11 @@ public class XmlJob { | ||||
|         this.engine = engine; | ||||
|         this.xmlJobType = xmlJobType; | ||||
|     } | ||||
|  | ||||
|     public XmlJob(Response response, XMLRequestBody requestBody, XmlJobType xmlJobType) { | ||||
|         this.response = response; | ||||
|         this.requestBody = requestBody; | ||||
|         this.xmlJobType = xmlJobType; | ||||
|     } | ||||
|     public Response getResponse() { | ||||
|         return response; | ||||
|     } | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| package com.r11.tools.controller.internal; | ||||
|  | ||||
| public enum XmlJobType { | ||||
|     XPath("XPath"), XSD("XSD"), XQuery("XQuery"), XSLT("XSLT"); | ||||
|     XPath("XPath"), XSD("XSD"), XQuery("XQuery"), XSLT("XSLT"), XSLT_PARAM("XSLT_PARAM"); | ||||
|  | ||||
|     XmlJobType(String type) { | ||||
|     } | ||||
|   | ||||
| @@ -0,0 +1,20 @@ | ||||
| package com.r11.tools.model; | ||||
|  | ||||
| import com.google.gson.annotations.SerializedName; | ||||
|  | ||||
|  | ||||
| public class Param { | ||||
|     @SerializedName("key") | ||||
|     private String key; | ||||
|     @SerializedName("value") | ||||
|     private String value; | ||||
|  | ||||
|     public String getKey() { | ||||
|         return key; | ||||
|     } | ||||
|  | ||||
|     public String getValue() { | ||||
|         return value; | ||||
|     } | ||||
| } | ||||
|  | ||||
| @@ -2,6 +2,8 @@ package com.r11.tools.model; | ||||
|  | ||||
| import com.google.gson.annotations.SerializedName; | ||||
|  | ||||
| import java.util.List; | ||||
|  | ||||
| public class XMLMultipleFilesBody { | ||||
|  | ||||
|     @SerializedName("data") | ||||
| @@ -12,6 +14,8 @@ public class XMLMultipleFilesBody { | ||||
|     private String processor; | ||||
|     @SerializedName("version") | ||||
|     private String version; | ||||
|     @SerializedName("params") | ||||
|     private List<Param> params; | ||||
|  | ||||
|  | ||||
|     public String getProcessorData() { | ||||
| @@ -29,4 +33,9 @@ public class XMLMultipleFilesBody { | ||||
|     public XMLMultipleFilesData[] getData() { | ||||
|         return data; | ||||
|     } | ||||
|  | ||||
|     public List<Param> getParams() { | ||||
|         return params; | ||||
|     } | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -2,6 +2,8 @@ package com.r11.tools.model; | ||||
|  | ||||
| import com.google.gson.annotations.SerializedName; | ||||
|  | ||||
| import java.util.List; | ||||
|  | ||||
| /** | ||||
|  * POJO class used to contain body of XML related requests | ||||
|  * @author Adam Bem | ||||
| @@ -15,20 +17,22 @@ public class XMLRequestBody { | ||||
|     private String processor; | ||||
|     @SerializedName("version") | ||||
|     private String version; | ||||
|     @SerializedName("params") | ||||
|     private List<Param> params; | ||||
|  | ||||
|     public String getData() { | ||||
|         return data; | ||||
|     } | ||||
|  | ||||
|     public String getProcessorData() { | ||||
|         return processorData; | ||||
|     } | ||||
|  | ||||
|     public String getProcessorData() {return processorData;} | ||||
|     public String getProcessor() { | ||||
|         return processor; | ||||
|     } | ||||
|  | ||||
|     public String getVersion() { | ||||
|         return version; | ||||
|     } | ||||
|     public List<Param> getParams() { | ||||
|         return params; | ||||
|     } | ||||
|  | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -10,6 +10,10 @@ public class XMLResponseBody { | ||||
|     // Optional | ||||
|     private String type; | ||||
|  | ||||
|     public XMLResponseBody(String status){ | ||||
|         this.status = status; | ||||
|     } | ||||
|  | ||||
|     public XMLResponseBody(String result, String status, String processor) { | ||||
|         this.result = result; | ||||
|         this.status = status; | ||||
|   | ||||
| @@ -0,0 +1,64 @@ | ||||
| package com.r11.tools.model; | ||||
|  | ||||
| import org.w3c.dom.Document; | ||||
| import org.w3c.dom.Element; | ||||
| import org.xml.sax.SAXException; | ||||
|  | ||||
| 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 java.io.ByteArrayInputStream; | ||||
| import java.io.ByteArrayOutputStream; | ||||
| import java.io.IOException; | ||||
| import java.io.InputStream; | ||||
| import java.nio.charset.StandardCharsets; | ||||
| import java.util.HashMap; | ||||
| import java.util.List; | ||||
| import java.util.Map; | ||||
|  | ||||
| public class XmlTools { | ||||
|     private final Map<String, String> createdParameters = new HashMap<>(); | ||||
|     private void createNewNode(String paramName, String paramValue, Document doc) { | ||||
|         String parameter = "xsl:param"; | ||||
|         Element param = doc.createElement(parameter); | ||||
|         String nameAttribute = "name"; | ||||
|         param.setAttribute(nameAttribute, paramName); | ||||
|         String valueAttribute = "select"; | ||||
|         param.setAttribute(valueAttribute, "'" + paramValue + "'"); | ||||
|         doc.getDocumentElement().appendChild(param); | ||||
|     } | ||||
|     public String addParams(List<Param> params,String processorData) { | ||||
|         params.forEach(param -> | ||||
|                 createdParameters.put(param.getKey(), param.getValue()) | ||||
|         ); | ||||
|         try { | ||||
|             return docToString(addNode(processorData)); | ||||
|  | ||||
|         } catch (ParserConfigurationException | SAXException | IOException | TransformerException e) { | ||||
|             throw new RuntimeException(e); | ||||
|         }finally { | ||||
|             createdParameters.clear(); | ||||
|         } | ||||
|     } | ||||
|     private Document addNode(String processorData) throws ParserConfigurationException, SAXException, IOException { | ||||
|         DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder(); | ||||
|         InputStream stream = new ByteArrayInputStream(processorData.getBytes(StandardCharsets.UTF_8)); | ||||
|         Document doc = builder.parse(stream); | ||||
|         createdParameters.forEach((paramName, paramValue) -> createNewNode(paramName, paramValue, doc)); | ||||
|         return doc; | ||||
|     } | ||||
|     private String docToString(Document doc) throws TransformerException { | ||||
|         TransformerFactory transformerFactory = TransformerFactory.newInstance(); | ||||
|         Transformer transformer = transformerFactory.newTransformer(); | ||||
|         DOMSource source = new DOMSource(doc); | ||||
|         ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); | ||||
|         StreamResult result = new StreamResult(outputStream); | ||||
|         transformer.transform(source, result); | ||||
|         return outputStream.toString(); | ||||
|     } | ||||
| } | ||||
| @@ -1,7 +1,10 @@ | ||||
| package com.r11.tools.xml; | ||||
|  | ||||
| import com.r11.tools.model.Param; | ||||
| import com.r11.tools.model.XMLMultipleFilesData; | ||||
| import com.r11.tools.model.XMLRequestBody; | ||||
| import com.r11.tools.model.XPathQueryResult; | ||||
| import com.r11.tools.model.XmlTools; | ||||
| import net.sf.saxon.s9api.*; | ||||
|  | ||||
| import javax.xml.transform.stream.StreamSource; | ||||
| @@ -10,6 +13,7 @@ import java.nio.file.Files; | ||||
| import java.nio.file.Path; | ||||
| import java.nio.file.Paths; | ||||
| import java.util.Comparator; | ||||
| import java.util.List; | ||||
| import java.util.UUID; | ||||
|  | ||||
| /** | ||||
| @@ -26,10 +30,13 @@ public class Saxon implements XmlEngine{ | ||||
|      * @throws SaxonApiException thrown on stylesheet or transformation error | ||||
|      * @throws IOException thrown when file does not exist, or cannot be read. | ||||
|      */ | ||||
|     public String processXSLT(XMLMultipleFilesData[] data, String transform) throws SaxonApiException, IOException{ | ||||
|     XmlTools tools = new XmlTools(); | ||||
|     public String processXSLT(List<Param> params, XMLMultipleFilesData[] data, String transform) throws SaxonApiException, IOException{ | ||||
|         Processor processor = new Processor(false); | ||||
|         XsltCompiler compiler = processor.newXsltCompiler(); | ||||
|  | ||||
|         transform = tools.addParams(params,transform); | ||||
|  | ||||
|         String filesPath = "/tmp/"+UUID.randomUUID()+"/"; | ||||
|         try{ | ||||
|             createXMLFilesFromData(data, filesPath); | ||||
| @@ -78,14 +85,18 @@ public class Saxon implements XmlEngine{ | ||||
|  | ||||
|     /** | ||||
|      * Transforms string containing xml document via xslt | ||||
|      * @param data xml to be transformed | ||||
|      * | ||||
|      * @param params | ||||
|      * @param data      xml to be transformed | ||||
|      * @param transform xslt | ||||
|      * @return transformed xml | ||||
|      * @throws SaxonApiException thrown on stylesheet or transformation errors | ||||
|      */ | ||||
|     public String processXSLT(String data, String transform) throws SaxonApiException { | ||||
|     public String processXSLT(List<Param> params, String data, String transform) throws SaxonApiException { | ||||
|         Processor processor = new Processor(false); | ||||
|         XsltCompiler compiler = processor.newXsltCompiler(); | ||||
|         transform = tools.addParams(params,transform); | ||||
|         System.out.println(transform); | ||||
|         XsltExecutable stylesheet = compiler.compile(new StreamSource(new StringReader(transform))); | ||||
|         StringWriter sw = new StringWriter(); | ||||
|         Serializer out = processor.newSerializer(sw); | ||||
|   | ||||
| @@ -1,7 +1,10 @@ | ||||
| package com.r11.tools.xml; | ||||
|  | ||||
| import com.r11.tools.model.Param; | ||||
| import com.r11.tools.model.XMLMultipleFilesData; | ||||
| import com.r11.tools.model.XMLRequestBody; | ||||
| import com.r11.tools.model.XPathQueryResult; | ||||
| import com.r11.tools.model.XmlTools; | ||||
| import org.apache.xpath.XPathAPI; | ||||
| import org.w3c.dom.Document; | ||||
| import org.w3c.dom.Node; | ||||
| @@ -18,14 +21,11 @@ import javax.xml.transform.stream.StreamSource; | ||||
| import javax.xml.validation.Schema; | ||||
| import javax.xml.validation.SchemaFactory; | ||||
| import javax.xml.validation.Validator; | ||||
| import javax.xml.xpath.XPath; | ||||
| import javax.xml.xpath.XPathConstants; | ||||
| import javax.xml.xpath.XPathExpression; | ||||
| import javax.xml.xpath.XPathFactory; | ||||
| import java.io.ByteArrayOutputStream; | ||||
| import java.io.OutputStreamWriter; | ||||
| import java.io.StringReader; | ||||
| import java.io.StringWriter; | ||||
| import java.util.List; | ||||
|  | ||||
| /** | ||||
|  * Handler for Xalan engine | ||||
| @@ -35,16 +35,20 @@ public class Xalan implements XmlEngine{ | ||||
|  | ||||
|     /** | ||||
|      * Transforms string containing xml document via xslt | ||||
|      * @param data xml to be transformed | ||||
|      * | ||||
|      * @param params | ||||
|      * @param data      xml to be transformed | ||||
|      * @param transform xslt | ||||
|      * @return transformed xml | ||||
|      * @throws Exception thrown on stylesheet or transformation errors | ||||
|      */ | ||||
|     public String processXSLT(String data, String transform) throws Exception { | ||||
|     public String processXSLT(List<Param> params, String data, String transform) throws Exception { | ||||
|         DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); | ||||
|         DocumentBuilder builder = factory.newDocumentBuilder(); | ||||
|         Document document = builder.parse(new InputSource(new StringReader(data))); | ||||
|  | ||||
|         XmlTools tools = new XmlTools(); | ||||
|         transform = tools.addParams(params,transform); | ||||
|         StreamSource stylesource = new StreamSource(new StringReader(transform)); | ||||
|         Transformer transformer = TransformerFactory.newInstance().newTransformer(stylesource); | ||||
|         transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8"); | ||||
| @@ -65,15 +69,8 @@ public class Xalan implements XmlEngine{ | ||||
|         return nodeType == Node.CDATA_SECTION_NODE || nodeType == Node.TEXT_NODE; | ||||
|     } | ||||
|  | ||||
|     private boolean isAttributeNode(Node n) { | ||||
|         if (n == null) | ||||
|             return false; | ||||
|         short nodeType = n.getNodeType(); | ||||
|         return nodeType == Node.CDATA_SECTION_NODE || nodeType == Node.ATTRIBUTE_NODE; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public String processXSLT(XMLMultipleFilesData[] data, String transform) throws Exception { | ||||
|     public String processXSLT(List<Param> params, XMLMultipleFilesData[] data, String transform) throws Exception { | ||||
|         throw new UnsupportedOperationException("Xalan does not support multiple files XSLT processing"); | ||||
|     } | ||||
|  | ||||
| @@ -112,10 +109,7 @@ public class Xalan implements XmlEngine{ | ||||
|                     for (Node nn = n.getNextSibling(); isTextNode(nn); nn = nn.getNextSibling()) { | ||||
|                         resultString.append(nn.getNodeValue()); | ||||
|                     } | ||||
|                 } else if (isAttributeNode(n)) { | ||||
|                     resultString.append(n.getNodeValue()); | ||||
|                 } | ||||
|                 else { | ||||
|                 } else { | ||||
|                     ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); | ||||
|                     serializer.transform(new DOMSource(n), new StreamResult(new OutputStreamWriter(outputStream))); | ||||
|                     resultString.append(outputStream); | ||||
|   | ||||
| @@ -1,17 +1,17 @@ | ||||
| package com.r11.tools.xml; | ||||
|  | ||||
| import com.r11.tools.model.Param; | ||||
| import com.r11.tools.model.XMLMultipleFilesData; | ||||
| import com.r11.tools.model.XPathQueryResult; | ||||
|  | ||||
| import java.util.List; | ||||
|  | ||||
| public interface XmlEngine { | ||||
|  | ||||
|     String processXSLT(XMLMultipleFilesData[] data, String transform) throws Exception; | ||||
|     String processXSLT(List<Param> params, XMLMultipleFilesData[] data, String transform) throws Exception; | ||||
|     XPathQueryResult processXPath(String data, String query, String version) throws Exception; | ||||
|     String processXSLT(String data, String transform) throws Exception; | ||||
|     String processXSLT(List<Param> params, String data, String transform) throws Exception; | ||||
|     String validate(String data, String xsd) throws Exception; | ||||
|  | ||||
|     String executeXQuery(String data, String xquery, String version) throws Exception; | ||||
|  | ||||
|     public String getVersion(); | ||||
|  | ||||
| } | ||||
|   | ||||
							
								
								
									
										72
									
								
								Frontend/src/components/XsltParamComponent.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										72
									
								
								Frontend/src/components/XsltParamComponent.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,72 @@ | ||||
| <script setup lang="ts"> | ||||
| import {ref} from 'vue' | ||||
|  | ||||
| const emit = defineEmits<{ | ||||
|   (event: 'update-value', options: { name: string }[]): void; | ||||
| }>(); | ||||
|  | ||||
| const options = ref<{ name: string }[]>([ | ||||
|   { name: "Add Param" } | ||||
| ]); | ||||
|  | ||||
| const sendToParent = () => { | ||||
|   options.value.forEach((value) => {console.log("KID" + value + options.value.indexOf(value))}) | ||||
|   emit('update-value', options.value); | ||||
| }; | ||||
| const nameInput = ref('') | ||||
| const valueInput = ref('') | ||||
| const selectedOption = ref(options.value[0].name) | ||||
|  | ||||
| const isNumeric = (string : string) => /^[+-]?\d+(\.\d+)?$/.test(string) | ||||
|  | ||||
| const selectedFunction = () => { | ||||
|   const action = selectOption(selectedOption.value); | ||||
|   const name = nameInput.value.trim(); | ||||
|   const value = valueInput.value.trim(); | ||||
|  | ||||
|   if (action === "Add Param" && name && value && !isNumeric(name)) { | ||||
|     options.value.push({ name: `${name} = ${value}` }); | ||||
|     nameInput.value = valueInput.value = ""; | ||||
|   } | ||||
|  | ||||
|   if (action === "Remove Param") { | ||||
|     options.value = options.value.filter(option => option.name !== selectedOption.value); | ||||
|     selectedOption.value = options.value.length ? options.value[0].name : ""; | ||||
|     nameInput.value = valueInput.value = ""; | ||||
|   } | ||||
|   sendToParent(); | ||||
| }; | ||||
| function selectOption(option: string): string { | ||||
|   return option == "Add Param" ? "Add Param" : "Remove Param" | ||||
| } | ||||
| </script> | ||||
|  | ||||
| <template> | ||||
|   <input | ||||
|       v-if="selectedOption==='Add Param'" | ||||
|       id="NameTextInput" | ||||
|       v-model="nameInput" | ||||
|       class="text-input h-full px-3 rounded-full border border-slate-400 bg-white dark:text-slate-100 dark:bg-gray-600 w-48" | ||||
|       type="text" | ||||
|       placeholder="Name" | ||||
|   /> | ||||
|   <input | ||||
|       v-if="selectedOption==='Add Param'" | ||||
|       id="ValueTextInput" | ||||
|       v-model="valueInput" | ||||
|       class="text-input h-full px-3 rounded-full border border-slate-400 bg-white dark:text-slate-100 dark:bg-gray-600 w-48" | ||||
|       type="text" | ||||
|       placeholder="Value" | ||||
|   /> | ||||
|   <select | ||||
|       v-model="selectedOption" | ||||
|       class="flex h-full px-3 rounded-full border border-slate-400 bg-white dark:text-slate-100 dark:bg-gray-600 w-48" | ||||
|   > | ||||
|     <option v-for="option in options" :value="option.name" :key="option.name"> | ||||
|       {{ option.name }} | ||||
|     </option> | ||||
|   </select> | ||||
|   <button class="tool-button w-24" @click="selectedFunction">{{ selectOption(selectedOption) }}</button> | ||||
|  | ||||
| </template> | ||||
|  | ||||
| @@ -2,8 +2,7 @@ | ||||
| import { onBeforeUpdate, inject } from 'vue' | ||||
| import { Codemirror } from 'vue-codemirror' | ||||
| import { oneDark } from '@codemirror/theme-one-dark' | ||||
| import { createTheme} from 'thememirror'; | ||||
| import {tags as t} from '@lezer/highlight'; | ||||
| import { espresso } from 'thememirror'; | ||||
| import {xml} from '@codemirror/lang-xml' | ||||
| import {json} from '@codemirror/lang-json' | ||||
| import {html} from '@codemirror/lang-html' | ||||
| @@ -19,47 +18,6 @@ const props= defineProps({ | ||||
|     }, | ||||
| }) | ||||
|  | ||||
| const lightTheme = createTheme({ | ||||
| 	variant: 'light', | ||||
| 	settings: { | ||||
| 		background: '#FFFFFF', | ||||
| 		foreground: '#000000', | ||||
| 		caret: '#000000', | ||||
| 		selection: '#80C7FF', | ||||
| 		gutterBackground: '#FFFFFF', | ||||
| 		gutterForeground: '#00000070', | ||||
| 		lineHighlight: '#C1E2F840', | ||||
| 	}, | ||||
| 	styles: [ | ||||
| 		{ | ||||
| 			tag: t.comment, | ||||
| 			color: '#AAAAAA', | ||||
| 		}, | ||||
| 		{ | ||||
| 			tag: [t.keyword, t.operator, t.typeName, t.tagName, t.propertyName], | ||||
| 			color: '#2F6F9F', | ||||
| 			fontWeight: 'bold', | ||||
| 		}, | ||||
| 		{ | ||||
| 			tag: [t.attributeName, t.definition(t.propertyName)], | ||||
| 			color: '#4F9FD0', | ||||
| 		}, | ||||
| 		{ | ||||
| 			tag: [t.className, t.string, t.special(t.brace)], | ||||
| 			color: '#CF4F5F', | ||||
| 		}, | ||||
| 		{ | ||||
| 			tag: t.number, | ||||
| 			color: '#CF4F5F', | ||||
| 			fontWeight: 'bold', | ||||
| 		}, | ||||
| 		{ | ||||
| 			tag: t.variableName, | ||||
| 			fontWeight: 'bold', | ||||
| 		}, | ||||
| 	], | ||||
| }); | ||||
|  | ||||
| const emit = defineEmits( | ||||
|   [ | ||||
|     'update:updatedCode' | ||||
| @@ -79,7 +37,7 @@ function selectTheme() { | ||||
|   if (isDarkModeSet()) | ||||
|     return oneDark; | ||||
|   else | ||||
|     return lightTheme; | ||||
|     return espresso; | ||||
| } | ||||
|  | ||||
| function isDarkModeSet(){ | ||||
| @@ -125,4 +83,4 @@ function parseLanguage(name: String){ | ||||
|       /> | ||||
|      | ||||
|   </div> | ||||
| </template> | ||||
| </template> | ||||
|   | ||||
| @@ -2,69 +2,95 @@ | ||||
| import InsertTemplateComponent from '@components/common/InsertTemplateComponent.vue' | ||||
| import XMLButtonFormatterComponent from '@components/formatter/XMLButtonFormatterComponent.vue' | ||||
| import CodeEditor from '@/components/common/CodeEditorComponent.vue' | ||||
| import XsltParamComponent from '@/components/XsltParamComponent.vue' | ||||
|  | ||||
| import { ref } from 'vue' | ||||
| import {ref} from 'vue' | ||||
|  | ||||
| const props = defineProps( | ||||
|     { | ||||
|         stylizedName: {type: String, required: true}, | ||||
|         data: {type: String}, | ||||
|     } | ||||
| ) | ||||
| const emit = defineEmits(['update:modelValue']) | ||||
| const props = defineProps<{ | ||||
|   stylizedName: string; | ||||
|   data?: string; | ||||
|   params?: { name: string }[]; | ||||
| }>(); | ||||
|  | ||||
| const emit = defineEmits(['update:modelValue','update:params']) | ||||
|  | ||||
| const data = ref('') | ||||
| const inputFile = ref() | ||||
| const params = ref<{ name: string }[]>([]); | ||||
|  | ||||
| function sendValue() { | ||||
|     emit('update:modelValue', data.value) | ||||
|   emit('update:modelValue', data.value) | ||||
| } | ||||
|  | ||||
| function updateParams(newParams: { name: string }[]) { | ||||
|   emit('update:params', newParams); | ||||
| } | ||||
|  | ||||
|  | ||||
| function updateData(newData: string, clearFileSelector: boolean = true) { | ||||
|     data.value = newData | ||||
|     if (clearFileSelector) | ||||
|         inputFile.value.value = ''; | ||||
|     sendValue() | ||||
|   data.value = newData | ||||
|   if (clearFileSelector) | ||||
|     inputFile.value.value = ''; | ||||
|   sendValue() | ||||
| } | ||||
|  | ||||
| function clear() { | ||||
|     updateData('') | ||||
|   updateData('') | ||||
| } | ||||
|  | ||||
| function canBeFormatted() { | ||||
|     return props.stylizedName.toLowerCase() == 'xml' ||  | ||||
|            props.stylizedName.toLowerCase() == 'xsd' ||  | ||||
|            props.stylizedName.toLowerCase() == 'xslt' | ||||
|   return props.stylizedName.toLowerCase() == 'xml' || | ||||
|       props.stylizedName.toLowerCase() == 'xsd' || | ||||
|       props.stylizedName.toLowerCase() == 'xslt' | ||||
| } | ||||
|  | ||||
| function readFile(file : any) { | ||||
|      | ||||
|     const reader = new FileReader() | ||||
|     reader.onloadend = () => { | ||||
|         let result = reader.result?.toString() | ||||
|         if (typeof result == "string") | ||||
|             updateData(result, false); | ||||
|              | ||||
|     } | ||||
|     reader.readAsText(file.target.files[0]) | ||||
| function addParameters() { | ||||
|   return props.stylizedName?.toLowerCase() == "xslt" | ||||
| } | ||||
|  | ||||
| function readFile(file: any) { | ||||
|  | ||||
|   const reader = new FileReader() | ||||
|   reader.onloadend = () => { | ||||
|     let result = reader.result?.toString() | ||||
|     if (typeof result == "string") | ||||
|       updateData(result, false) | ||||
|  | ||||
|   } | ||||
|   reader.readAsText(file.target.files[0]) | ||||
| } | ||||
|  | ||||
| const handleUpdateValue = (options: { name: string }[]) => { | ||||
|   console.log("from parent" +options.length) | ||||
|   params.value = options | ||||
|   updateParams(params.value) | ||||
| }; | ||||
|  | ||||
| </script> | ||||
|  | ||||
| <template> | ||||
|     <div class="flex flex-col w-full h-1/2 lg:h-1/2 flex-none xl:pr-2 2xl:pr-4 pb-2"> | ||||
|         <div class="flex place-content-between w-full items-center"> | ||||
|             <span class="dark:text-white mr-2">{{ stylizedName }}</span> | ||||
|             <div class="flex space-x-2 pb-2 overflow-x-auto"> | ||||
|                 <div class="flex items-stretch w-64"> | ||||
|                     <input id="fileLoader" ref="inputFile" class="file-selector" type="file" accept=".xml,.xql,.xquery,.xslt,text/xml,text/plain" @change="readFile" /> | ||||
|                 </div> | ||||
|                  | ||||
|                 <InsertTemplateComponent :stylized-name="props.stylizedName" @update:default-data="updateData"></InsertTemplateComponent> | ||||
|                 <XMLButtonFormatterComponent v-if="canBeFormatted()" :xml="data" @update:result="(data:any) => updateData(data.result)"></XMLButtonFormatterComponent> | ||||
|                 <button class="tool-button" @click="clear">Clear</button> | ||||
|             </div> | ||||
|         </div> | ||||
|         <CodeEditor @update:updated-code="updateData" v-model="data" :code="data" :config="{disabled:false, language:stylizedName}"></CodeEditor> | ||||
|   <div class="flex flex-col w-full h-1/2 lg:h-1/2 flex-none xl:pr-2 2xl:pr-4 pb-2"> | ||||
|     <div class="flex justify-between mb-2"></div> | ||||
|  | ||||
|     <div class="flex place-content-between w-full items-center"> | ||||
|  | ||||
|       <span class="dark:text-white mr-2">{{ stylizedName }}</span> | ||||
|  | ||||
|       <div class="flex items-stretch w-64"> | ||||
|         <input id="fileLoader" ref="inputFile" class="file-selector" type="file" accept=".xml,.xql,.xquery,.xslt,text/xml,text/plain" @change="readFile"/> | ||||
|       </div> | ||||
|  | ||||
|       <div class="flex space-x-2 pb-2 overflow-x-auto"> | ||||
|         <InsertTemplateComponent :stylized-name="props.stylizedName" @update:default-data="updateData"></InsertTemplateComponent> | ||||
|         <XMLButtonFormatterComponent v-if="canBeFormatted()" :xml="data" @update:result="(data:any) => updateData(data.result)"></XMLButtonFormatterComponent> | ||||
|         <button class="tool-button" @click="clear">Clear</button> | ||||
|       </div> | ||||
|     </div> | ||||
|  | ||||
|     <div class="flex justify-end gap-2 place-content-between w-full items-center"> | ||||
|         <XsltParamComponent v-if="addParameters()" @update-value="handleUpdateValue" :xslt="data"/> | ||||
|     </div> | ||||
|  | ||||
|     <CodeEditor @update:updated-code="updateData" v-model="data" :code="data" :config="{disabled:false, language:stylizedName}"></CodeEditor> | ||||
|   </div> | ||||
| </template> | ||||
| @@ -1,22 +1,22 @@ | ||||
| <script setup lang="ts"> | ||||
| import { onMounted, ref } from 'vue'; | ||||
| import { type TabData } from '../common/TabData' | ||||
| import {onMounted, ref} from 'vue'; | ||||
| import {type TabData} from '../common/TabData' | ||||
| import CodeEditor from '@/components/common/CodeEditorComponent.vue'; | ||||
|  | ||||
| const props = defineProps( | ||||
|     { | ||||
|         tool: {type: String, required: true}, | ||||
|         xml: {type: [String, Array<TabData>], required: true}, | ||||
|         query: {type: String, required: true}, | ||||
|         activeTabId: {type: Number, required: false} | ||||
|     } | ||||
| ) | ||||
| const props = defineProps<{ | ||||
|   tool: string; | ||||
|   xml: string | TabData[]; | ||||
|   query: string; | ||||
|   params?: { name: string }[]; | ||||
| }>(); | ||||
|  | ||||
| const emit = defineEmits(["update", "update:engine"]); | ||||
| const emit = defineEmits(["update"]); | ||||
|  | ||||
| const result = ref(''); | ||||
|  | ||||
| let enginesForCurrentTool = ref(["saxon", "xalan", "libxml"]); | ||||
| const formattedParams = ref(); | ||||
|  | ||||
|  | ||||
| const allVersions = ["1.0", "2.0", "3.0", "3.1"]; | ||||
| let versionsForCurrentEngine = ref([""]); | ||||
| @@ -87,7 +87,9 @@ function selectDefaultVersion() { | ||||
| } | ||||
|  | ||||
| function process() { | ||||
|     let request:Request = prepareRequest(); | ||||
|   props.params?.forEach((param) => {console.log(param.name)}) | ||||
|  | ||||
|   let request:Request = prepareRequest(); | ||||
|     fetchRequest(request).then((data) => { | ||||
|         updateOutputField(data); | ||||
|     }) | ||||
| @@ -100,6 +102,7 @@ function updateOutputField(data: any) { | ||||
| } | ||||
|  | ||||
| function prepareRequest():Request { | ||||
|     formattedParams.value = formatParams() | ||||
|     let request = new Request(prepareURL(), { | ||||
|         body: selectRequestBodyType(), | ||||
|         method: "POST" | ||||
| @@ -110,42 +113,51 @@ function prepareRequest():Request { | ||||
|  | ||||
| function prepareURL(): string { | ||||
|     const engineEndpoint = engine.value == "libxml" ? "libxml" : "java"; | ||||
|  | ||||
|     | ||||
|     let tool = props.tool; | ||||
|     if (Array.isArray(props.xml) && props.xml.length > 1 && engine.value == "saxon") | ||||
|     if (Array.isArray(props.xml) && props.xml.length > 1) | ||||
|         tool = "multiple/xslt"; | ||||
|  | ||||
|     return document.location.protocol + "//" + document.location.hostname + "/" + engineEndpoint + "/" + tool; | ||||
| } | ||||
|  | ||||
| function selectRequestBodyType() : string { | ||||
|     if (Array.isArray(props.xml) && (engine.value == "xalan" || engine.value == "libxml")) | ||||
|       return prepareRequestBodySingleXml(props.xml.at(props.activeTabId!)!.data) | ||||
|     else if (Array.isArray(props.xml) && props.xml.length > 1) | ||||
|       return prepareRequestBodyMultiXml(); | ||||
|     if (Array.isArray(props.xml) && props.xml.length > 1) | ||||
|         return prepareRequestBodyMultiXml(); | ||||
|     else if (Array.isArray(props.xml)) | ||||
|       return prepareRequestBodySingleXml(props.xml.at(0)!.data); | ||||
|         return prepareRequestBodySingleXml(props.xml.at(0)!.data); | ||||
|     else | ||||
|       return prepareRequestBodySingleXml(props.xml!); | ||||
|         return prepareRequestBodySingleXml(props.xml!); | ||||
| } | ||||
| function formatParams() { | ||||
|   return props.params?.slice(1).map(param => { | ||||
|     const [key, value] = param.name.split(" = "); | ||||
|     return { key, value }; | ||||
|   }) ?? []; | ||||
| } | ||||
|  | ||||
| function prepareRequestBodySingleXml(data: string):string { | ||||
|     let requestBody = JSON.stringify({ | ||||
|  | ||||
|   let requestBody = JSON.stringify({ | ||||
|         "params": formattedParams.value, | ||||
|         "data": data, | ||||
|         "processorData": props.query, | ||||
|         "processor": engine.value, | ||||
|         "version": version.value | ||||
|     }); | ||||
|     console.log(requestBody); | ||||
|     return requestBody; | ||||
| } | ||||
|  | ||||
| function prepareRequestBodyMultiXml():string { | ||||
|     if (!Array.isArray(props.xml)) | ||||
|  | ||||
|   if (!Array.isArray(props.xml)) | ||||
|         return ""; | ||||
|  | ||||
|     let xmlFilesArray = convertDataArray(props.xml); | ||||
|  | ||||
|     let requestBody = JSON.stringify({ | ||||
|         "params": formattedParams.value, | ||||
|         "data": xmlFilesArray, | ||||
|         "processorData": props.query, | ||||
|         "processor": engine.value, | ||||
| @@ -191,10 +203,6 @@ function emitVersionChange() { | ||||
|     emit("update", version.value); | ||||
| } | ||||
|  | ||||
| function emitEngineChange() { | ||||
|   emit("update:engine", engine.value); | ||||
| } | ||||
|  | ||||
| function isVersionSelectionAvailable() { | ||||
|     return !(versionsForCurrentEngine.value.length == 1 && versionsForCurrentEngine.value.at(0) == "N/A"); | ||||
| } | ||||
| @@ -209,11 +217,6 @@ function highlightField() { | ||||
|     return ""; | ||||
| } | ||||
|  | ||||
| function handleChange() { | ||||
|   changeAvailableVersions(); | ||||
|   emitEngineChange(); | ||||
| } | ||||
|  | ||||
| </script> | ||||
|  | ||||
| <template> | ||||
| @@ -221,7 +224,7 @@ function handleChange() { | ||||
|         <div class="flex place-content-between w-full items-center pb-2"> | ||||
|             <span class="dark:text-white">Result:</span> | ||||
|             <div class="flex space-x-2 overflow-x-auto"> | ||||
|                 <select v-model="engine" name="engine" @change="handleChange()" class="px-3 rounded-full border border-slate-400 bg-white dark:text-slate-100 dark:bg-gray-600"> | ||||
|                 <select v-model="engine" name="engine" @change="changeAvailableVersions()" class="px-3 rounded-full border border-slate-400 bg-white dark:text-slate-100 dark:bg-gray-600"> | ||||
|                     <option v-for="engine in enginesForCurrentTool" :value="engine">{{ engine }}</option> | ||||
|                 </select> | ||||
|                 <select v-model="version" v-if="isVersionSelectionAvailable()" name="version" @change="emitVersionChange()" class="px-3 rounded-full border border-slate-400 bg-white dark:text-slate-100 dark:bg-gray-600"> | ||||
|   | ||||
| @@ -10,11 +10,11 @@ const props = defineProps( | ||||
|     { | ||||
|         stylizedName: {type: String, required: true}, | ||||
|         data: {type: Array<TabData>}, | ||||
|         tabCountLimit: {type: Number, required: false, validator: (value) => typeof value == "number" && value > 0}, | ||||
|         engine: {type: String, required: true} | ||||
|         tabCountLimit: {type: Number, required: false, validator: (value) => typeof value == "number" && value > 0} | ||||
|     } | ||||
| ) | ||||
| const emit = defineEmits(['update:modelValue', 'update:activeTabId']) | ||||
| const emit = defineEmits(['update:modelValue']) | ||||
|  | ||||
| const newTabId = ref(0); | ||||
| const activeTabId = ref(0); | ||||
| const data = ref('') | ||||
| @@ -30,9 +30,6 @@ tabs.value.push({ | ||||
| function sendValue() { | ||||
|     emit('update:modelValue', tabs.value); | ||||
| } | ||||
| function emitActiveTab() { | ||||
|   emit('update:activeTabId', activeTabId.value); | ||||
| } | ||||
|  | ||||
| function updateData(newData: string) { | ||||
|     data.value = newData; | ||||
| @@ -76,7 +73,6 @@ function changeActiveTab(id : number) { | ||||
|      | ||||
|     tabs.value.at(index)!.data = data.value; | ||||
|     activeTabId.value = id; | ||||
|     emitActiveTab(); | ||||
|     data.value = tabs.value.at(newIndex)!.data; | ||||
|  | ||||
|     sendValue(); | ||||
| @@ -132,7 +128,7 @@ function findIndexWithID(id : number) : number { | ||||
|     <div class="flex flex-col w-full h-1/2 lg:h-1/2 flex-none xl:pr-2 2xl:pr-4 pb-2"> | ||||
|         <div class="flex justify-between mb-2"> | ||||
|             <div class="flex gap-2 overflow-x-auto"> | ||||
|                 <TabComponent @click:activate="changeActiveTab" @click:remove="removeTab" v-for="tab in tabs" :id="tab.id" :isActive="tab.id == activeTabId" :class="[tab.id !== activeTabId && (props.engine === 'xalan' ? 'disabled-tab' : '' || props.engine === 'libxml' ? 'disabled-tab' : '')]">{{ tab.name }}</TabComponent> | ||||
|                 <TabComponent @click:activate="changeActiveTab" @click:remove="removeTab" v-for="tab in tabs" :id="tab.id" :isActive="tab.id == activeTabId">{{ tab.name }}</TabComponent> | ||||
|             </div> | ||||
|             <div class="flex gap-2"> | ||||
|                 <div class="flex items-stretch w-64"> | ||||
|   | ||||
| @@ -43,7 +43,3 @@ | ||||
| .tab-active { | ||||
|     @apply py-2 px-3 h-fit text-slate-700 border-t border-l border-r border-slate-400 rounded-t-2xl bg-gradient-to-r from-blue-400 to-sky-300  dark:text-white dark:from-sky-600 dark:to-sky-800 hover:bg-blue-400 | ||||
| } | ||||
|  | ||||
| .disabled-tab { | ||||
|     @apply py-2 px-3 h-fit text-slate-400 border-t border-l border-r border-slate-300 rounded-t-2xl bg-gradient-to-r from-gray-200 to-gray-300 dark:from-gray-700 dark:to-gray-800 pointer-events-none opacity-50; | ||||
| } | ||||
|   | ||||
| @@ -11,8 +11,7 @@ import { ref } from 'vue'; | ||||
| const xml = ref(new Array<TabData>); | ||||
| const query = ref(''); | ||||
| const version = ref(''); | ||||
| const engine = ref('') | ||||
| const activeTabId = ref(0) | ||||
| const params =  ref<{ name: string }[]>([]); | ||||
|  | ||||
| function updateVersion(newVersion: string) { | ||||
|     version.value = newVersion; | ||||
| @@ -24,10 +23,10 @@ function updateVersion(newVersion: string) { | ||||
|     <div id="layout" class="flex flex-row w-full h-full"> | ||||
|         <div class="flex flex-col 2xl:flex-row w-full xl:w-7/12 grow overflow-hide pr-2"> | ||||
|             <div class="flex flex-col w-full 2xl:w-1/2 h-2/3 2xl:h-full flex-none items-center"> | ||||
|                 <xmlTabbedInputComponent stylized-name="XML" v-model:activeTabId="activeTabId" :engine="engine" :tab-count-limit="3" v-model="xml"></xmlTabbedInputComponent> | ||||
|                 <xmlInputFieldComponent stylized-name="XSLT" :data="query" v-model="query"></xmlInputFieldComponent> | ||||
|                 <xmlTabbedInputComponent stylized-name="XML" :tab-count-limit="3" v-model="xml"></xmlTabbedInputComponent> | ||||
|                 <xmlInputFieldComponent stylized-name="XSLT" :data="query" v-model:params="params" v-model="query"></xmlInputFieldComponent> | ||||
|             </div> | ||||
|             <xmlOutputFieldComponent tool="xslt" :xml="xml" :active-tab-id="activeTabId" v-model:engine="engine" :query="query" @update="updateVersion"></xmlOutputFieldComponent> | ||||
|             <xmlOutputFieldComponent tool="xslt" :xml="xml" :query="query" :params="params" @update="updateVersion"></xmlOutputFieldComponent> | ||||
|         </div> | ||||
|         <TooltipComponent tool-type="xslt" :version="version"></TooltipComponent> | ||||
|     </div> | ||||
|   | ||||
| @@ -1,21 +0,0 @@ | ||||
| --- | ||||
|  | ||||
| name: "Bug template"     | ||||
| about: "This template is for reporting bugs"   | ||||
| title: "Bug"     | ||||
|  | ||||
| --- | ||||
|  | ||||
| ## Description | ||||
| *A clear and concise description of the issue.* | ||||
|  | ||||
| ### Selected Fields | ||||
| - **Engine:** [Specify the engine used] | ||||
| - **Version:** [Specify the version] | ||||
| - **Tool:** [Specify the currently used tool] | ||||
|  | ||||
| ### Data:  | ||||
| *Paste used xml/xslt/json etc...* | ||||
|  | ||||
| ## Additional Context | ||||
| *Any other information that might help with this issue.* | ||||
| @@ -1,10 +0,0 @@ | ||||
| --- | ||||
|  | ||||
| name: "Feature request template"     | ||||
| about: "This template is for requesting features"     | ||||
| title: "Feature request"  | ||||
|  | ||||
| --- | ||||
|  | ||||
| ## Feature Request  | ||||
| *Describe the feature you’d like to see and why it’s useful.* | ||||
		Reference in New Issue
	
	Block a user