This is an automated email from the ASF dual-hosted git repository. davsclaus pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/camel.git
The following commit(s) were added to refs/heads/main by this push: new dfdd5b1274a CAMEL-19087: camel trace - Added pretty option dfdd5b1274a is described below commit dfdd5b1274a5170da5e78c3679ea7e7d52bb31e8 Author: Claus Ibsen <claus.ib...@gmail.com> AuthorDate: Sun Mar 5 21:06:44 2023 +0100 CAMEL-19087: camel trace - Added pretty option --- .../apache/camel/util/xml/XmlPrettyPrinter.java | 182 +++++++++++++++++++++ .../camel/util/xml/XmlPrettyPrinterTest.java | 53 ++++++ .../core/commands/action/CamelTraceAction.java | 23 ++- .../camel/dsl/jbang/core/common/XmlHelper.java | 34 ++++ 4 files changed, 291 insertions(+), 1 deletion(-) diff --git a/core/camel-xml-jaxp/src/main/java/org/apache/camel/util/xml/XmlPrettyPrinter.java b/core/camel-xml-jaxp/src/main/java/org/apache/camel/util/xml/XmlPrettyPrinter.java new file mode 100644 index 00000000000..23a91a2081b --- /dev/null +++ b/core/camel-xml-jaxp/src/main/java/org/apache/camel/util/xml/XmlPrettyPrinter.java @@ -0,0 +1,182 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.camel.util.xml; + +import java.io.ByteArrayInputStream; + +import javax.xml.XMLConstants; +import javax.xml.parsers.SAXParser; +import javax.xml.parsers.SAXParserFactory; + +import org.xml.sax.Attributes; +import org.xml.sax.SAXException; +import org.xml.sax.helpers.DefaultHandler; + +import static org.apache.camel.util.StringHelper.padString; + +public final class XmlPrettyPrinter { + + @FunctionalInterface + public interface ColorPrintElement { + + int DECLARATION = 1; + int ELEMENT = 2; + int ATTRIBUTE_KEY = 3; + int ATTRIBUTE_VALUE = 4; + int ATTRIBUTE_EQUAL = 5; + int ATTRIBUTE_QUOTE = 6; + int VALUE = 7; + + String color(int type, String value); + } + + public static String colorPrint(String xml, int blanks, boolean declaration, ColorPrintElement color) + throws Exception { + return doParse(xml, blanks, declaration, color); + } + + /** + * Pretty print the XML (does not use DOM or any kind of parser) + * + * @param xml the XML + * @param blanks number of blanks to use as indent + * @return the XML in pretty, without XML declaration + */ + public static String pettyPrint(String xml, int blanks) throws Exception { + return doParse(xml, blanks, false, new NoopColor()); + } + + /** + * Pretty print the XML (does not use DOM or any kind of parser) + * + * @param xml the XML + * @param blanks number of blanks to use as indent + * @param declaration whether to include XML declaration + * @return the XML in pretty + */ + public static String pettyPrint(String xml, int blanks, boolean declaration) throws Exception { + return doParse(xml, blanks, declaration, new NoopColor()); + } + + private static class NoopColor implements ColorPrintElement { + + @Override + public String color(int type, String value) { + return value; + } + } + + private static String doParse(String xml, int blanks, boolean declaration, ColorPrintElement color) throws Exception { + SAXParser parser; + final SAXParserFactory factory = SAXParserFactory.newInstance(); + factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true); + factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); + factory.setFeature("http://xml.org/sax/features/namespaces", false); + factory.setFeature("http://xml.org/sax/features/validation", false); + factory.setFeature("http://apache.org/xml/features/nonvalidating/load-dtd-grammar", false); + factory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false); + factory.setFeature("http://xml.org/sax/features/external-parameter-entities", false); + factory.setFeature("http://xml.org/sax/features/external-general-entities", false); + parser = factory.newSAXParser(); + + final StringBuilder sb = new StringBuilder(); + final DefaultHandler handler = new DefaultHandler() { + int indent; + + @Override + public void declaration(String version, String encoding, String standalone) throws SAXException { + if (declaration) { + StringBuilder lb = new StringBuilder(); + lb.append("<?xml"); + if (version != null) { + lb.append(" version=\"").append(version).append("\""); + } + if (encoding != null) { + lb.append(" encoding=\"").append(encoding).append("\""); + } + if (standalone != null) { + lb.append(" standalone=\"").append(encoding).append("\""); + } + lb.append("?>"); + + String value = color.color(ColorPrintElement.DECLARATION, lb.toString()); + sb.append(value); + sb.append("\n"); + } + } + + @Override + public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { + sb.append(padString(indent, blanks)); + + StringBuilder lb = new StringBuilder(); + lb.append("<"); + lb.append(qName); + String value = color.color(ColorPrintElement.ELEMENT, lb.toString()); + sb.append(value); + + lb.setLength(0); + for (int i = 0; i < attributes.getLength(); i++) { + String k = color.color(ColorPrintElement.ATTRIBUTE_KEY, attributes.getQName(i)); + String v = color.color(ColorPrintElement.ATTRIBUTE_VALUE, attributes.getValue(i)); + String eq = color.color(ColorPrintElement.ATTRIBUTE_EQUAL, "="); + String quote = color.color(ColorPrintElement.ATTRIBUTE_QUOTE, "\""); + lb.append(" ").append(k).append(eq).append(quote).append(v).append(quote); + } + sb.append(lb); + + value = color.color(ColorPrintElement.ELEMENT, ">"); + sb.append(value); + sb.append("\n"); + + indent++; + } + + @Override + public void endElement(String uri, String localName, String qName) throws SAXException { + --indent; + + StringBuilder lb = new StringBuilder(); + lb.append("</"); + lb.append(qName); + lb.append(">"); + + sb.append(padString(indent, blanks)); + String value = color.color(ColorPrintElement.ELEMENT, lb.toString()); + sb.append(value); + if (indent > 0) { + sb.append("\n"); + } + } + + @Override + public void characters(char[] ch, int start, int length) throws SAXException { + char[] chars = new char[length]; + System.arraycopy(ch, start, chars, 0, length); + String value = color.color(ColorPrintElement.VALUE, new String(chars)); + + sb.append(padString(indent, blanks)); + sb.append(value); + sb.append("\n"); + } + }; + + parser.parse(new ByteArrayInputStream(xml.getBytes()), handler); + return sb.toString(); + } + +} diff --git a/core/camel-xml-jaxp/src/test/java/org/apache/camel/util/xml/XmlPrettyPrinterTest.java b/core/camel-xml-jaxp/src/test/java/org/apache/camel/util/xml/XmlPrettyPrinterTest.java new file mode 100644 index 00000000000..e84dd9589d2 --- /dev/null +++ b/core/camel-xml-jaxp/src/test/java/org/apache/camel/util/xml/XmlPrettyPrinterTest.java @@ -0,0 +1,53 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.camel.util.xml; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +public class XmlPrettyPrinterTest { + + @Test + public void testPrettyPrint() throws Exception { + String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?><root><tag><nested>hello</nested></tag></root>"; + String expected = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" + + "<root>\n" + + " <tag>\n" + + " <nested>\n" + + " hello\n" + + " </nested>\n" + + " </tag>\n" + + "</root>"; + String pretty = XmlPrettyPrinter.pettyPrint(xml, 2, true); + Assertions.assertEquals(expected, pretty); + } + + @Test + public void testPrettyPrintNoDecl() throws Exception { + String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?><root><tag><nested>hello</nested></tag></root>"; + String expected = "<root>\n" + + " <tag>\n" + + " <nested>\n" + + " hello\n" + + " </nested>\n" + + " </tag>\n" + + "</root>"; + String pretty = XmlPrettyPrinter.pettyPrint(xml, 2, false); + Assertions.assertEquals(expected, pretty); + } + +} diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/action/CamelTraceAction.java b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/action/CamelTraceAction.java index df35b0f42de..a178822f3ad 100644 --- a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/action/CamelTraceAction.java +++ b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/action/CamelTraceAction.java @@ -43,6 +43,7 @@ import org.apache.camel.catalog.impl.TimePatternConverter; import org.apache.camel.dsl.jbang.core.commands.CamelJBangMain; import org.apache.camel.dsl.jbang.core.common.JSonHelper; import org.apache.camel.dsl.jbang.core.common.ProcessHelper; +import org.apache.camel.dsl.jbang.core.common.XmlHelper; import org.apache.camel.util.StopWatch; import org.apache.camel.util.StringHelper; import org.apache.camel.util.TimeUtils; @@ -131,7 +132,7 @@ public class CamelTraceAction extends ActionBaseCommand { boolean latest; @CommandLine.Option(names = { "--pretty" }, - description = "Pretty print message body when using JSon format") + description = "Pretty print message body when using JSon or XML format") boolean pretty; String findAnsi; @@ -902,6 +903,7 @@ public class CamelTraceAction extends ActionBaseCommand { if (value == null) { return "null"; } + boolean json = false; String s = value.toString(); if (!s.isEmpty()) { try { @@ -911,12 +913,31 @@ public class CamelTraceAction extends ActionBaseCommand { } else { s = JSonHelper.prettyPrint(s, 2); } + if (s != null && !s.isEmpty()) { + json = true; + } } catch (Throwable e) { // ignore as not json } if (s == null || s.isEmpty()) { s = value.toString(); } + if (!json) { + // try with xml + try { + s = Jsoner.unescape(s); + if (loggingColor) { + s = XmlHelper.colorPrint(s, 2, true); + } else { + s = XmlHelper.prettyPrint(s, 2); + } + } catch (Throwable e) { + // ignore as not xml + } + } + if (s == null || s.isEmpty()) { + s = value.toString(); + } } if (s == null) { return "null"; diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/common/XmlHelper.java b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/common/XmlHelper.java index 452307af4cd..374ad0a3930 100644 --- a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/common/XmlHelper.java +++ b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/common/XmlHelper.java @@ -21,12 +21,46 @@ import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import org.apache.camel.util.ObjectHelper; +import org.apache.camel.util.xml.XmlPrettyPrinter; +import org.fusesource.jansi.Ansi; public final class XmlHelper { private XmlHelper() { } + /** + * Prints the XML in pretty mode (no color). + */ + public static String prettyPrint(String xml, int spaces) throws Exception { + return XmlPrettyPrinter.pettyPrint(xml, spaces); + } + + /** + * Prints the XML with ANSi color (similar to jq) + */ + public static String colorPrint(String xml, int spaces, boolean pretty) throws Exception { + return XmlPrettyPrinter.colorPrint(xml, spaces, pretty, new XmlPrettyPrinter.ColorPrintElement() { + @Override + public String color(int type, String value) { + String s = value != null ? value : "null"; + if (type == XmlPrettyPrinter.ColorPrintElement.DECLARATION) { + s = Ansi.ansi().fgBrightDefault().a(Ansi.Attribute.INTENSITY_FAINT).a(s).reset().toString(); + } else if (type == XmlPrettyPrinter.ColorPrintElement.ELEMENT) { + s = Ansi.ansi().fgBright(Ansi.Color.BLUE).a(s).reset().toString(); + } else if (type == XmlPrettyPrinter.ColorPrintElement.VALUE) { + s = Ansi.ansi().fgDefault().a(s).reset().toString(); + } else if (type == XmlPrettyPrinter.ColorPrintElement.ATTRIBUTE_KEY) { + s = Ansi.ansi().fg(Ansi.Color.MAGENTA).a(s).reset().toString(); + } else if (type == XmlPrettyPrinter.ColorPrintElement.ATTRIBUTE_VALUE + || type == XmlPrettyPrinter.ColorPrintElement.ATTRIBUTE_QUOTE) { + s = Ansi.ansi().fg(Ansi.Color.GREEN).a(s).reset().toString(); + } + return s; + } + }); + } + public static DocumentBuilderFactory createDocumentBuilderFactory() { DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); factory.setNamespaceAware(true);