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 */ 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 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 99 public static String prettyPrint(@NonNull Node node, boolean endWithNewline) { 100 return prettyPrint(node, EclipseXmlFormatPreferences.create(), XmlFormatStyle.get(node), 101 null, endWithNewline); 102 } 103 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 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 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 ">" 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 > 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 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 */ 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 */ 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 */ 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