1 /*
2  * Copyright (C) 2011 The Android Open Source Project
3  *
4  * Licensed under the Eclipse Public License, Version 1.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.eclipse.org/org/documents/epl-v10.php
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.android.ide.eclipse.adt.internal.editors.formatting;
17 
18 import com.android.SdkConstants;
19 import com.android.annotations.NonNull;
20 import com.android.annotations.Nullable;
21 import com.android.ide.common.xml.XmlFormatPreferences;
22 import com.android.ide.common.xml.XmlFormatStyle;
23 import com.android.ide.common.xml.XmlPrettyPrinter;
24 import com.android.ide.eclipse.adt.internal.editors.layout.gle2.DomUtilities;
25 import com.android.resources.ResourceFolderType;
26 import com.android.resources.ResourceType;
27 import com.android.utils.SdkUtils;
28 import com.android.utils.XmlUtils;
29 
30 import org.eclipse.core.runtime.IPath;
31 import org.eclipse.jface.text.TextUtilities;
32 import org.eclipse.wst.xml.core.internal.provisional.document.IDOMElement;
33 import org.eclipse.wst.xml.core.internal.provisional.document.IDOMNode;
34 import org.w3c.dom.Document;
35 import org.w3c.dom.Element;
36 import org.w3c.dom.Node;
37 
38 /**
39  * Eclipse customization of the {@link EclipseXmlPrettyPrinter} which takes advantage of the
40  * Eclipse DOM Api to track additional information, such as whether an element with no children
41  * was of the open form ({@code <foo></foo>}) or the closed form ({@code <foo/>}), the ability to
42  * look up the original source (for proper entity handling), the ability to preserve attribute
43  * source order, etc.
44  */
45 @SuppressWarnings("restriction") // WST XML API
46 public class EclipseXmlPrettyPrinter extends XmlPrettyPrinter {
47 
48     /**
49      * Creates a new {@link com.android.ide.common.xml.XmlPrettyPrinter}
50      *
51      * @param prefs         the preferences to format with
52      * @param style         the style to format with
53      * @param lineSeparator the line separator to use, such as "\n" (can be null, in which case the
54      *                      system default is looked up via the line.separator property)
55      */
EclipseXmlPrettyPrinter( XmlFormatPreferences prefs, XmlFormatStyle style, String lineSeparator)56     public EclipseXmlPrettyPrinter(
57             XmlFormatPreferences prefs,
58             XmlFormatStyle style,
59             String lineSeparator) {
60         super(prefs, style, lineSeparator == null ? getDefaultLineSeparator() : lineSeparator);
61     }
62 
63     /**
64      * Pretty-prints the given XML document, which must be well-formed. If it is not,
65      * the original unformatted XML document is returned
66      *
67      * @param xml the XML content to format
68      * @param prefs the preferences to format with
69      * @param style the style to format with
70      * @param lineSeparator the line separator to use, such as "\n" (can be null, in which
71      *     case the system default is looked up via the line.separator property)
72      * @return the formatted document (or if a parsing error occurred, returns the
73      *     unformatted document)
74      */
75     @NonNull
prettyPrint( @onNull String xml, @NonNull XmlFormatPreferences prefs, @NonNull XmlFormatStyle style, @Nullable String lineSeparator)76     public static String prettyPrint(
77             @NonNull String xml,
78             @NonNull XmlFormatPreferences prefs,
79             @NonNull XmlFormatStyle style,
80             @Nullable String lineSeparator) {
81         Document document = DomUtilities.parseStructuredDocument(xml);
82         if (document != null) {
83             EclipseXmlPrettyPrinter printer = new EclipseXmlPrettyPrinter(prefs, style,
84                     lineSeparator);
85             if (xml.endsWith("\n")) { //$NON-NLS-1$
86                 printer.setEndWithNewline(true);
87             }
88 
89             StringBuilder sb = new StringBuilder(3 * xml.length() / 2);
90             printer.prettyPrint(-1, document, null, null, sb, false /*openTagOnly*/);
91             return sb.toString();
92         } else {
93             // Parser error: just return the unformatted content
94             return xml;
95         }
96     }
97 
98     @NonNull
prettyPrint(@onNull Node node, boolean endWithNewline)99     public static String prettyPrint(@NonNull Node node, boolean endWithNewline) {
100         return prettyPrint(node, EclipseXmlFormatPreferences.create(), XmlFormatStyle.get(node),
101                 null, endWithNewline);
102     }
103 
getDefaultLineSeparator()104     private static String getDefaultLineSeparator() {
105         org.eclipse.jface.text.Document blank = new org.eclipse.jface.text.Document();
106         String lineSeparator = TextUtilities.getDefaultLineDelimiter(blank);
107         if (lineSeparator == null) {
108             lineSeparator = SdkUtils.getLineSeparator();
109         }
110 
111         return lineSeparator;
112     }
113 
114     /**
115      * Pretty prints the given node
116      *
117      * @param node the node, usually a document, to be printed
118      * @param prefs the formatting preferences
119      * @param style the formatting style to use
120      * @param lineSeparator the line separator to use, or null to use the
121      *            default
122      * @return a formatted string
123      */
124     @NonNull
prettyPrint( @onNull Node node, @NonNull XmlFormatPreferences prefs, @NonNull XmlFormatStyle style, @Nullable String lineSeparator, boolean endWithNewline)125     public static String prettyPrint(
126             @NonNull Node node,
127             @NonNull XmlFormatPreferences prefs,
128             @NonNull XmlFormatStyle style,
129             @Nullable String lineSeparator,
130             boolean endWithNewline) {
131         XmlPrettyPrinter printer = new EclipseXmlPrettyPrinter(prefs, style, lineSeparator);
132         printer.setEndWithNewline(endWithNewline);
133         StringBuilder sb = new StringBuilder(1000);
134         printer.prettyPrint(-1, node, null, null, sb, false /*openTagOnly*/);
135         String xml = sb.toString();
136         if (node.getNodeType() == Node.DOCUMENT_NODE && !xml.startsWith("<?")) { //$NON-NLS-1$
137             xml = XmlUtils.XML_PROLOG + xml;
138         }
139         return xml;
140     }
141 
142     @Nullable
143     @Override
getSource(@onNull Node node)144     protected String getSource(@NonNull Node node) {
145         // In Eclipse, org.w3c.dom.DocumentType.getTextContent() returns null
146         if (node instanceof IDOMNode) {
147             // Get the original source string. This will contain the actual entities
148             // such as "&gt;" instead of ">" which it gets turned into for the DOM nodes.
149             // By operating on source we can preserve the user's entities rather than
150             // having &gt; for example always turned into >.
151             IDOMNode textImpl = (IDOMNode) node;
152             return textImpl.getSource();
153         }
154 
155         return super.getSource(node);
156     }
157 
158     @Override
isEmptyTag(Element element)159     protected boolean isEmptyTag(Element element) {
160         if (element instanceof IDOMElement) {
161             IDOMElement elementImpl = (IDOMElement) element;
162             if (elementImpl.isEmptyTag()) {
163                 return true;
164             }
165         }
166 
167         return false;
168     }
169 
170     /**
171      * Returns the {@link XmlFormatStyle} to use for a resource of the given type
172      *
173      * @param resourceType the type of resource to be formatted
174      * @return the suitable format style to use
175      */
get(ResourceType resourceType)176     public static XmlFormatStyle get(ResourceType resourceType) {
177         switch (resourceType) {
178             case ARRAY:
179             case ATTR:
180             case BOOL:
181             case DECLARE_STYLEABLE:
182             case DIMEN:
183             case FRACTION:
184             case ID:
185             case INTEGER:
186             case STRING:
187             case PLURALS:
188             case STYLE:
189             case STYLEABLE:
190             case COLOR:
191                 return XmlFormatStyle.RESOURCE;
192 
193             case LAYOUT:
194                 return XmlFormatStyle.LAYOUT;
195 
196             case DRAWABLE:
197             case MENU:
198             case ANIM:
199             case ANIMATOR:
200             case INTERPOLATOR:
201             default:
202                 return XmlFormatStyle.FILE;
203         }
204     }
205 
206     /**
207      * Returns the {@link XmlFormatStyle} to use for resource files in the given resource
208      * folder
209      *
210      * @param folderType the type of folder containing the resource file
211      * @return the suitable format style to use
212      */
getForFolderType(ResourceFolderType folderType)213     public static XmlFormatStyle getForFolderType(ResourceFolderType folderType) {
214         switch (folderType) {
215             case LAYOUT:
216                 return XmlFormatStyle.LAYOUT;
217             case COLOR:
218             case VALUES:
219                 return XmlFormatStyle.RESOURCE;
220             case ANIM:
221             case ANIMATOR:
222             case DRAWABLE:
223             case INTERPOLATOR:
224             case MENU:
225             default:
226                 return XmlFormatStyle.FILE;
227         }
228     }
229 
230     /**
231      * Returns the {@link XmlFormatStyle} to use for resource files of the given path.
232      *
233      * @param path the path to the resource file
234      * @return the suitable format style to use
235      */
getForFile(IPath path)236     public static XmlFormatStyle getForFile(IPath path) {
237         if (SdkConstants.FN_ANDROID_MANIFEST_XML.equals(path.lastSegment())) {
238             return XmlFormatStyle.MANIFEST;
239         }
240 
241         if (path.segmentCount() > 2) {
242             String parentName = path.segment(path.segmentCount() - 2);
243             ResourceFolderType folderType = ResourceFolderType.getFolderType(parentName);
244             return getForFolderType(folderType);
245         }
246 
247         return XmlFormatStyle.FILE;
248     }
249 }
250