Home | History | Annotate | Download | only in formatting
      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 "&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
    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