View Javadoc
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  
22  import java.io.IOException;
23  import java.io.Writer;
24  import java.util.ArrayList;
25  import java.util.List;
26  import java.util.Set;
27  
28  import org.apache.commons.lang3.StringUtils;
29  import org.jdom2.Attribute;
30  import org.jdom2.Content;
31  import org.jdom2.Element;
32  import org.jdom2.Namespace;
33  import org.jdom2.output.Format.TextMode;
34  import org.jdom2.output.support.AbstractXMLOutputProcessor;
35  import org.jdom2.output.support.FormatStack;
36  import org.jdom2.output.support.Walker;
37  import org.jdom2.util.NamespaceStack;
38  
39  /**
40   * XML output processor that renders one attribute per line for easier diff-ing on content changes.
41   */
42  class OneAttributePerLineXmlProcessor extends AbstractXMLOutputProcessor {
43  
44    private final Set<String> namespacePrefixes;
45    private final Set<String> namespacePrefixesActuallyUsed;
46  
47    OneAttributePerLineXmlProcessor(Set<String> namespacePrefixes, Set<String> namespacePrefixesActuallyUsed) {
48      this.namespacePrefixes = namespacePrefixes;
49      this.namespacePrefixesActuallyUsed = namespacePrefixesActuallyUsed;
50    }
51  
52    /**
53     * This will handle printing of an {@link Element}.
54     * <p>
55     * This method arranges for outputting the Element infrastructure including
56     * Namespace Declarations and Attributes.
57     * </p>
58     * @param out
59     *          <code>Writer</code> to use.
60     * @param fstack
61     *          the FormatStack
62     * @param nstack
63     *          the NamespaceStack
64     * @param element
65     *          <code>Element</code> to write.
66     * @throws IOException
67     *           if the destination Writer fails
68     */
69    @Override
70    protected void printElement(final Writer out, final FormatStack fstack,
71        final NamespaceStack nstack, final Element element) throws IOException {
72  
73      nstack.push(element);
74      try {
75        final List<Content> content = element.getContent();
76  
77        // Print the beginning of the tag plus attributes and any
78        // necessary namespace declarations
79        write(out, "<");
80  
81        write(out, element.getQualifiedName());
82  
83        // Print the element's namespace, if appropriate - try to keep order from given namespacePrefixes set
84        List<Namespace> definedNamespaces = new ArrayList<>();
85        for (final Namespace ns : nstack.addedForward()) {
86          definedNamespaces.add(ns);
87        }
88        for (String prefix : namespacePrefixes) {
89          for (int i = 0; i < definedNamespaces.size(); i++) {
90            Namespace ns = definedNamespaces.get(i);
91            if (StringUtils.equals(prefix, ns.getPrefix())) {
92              if (namespacePrefixesActuallyUsed.contains(ns.getPrefix())) {
93                printNamespace(out, fstack, ns);
94              }
95              definedNamespaces.remove(i);
96              break;
97            }
98          }
99        }
100       for (Namespace ns : definedNamespaces) {
101         if (namespacePrefixesActuallyUsed.contains(ns.getPrefix())) {
102           printNamespace(out, fstack, ns);
103         }
104       }
105 
106       // Print out attributes
107       if (element.hasAttributes()) {
108         boolean printMultiLine = element.getAttributes().size() > 1
109             || nstack.addedForward().iterator().hasNext();
110         for (final Attribute attribute : element.getAttributes()) {
111           printAttribute(out, fstack, attribute, printMultiLine);
112         }
113       }
114 
115       if (content.isEmpty()) {
116         // Case content is empty
117         if (fstack.isExpandEmptyElements()) {
118           write(out, "></");
119           write(out, element.getQualifiedName());
120           write(out, ">");
121         }
122         else {
123           write(out, "/>");
124         }
125         // nothing more to do.
126         return;
127       }
128 
129       // OK, we have real content to push.
130       fstack.push();
131       try {
132 
133         // Check for xml:space and adjust format settings
134         final String space = element.getAttributeValue("space",
135             Namespace.XML_NAMESPACE);
136 
137         if ("default".equals(space)) {
138           fstack.setTextMode(fstack.getDefaultMode());
139         }
140         else if ("preserve".equals(space)) {
141           fstack.setTextMode(TextMode.PRESERVE);
142         }
143 
144         // note we ensure the FStack is right before creating the walker
145         Walker walker = buildWalker(fstack, content, true);
146 
147         if (!walker.hasNext()) {
148           // the walker has formatted out whatever content we had
149           if (fstack.isExpandEmptyElements()) {
150             write(out, "></");
151             write(out, element.getQualifiedName());
152             write(out, ">");
153           }
154           else {
155             write(out, "/>");
156           }
157           // nothing more to do.
158           return;
159         }
160         // we have some content.
161         write(out, ">");
162         if (!walker.isAllText()) {
163           // we need to newline/indent
164           textRaw(out, fstack.getPadBetween());
165         }
166 
167         printContent(out, fstack, nstack, walker);
168 
169         if (!walker.isAllText()) {
170           // we need to newline/indent
171           textRaw(out, fstack.getPadLast());
172         }
173         write(out, "</");
174         write(out, element.getQualifiedName());
175         write(out, ">");
176 
177       }
178       finally {
179         fstack.pop();
180       }
181     }
182     finally {
183       nstack.pop();
184     }
185 
186   }
187 
188   private void printAttribute(Writer out, FormatStack fstack, Attribute attribute, boolean printMultiLine) throws IOException {
189     if (!attribute.isSpecified() && fstack.isSpecifiedAttributesOnly()) {
190       return;
191     }
192 
193     if (printMultiLine) {
194       write(out, StringUtils.defaultString(fstack.getLineSeparator()));
195       write(out, StringUtils.defaultString(fstack.getLevelIndent()));
196       write(out, StringUtils.defaultString(fstack.getIndent()));
197     }
198     else {
199       write(out, " ");
200     }
201 
202     write(out, attribute.getQualifiedName());
203     write(out, "=");
204 
205     write(out, "\"");
206     attributeEscapedEntitiesFilter(out, fstack, attribute.getValue());
207     write(out, "\"");
208   }
209 
210 }