OneAttributePerLineXmlProcessor.java

  1. /*
  2.  * #%L
  3.  * wcm.io
  4.  * %%
  5.  * Copyright (C) 2014 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 renders one attribute per line for easier diff-ing on content changes.
  38.  */
  39. class OneAttributePerLineXmlProcessor extends AbstractXMLOutputProcessor {

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

  42.   OneAttributePerLineXmlProcessor(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.    * <p>
  49.    * This method arranges for outputting the Element infrastructure including
  50.    * Namespace Declarations and Attributes.
  51.    * </p>
  52.    * @param out
  53.    *          <code>Writer</code> to use.
  54.    * @param fstack
  55.    *          the FormatStack
  56.    * @param nstack
  57.    *          the NamespaceStack
  58.    * @param element
  59.    *          <code>Element</code> to write.
  60.    * @throws IOException
  61.    *           if the destination Writer fails
  62.    */
  63.   @Override
  64.   protected void printElement(final Writer out, final FormatStack fstack,
  65.       final NamespaceStack nstack, final Element element) throws IOException {

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

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

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

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

  95.       // Print out attributes
  96.       if (element.hasAttributes()) {
  97.         boolean printMultiLine = element.getAttributes().size() > 1
  98.             || nstack.addedForward().iterator().hasNext();
  99.         for (final Attribute attribute : element.getAttributes()) {
  100.           printAttribute(out, fstack, attribute, printMultiLine);
  101.         }
  102.       }

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

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

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

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

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

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

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

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

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

  165.   }

  166.   private void printAttribute(Writer out, FormatStack fstack, Attribute attribute, boolean printMultiLine) throws IOException {
  167.     if (!attribute.isSpecified() && fstack.isSpecifiedAttributesOnly()) {
  168.       return;
  169.     }

  170.     if (printMultiLine) {
  171.       write(out, StringUtils.defaultString(fstack.getLineSeparator()));
  172.       write(out, StringUtils.defaultString(fstack.getLevelIndent()));
  173.       write(out, StringUtils.defaultString(fstack.getIndent()));
  174.     }
  175.     else {
  176.       write(out, " ");
  177.     }

  178.     write(out, attribute.getQualifiedName());
  179.     write(out, "=");

  180.     write(out, "\"");
  181.     attributeEscapedEntitiesFilter(out, fstack, attribute.getValue());
  182.     write(out, "\"");
  183.   }

  184. }