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