NamspaceOrderedXmlProcessor.java

  1. /*
  2.  * #%L
  3.  * wcm.io
  4.  * %%
  5.  * Copyright (C) 2025 wcm.io
  6.  * %%
  7.  * Licensed under the Apache License, Version 2.0 (the "License");
  8.  * you may not use this file except in compliance with the License.
  9.  * You may obtain a copy of the License at
  10.  *
  11.  *      http://www.apache.org/licenses/LICENSE-2.0
  12.  *
  13.  * Unless required by applicable law or agreed to in writing, software
  14.  * distributed under the License is distributed on an "AS IS" BASIS,
  15.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  16.  * See the License for the specific language governing permissions and
  17.  * limitations under the License.
  18.  * #L%
  19.  */
  20. package io.wcm.tooling.commons.packmgr.unpack;

  21. import java.io.IOException;
  22. import java.io.Writer;
  23. import java.util.ArrayList;
  24. import java.util.List;
  25. import java.util.Set;

  26. import org.apache.commons.lang3.StringUtils;
  27. import org.jdom2.Attribute;
  28. import org.jdom2.Content;
  29. import org.jdom2.Element;
  30. import org.jdom2.Namespace;
  31. import org.jdom2.output.Format.TextMode;
  32. import org.jdom2.output.support.AbstractXMLOutputProcessor;
  33. import org.jdom2.output.support.FormatStack;
  34. import org.jdom2.output.support.Walker;
  35. import org.jdom2.util.NamespaceStack;

  36. /**
  37.  * XML output processor that prints namespaces in the exact given order.
  38.  */
  39. class NamspaceOrderedXmlProcessor extends AbstractXMLOutputProcessor {

  40.   private final Set<String> namespacePrefixes;
  41.   private final Set<String> namespacePrefixesActuallyUsed;

  42.   NamspaceOrderedXmlProcessor(Set<String> namespacePrefixes, Set<String> namespacePrefixesActuallyUsed) {
  43.     this.namespacePrefixes = namespacePrefixes;
  44.     this.namespacePrefixesActuallyUsed = namespacePrefixesActuallyUsed;
  45.   }

  46.   /**
  47.    * This will handle printing of an {@link Element}.
  48.    *
  49.    * <p>
  50.    * This method arranges for outputting the Element infrastructure including
  51.    * Namespace Declarations and Attributes.
  52.    * </p>
  53.    * @param out
  54.    *          <code>Writer</code> to use.
  55.    * @param fstack
  56.    *          the FormatStack
  57.    * @param nstack
  58.    *          the NamespaceStack
  59.    * @param element
  60.    *          <code>Element</code> to write.
  61.    * @throws IOException
  62.    *           if the destination Writer fails
  63.    */
  64.   @Override
  65.   protected void printElement(final Writer out, final FormatStack fstack,
  66.       final NamespaceStack nstack, final Element element) throws IOException {

  67.     nstack.push(element);
  68.     try {
  69.       final List<Content> content = element.getContent();

  70.       // Print the beginning of the tag plus attributes and any
  71.       // necessary namespace declarations
  72.       write(out, "<");

  73.       write(out, element.getQualifiedName());

  74.       // Print the element's namespace, if appropriate - try to keep order from given namespacePrefixes set
  75.       List<Namespace> definedNamespaces = new ArrayList<>();
  76.       for (final Namespace ns : nstack.addedForward()) {
  77.         definedNamespaces.add(ns);
  78.       }
  79.       for (String prefix : namespacePrefixes) {
  80.         for (int i = 0; i < definedNamespaces.size(); i++) {
  81.           Namespace ns = definedNamespaces.get(i);
  82.           if (StringUtils.equals(prefix, ns.getPrefix())) {
  83.             if (namespacePrefixesActuallyUsed.contains(ns.getPrefix())) {
  84.               printNamespace(out, fstack, ns);
  85.             }
  86.             definedNamespaces.remove(i);
  87.             break;
  88.           }
  89.         }
  90.       }
  91.       for (Namespace ns : definedNamespaces) {
  92.         if (namespacePrefixesActuallyUsed.contains(ns.getPrefix())) {
  93.           printNamespace(out, fstack, ns);
  94.         }
  95.       }

  96.       // Print out attributes
  97.       if (element.hasAttributes()) {
  98.         for (final Attribute attribute : element.getAttributes()) {
  99.           printAttribute(out, fstack, attribute);
  100.         }
  101.       }

  102.       if (content.isEmpty()) {
  103.         // Case content is empty
  104.         if (fstack.isExpandEmptyElements()) {
  105.           write(out, "></");
  106.           write(out, element.getQualifiedName());
  107.           write(out, ">");
  108.         }
  109.         else {
  110.           write(out, " />");
  111.         }
  112.         // nothing more to do.
  113.         return;
  114.       }

  115.       // OK, we have real content to push.
  116.       fstack.push();
  117.       try {

  118.         // Check for xml:space and adjust format settings
  119.         final String space = element.getAttributeValue("space",
  120.             Namespace.XML_NAMESPACE);

  121.         if ("default".equals(space)) {
  122.           fstack.setTextMode(fstack.getDefaultMode());
  123.         }
  124.         else if ("preserve".equals(space)) {
  125.           fstack.setTextMode(TextMode.PRESERVE);
  126.         }

  127.         // note we ensure the FStack is right before creating the walker
  128.         Walker walker = buildWalker(fstack, content, true);

  129.         if (!walker.hasNext()) {
  130.           // the walker has formatted out whatever content we had
  131.           if (fstack.isExpandEmptyElements()) {
  132.             write(out, "></");
  133.             write(out, element.getQualifiedName());
  134.             write(out, ">");
  135.           }
  136.           else {
  137.             write(out, " />");
  138.           }
  139.           // nothing more to do.
  140.           return;
  141.         }
  142.         // we have some content.
  143.         write(out, ">");
  144.         if (!walker.isAllText()) {
  145.           // we need to newline/indent
  146.           textRaw(out, fstack.getPadBetween());
  147.         }

  148.         printContent(out, fstack, nstack, walker);

  149.         if (!walker.isAllText()) {
  150.           // we need to newline/indent
  151.           textRaw(out, fstack.getPadLast());
  152.         }
  153.         write(out, "</");
  154.         write(out, element.getQualifiedName());
  155.         write(out, ">");

  156.       }
  157.       finally {
  158.         fstack.pop();
  159.       }
  160.     }
  161.     finally {
  162.       nstack.pop();
  163.     }

  164.   }

  165. }