mirror of
https://gitee.com/zhangsken/AndroidIDE.git
synced 2026-10-16 07:49:44 +08:00
fix(templates): use XmlBuilder from 'xml-dom' module for building XML files
This commit is contained in:
Vendored
+1
-1
@@ -78,7 +78,7 @@
|
||||
}
|
||||
|
||||
# Used in preferences
|
||||
-keep enum com.itsaky.androidide.lsp.xml.models.EmptyElements { *; }
|
||||
-keep enum org.eclipse.lemminx.dom.builder.EmptyElements { *; }
|
||||
-keep enum com.itsaky.androidide.xml.permissions.Permission { *; }
|
||||
|
||||
# Lots of native methods in tree-sitter
|
||||
|
||||
@@ -19,7 +19,6 @@ package com.itsaky.androidide.preferences
|
||||
|
||||
import android.content.Context
|
||||
import com.itsaky.androidide.resources.R.string
|
||||
import com.itsaky.androidide.lsp.xml.models.EmptyElements
|
||||
import com.itsaky.androidide.preferences.internal.CLOSING_BRACKET_NEW_LINE
|
||||
import com.itsaky.androidide.preferences.internal.EMPTY_ELEMENTS_BEHAVIOR
|
||||
import com.itsaky.androidide.preferences.internal.INSERT_FINAL_NEW_LINE
|
||||
@@ -50,6 +49,7 @@ import com.itsaky.androidide.preferences.internal.splitAttributes
|
||||
import com.itsaky.androidide.preferences.internal.splitAttributesIndentSize
|
||||
import com.itsaky.androidide.preferences.internal.trimFinalNewLine
|
||||
import com.itsaky.androidide.preferences.internal.trimTrailingWhitespace
|
||||
import org.eclipse.lemminx.dom.builder.EmptyElements
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
@Parcelize
|
||||
|
||||
+23
-34
@@ -19,6 +19,8 @@ package com.itsaky.androidide.lsp.xml.models
|
||||
|
||||
import com.itsaky.androidide.lsp.xml.providers.format.FormatElementCategory
|
||||
import com.itsaky.androidide.lsp.xml.providers.format.FormatElementCategory.PreserveSpace
|
||||
import org.eclipse.lemminx.dom.builder.EmptyElements
|
||||
import org.eclipse.lemminx.dom.builder.BaseXmlFormattingOptions
|
||||
import org.eclipse.lemminx.dom.DOMElement
|
||||
|
||||
/**
|
||||
@@ -26,60 +28,47 @@ import org.eclipse.lemminx.dom.DOMElement
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
open class XMLFormattingOptions {
|
||||
open val trimFinalNewLine: Boolean
|
||||
open class XMLFormattingOptions : BaseXmlFormattingOptions() {
|
||||
|
||||
override val isTrimFinalNewLine: Boolean
|
||||
get() = com.itsaky.androidide.preferences.internal.trimFinalNewLine
|
||||
open val insertFinalNewLine: Boolean
|
||||
override val isInsertFinalNewLine: Boolean
|
||||
get() = com.itsaky.androidide.preferences.internal.insertFinalNewLine
|
||||
open val splitAttributes: Boolean
|
||||
override val isSplitAttributes: Boolean
|
||||
get() = com.itsaky.androidide.preferences.internal.splitAttributes
|
||||
open val joinCDataLines: Boolean
|
||||
override val isJoinCDataLines: Boolean
|
||||
get() = com.itsaky.androidide.preferences.internal.joinCDataLines
|
||||
open val joinCommentLines: Boolean
|
||||
override val isJoinCommentLines: Boolean
|
||||
get() = com.itsaky.androidide.preferences.internal.joinCommentLines
|
||||
open val joinContentLines: Boolean
|
||||
override val isJoinContentLines: Boolean
|
||||
get() = com.itsaky.androidide.preferences.internal.joinContentLines
|
||||
open val spaceBeforeEmptyCloseTag: Boolean
|
||||
override val isSpaceBeforeEmptyCloseTag: Boolean
|
||||
get() = com.itsaky.androidide.preferences.internal.spaceBeforeEmptyCloseTag
|
||||
open val preserveEmptyContent: Boolean
|
||||
override val isPreserveEmptyContent: Boolean
|
||||
get() = com.itsaky.androidide.preferences.internal.preserveEmptyContent
|
||||
open val preserveAttributeLineBreaks: Boolean
|
||||
override val isPreserveAttributeLineBreaks: Boolean
|
||||
get() = com.itsaky.androidide.preferences.internal.preserveAttributeLineBreaks
|
||||
open val closingBracketNewLine: Boolean
|
||||
override val isClosingBracketNewLine: Boolean
|
||||
get() = com.itsaky.androidide.preferences.internal.closingBracketNewLine
|
||||
open val trimTrailingWhitespace: Boolean
|
||||
override val isTrimTrailingWhitespace: Boolean
|
||||
get() = com.itsaky.androidide.preferences.internal.trimTrailingWhitespace
|
||||
|
||||
open val maxLineWidth: Int
|
||||
override val maxLineWidth: Int
|
||||
get() = com.itsaky.androidide.preferences.internal.maxLineWidth
|
||||
open val preservedNewLines: Int
|
||||
override val preservedNewLines: Int
|
||||
get() = com.itsaky.androidide.preferences.internal.preservedNewLines
|
||||
open val splitAttributesIndentSize: Int
|
||||
override val splitAttributesIndentSize: Int
|
||||
get() = com.itsaky.androidide.preferences.internal.splitAttributesIndentSize
|
||||
|
||||
open val emptyElementsBehavior: EmptyElements
|
||||
get() = EmptyElements.valueOf(com.itsaky.androidide.preferences.internal.emptyElementsBehavior)
|
||||
override val emptyElementsBehavior: EmptyElements
|
||||
get() = EmptyElements.valueOf(
|
||||
com.itsaky.androidide.preferences.internal.emptyElementsBehavior)
|
||||
|
||||
private val preserveSpace =
|
||||
listOf(
|
||||
"xsl:text",
|
||||
"xsl:comment",
|
||||
"xsl:processing-instruction",
|
||||
"literallayout",
|
||||
"programlisting",
|
||||
"screen",
|
||||
"synopsis",
|
||||
"pre",
|
||||
"xd:pre"
|
||||
)
|
||||
listOf("xsl:text", "xsl:comment", "xsl:processing-instruction",
|
||||
"literallayout", "programlisting", "screen", "synopsis", "pre", "xd:pre")
|
||||
|
||||
fun getFormatElementCategory(element: DOMElement): FormatElementCategory? {
|
||||
return preserveSpace.find { it == element.tagName }?.let { PreserveSpace }
|
||||
}
|
||||
}
|
||||
|
||||
enum class EmptyElements {
|
||||
Expand,
|
||||
Collapse,
|
||||
Ignore
|
||||
}
|
||||
|
||||
+2
-2
@@ -144,7 +144,7 @@ public class DOMAttributeFormatter {
|
||||
}
|
||||
|
||||
private boolean isSplitAttributes() {
|
||||
return formatterDocument.getSharedSettings().getFormattingOptions().getSplitAttributes();
|
||||
return formatterDocument.getSharedSettings().getFormattingOptions().isSplitAttributes();
|
||||
}
|
||||
|
||||
private int getSplitAttributesIndentSize() {
|
||||
@@ -158,7 +158,7 @@ public class DOMAttributeFormatter {
|
||||
return formatterDocument
|
||||
.getSharedSettings()
|
||||
.getFormattingOptions()
|
||||
.getPreserveAttributeLineBreaks();
|
||||
.isPreserveAttributeLineBreaks();
|
||||
}
|
||||
|
||||
private boolean hasLineBreak(int prevOffset, int start) {
|
||||
|
||||
+1
-1
@@ -113,7 +113,7 @@ public class DOMCDATAFormatter {
|
||||
}
|
||||
|
||||
private boolean isJoinCDATALines() {
|
||||
return formatterDocument.getSharedSettings().getFormattingOptions().getJoinCDataLines();
|
||||
return formatterDocument.getSharedSettings().getFormattingOptions().isJoinCDataLines();
|
||||
}
|
||||
|
||||
private int getTabSize() {
|
||||
|
||||
+1
-1
@@ -109,7 +109,7 @@ public class DOMCommentFormatter {
|
||||
}
|
||||
|
||||
private boolean isJoinCommentLines() {
|
||||
return formatterDocument.getSharedSettings().getFormattingOptions().getJoinCommentLines();
|
||||
return formatterDocument.getSharedSettings().getFormattingOptions().isJoinCommentLines();
|
||||
}
|
||||
|
||||
private int getTabSize() {
|
||||
|
||||
+6
-6
@@ -29,8 +29,8 @@
|
||||
package com.itsaky.androidide.lsp.xml.providers.format;
|
||||
|
||||
import com.itsaky.androidide.lsp.models.TextEdit;
|
||||
import com.itsaky.androidide.lsp.xml.models.EmptyElements;
|
||||
|
||||
import org.eclipse.lemminx.dom.builder.EmptyElements;
|
||||
import org.eclipse.lemminx.commons.BadLocationException;
|
||||
import org.eclipse.lemminx.dom.DOMAttr;
|
||||
import org.eclipse.lemminx.dom.DOMElement;
|
||||
@@ -435,7 +435,7 @@ public class DOMElementFormatter {
|
||||
private boolean shouldFormatClosingBracketNewLine(DOMElement element) {
|
||||
boolean isSingleAttribute =
|
||||
element.getAttributeNodes() == null || element.getAttributeNodes().size() == 1;
|
||||
return (formatterDocument.getSharedSettings().getFormattingOptions().getClosingBracketNewLine()
|
||||
return (formatterDocument.getSharedSettings().getFormattingOptions().isClosingBracketNewLine()
|
||||
&& isSplitAttributes()
|
||||
&& !isSingleAttribute);
|
||||
}
|
||||
@@ -500,11 +500,11 @@ public class DOMElementFormatter {
|
||||
return formatterDocument
|
||||
.getSharedSettings()
|
||||
.getFormattingOptions()
|
||||
.getPreserveAttributeLineBreaks();
|
||||
.isPreserveAttributeLineBreaks();
|
||||
}
|
||||
|
||||
private boolean isSplitAttributes() {
|
||||
return formatterDocument.getSharedSettings().getFormattingOptions().getSplitAttributes();
|
||||
return formatterDocument.getSharedSettings().getFormattingOptions().isSplitAttributes();
|
||||
}
|
||||
|
||||
private int getSplitAttributesIndentSize() {
|
||||
@@ -518,7 +518,7 @@ public class DOMElementFormatter {
|
||||
return formatterDocument
|
||||
.getSharedSettings()
|
||||
.getFormattingOptions()
|
||||
.getSpaceBeforeEmptyCloseTag();
|
||||
.isSpaceBeforeEmptyCloseTag();
|
||||
}
|
||||
|
||||
private EmptyElements getEmptyElements() {
|
||||
@@ -526,7 +526,7 @@ public class DOMElementFormatter {
|
||||
}
|
||||
|
||||
private boolean isPreserveEmptyContent() {
|
||||
return formatterDocument.getSharedSettings().getFormattingOptions().getPreserveEmptyContent();
|
||||
return formatterDocument.getSharedSettings().getFormattingOptions().isPreserveEmptyContent();
|
||||
}
|
||||
|
||||
private void formatChildren(
|
||||
|
||||
+2
-2
@@ -123,7 +123,7 @@ public class DOMTextFormatter {
|
||||
}
|
||||
|
||||
private boolean isPreserveEmptyContent() {
|
||||
return formatterDocument.getSharedSettings().getFormattingOptions().getPreserveEmptyContent();
|
||||
return formatterDocument.getSharedSettings().getFormattingOptions().isPreserveEmptyContent();
|
||||
}
|
||||
|
||||
private void replaceSpacesWithOneSpace(int spaceStart, int spaceEnd, List<TextEdit> edits) {
|
||||
@@ -131,6 +131,6 @@ public class DOMTextFormatter {
|
||||
}
|
||||
|
||||
private boolean isJoinContentLines() {
|
||||
return formatterDocument.getSharedSettings().getFormattingOptions().getJoinContentLines();
|
||||
return formatterDocument.getSharedSettings().getFormattingOptions().isJoinContentLines();
|
||||
}
|
||||
}
|
||||
|
||||
+9
-9
@@ -28,13 +28,13 @@
|
||||
package com.itsaky.androidide.lsp.xml.providers.format;
|
||||
|
||||
import com.itsaky.androidide.lsp.models.TextEdit;
|
||||
import com.itsaky.androidide.lsp.xml.models.EmptyElements;
|
||||
import com.itsaky.androidide.lsp.xml.models.XMLServerSettings;
|
||||
import com.itsaky.androidide.lsp.xml.utils.XMLBuilder;
|
||||
import com.itsaky.androidide.models.Position;
|
||||
import com.itsaky.androidide.models.Range;
|
||||
import com.itsaky.androidide.preferences.internal.EditorPreferencesKt;
|
||||
|
||||
import org.eclipse.lemminx.dom.builder.EmptyElements;
|
||||
import org.eclipse.lemminx.commons.BadLocationException;
|
||||
import org.eclipse.lemminx.commons.TextDocument;
|
||||
import org.eclipse.lemminx.dom.DOMAttr;
|
||||
@@ -505,7 +505,7 @@ public class XMLFormatterDocument {
|
||||
* @throws BadLocationException
|
||||
*/
|
||||
private void formatElementStartTagCloseBracket(DOMElement element) throws BadLocationException {
|
||||
if (this.sharedSettings.getFormattingOptions().getPreserveAttributeLineBreaks()
|
||||
if (this.sharedSettings.getFormattingOptions().isPreserveAttributeLineBreaks()
|
||||
&& element.hasAttributes()
|
||||
&& !isSameLine(getLastAttribute(element).getEnd(), element.getStartTagCloseOffset())) {
|
||||
xmlBuilder.linefeed();
|
||||
@@ -527,7 +527,7 @@ public class XMLFormatterDocument {
|
||||
*/
|
||||
private void formatElementStartTagSelfCloseBracket(DOMElement element)
|
||||
throws BadLocationException {
|
||||
if (this.sharedSettings.getFormattingOptions().getPreserveAttributeLineBreaks()
|
||||
if (this.sharedSettings.getFormattingOptions().isPreserveAttributeLineBreaks()
|
||||
&& element.hasAttributes()) {
|
||||
int elementEndOffset = element.getEnd();
|
||||
if (element.isStartTagClosed()) {
|
||||
@@ -550,8 +550,8 @@ public class XMLFormatterDocument {
|
||||
formatAttribute(attr, isSingleAttribute, prevOffset);
|
||||
prevOffset = attr.getEnd();
|
||||
}
|
||||
if ((this.sharedSettings.getFormattingOptions().getClosingBracketNewLine()
|
||||
&& this.sharedSettings.getFormattingOptions().getSplitAttributes())
|
||||
if ((this.sharedSettings.getFormattingOptions().isClosingBracketNewLine()
|
||||
&& this.sharedSettings.getFormattingOptions().isSplitAttributes())
|
||||
&& !isSingleAttribute) {
|
||||
xmlBuilder.linefeed();
|
||||
// Indent by tag + splitAttributesIndentSize to match with attribute indent
|
||||
@@ -565,7 +565,7 @@ public class XMLFormatterDocument {
|
||||
|
||||
private void formatAttribute(DOMAttr attr, boolean isSingleAttribute, int prevOffset)
|
||||
throws BadLocationException {
|
||||
if (this.sharedSettings.getFormattingOptions().getPreserveAttributeLineBreaks()
|
||||
if (this.sharedSettings.getFormattingOptions().isPreserveAttributeLineBreaks()
|
||||
&& !isSameLine(prevOffset, attr.getStart())) {
|
||||
xmlBuilder.linefeed();
|
||||
xmlBuilder.indent(this.indentLevel + 1);
|
||||
@@ -636,7 +636,7 @@ public class XMLFormatterDocument {
|
||||
case Expand:
|
||||
case Collapse:
|
||||
{
|
||||
if (this.sharedSettings.getFormattingOptions().getPreserveEmptyContent()) {
|
||||
if (this.sharedSettings.getFormattingOptions().isPreserveEmptyContent()) {
|
||||
// preserve content
|
||||
if (element.hasChildNodes()) {
|
||||
// The element is empty and contains somes spaces which must be preserved
|
||||
@@ -752,11 +752,11 @@ public class XMLFormatterDocument {
|
||||
// check if format range reaches the end of the document
|
||||
if (this.endOffset == this.textDocument.getText().length()) {
|
||||
|
||||
if (this.sharedSettings.getFormattingOptions().getTrimFinalNewLine()) {
|
||||
if (this.sharedSettings.getFormattingOptions().isTrimFinalNewLine()) {
|
||||
this.xmlBuilder.trimFinalNewlines();
|
||||
}
|
||||
|
||||
if (this.sharedSettings.getFormattingOptions().getInsertFinalNewLine()
|
||||
if (this.sharedSettings.getFormattingOptions().isInsertFinalNewLine()
|
||||
&& !this.xmlBuilder.isLastLineEmptyOrWhitespace()) {
|
||||
this.xmlBuilder.linefeed();
|
||||
}
|
||||
|
||||
+2
-2
@@ -683,11 +683,11 @@ public class XMLFormatterDocumentNew {
|
||||
}
|
||||
|
||||
private boolean isTrimFinalNewlines() {
|
||||
return getFormattingSettings().getTrimFinalNewLine();
|
||||
return getFormattingSettings().isTrimFinalNewLine();
|
||||
}
|
||||
|
||||
private boolean isInsertFinalNewline() {
|
||||
return getFormattingSettings().getInsertFinalNewLine();
|
||||
return getFormattingSettings().isInsertFinalNewLine();
|
||||
}
|
||||
|
||||
private String getQuotationAsString() {
|
||||
|
||||
@@ -27,535 +27,21 @@
|
||||
|
||||
package com.itsaky.androidide.lsp.xml.utils;
|
||||
|
||||
import static org.eclipse.lemminx.utils.StringUtils.normalizeSpace;
|
||||
|
||||
import com.itsaky.androidide.lsp.xml.models.XMLServerSettings;
|
||||
import com.itsaky.androidide.preferences.internal.EditorPreferencesKt;
|
||||
import org.eclipse.lemminx.dom.DOMAttr;
|
||||
import org.eclipse.lemminx.dom.DOMComment;
|
||||
import org.eclipse.lemminx.dom.DTDDeclNode;
|
||||
import org.eclipse.lemminx.dom.builder.BaseXmlFormattingOptions;
|
||||
import org.eclipse.lemminx.dom.builder.XmlBuilder;
|
||||
|
||||
/**
|
||||
* XML content builder utilities.
|
||||
*/
|
||||
public class XMLBuilder {
|
||||
|
||||
private final XMLServerSettings sharedSettings;
|
||||
private final String lineDelimiter;
|
||||
private final StringBuilder xml;
|
||||
private final String whitespacesIndent;
|
||||
public class XMLBuilder extends XmlBuilder {
|
||||
|
||||
public XMLBuilder(String whitespacesIndent, String lineDelimiter) {
|
||||
this(whitespacesIndent, lineDelimiter, XMLServerSettings.INSTANCE);
|
||||
this(whitespacesIndent, lineDelimiter, XMLServerSettings.INSTANCE.getFormattingOptions());
|
||||
}
|
||||
|
||||
public XMLBuilder(String whitespacesIndent, String lineDelimiter, XMLServerSettings settings) {
|
||||
this.whitespacesIndent = whitespacesIndent;
|
||||
this.lineDelimiter = lineDelimiter;
|
||||
this.sharedSettings = settings;
|
||||
this.xml = new StringBuilder();
|
||||
}
|
||||
|
||||
public XMLBuilder appendSpace() {
|
||||
append(" ");
|
||||
return this;
|
||||
}
|
||||
|
||||
public XMLBuilder startElement(String prefix, String name, boolean close) {
|
||||
append("<");
|
||||
if (prefix != null && !prefix.isEmpty()) {
|
||||
append(prefix);
|
||||
append(":");
|
||||
}
|
||||
append(name);
|
||||
if (close) {
|
||||
closeStartElement();
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public XMLBuilder startElement(String name, boolean close) {
|
||||
return startElement(null, name, close);
|
||||
}
|
||||
|
||||
public XMLBuilder endElement(String name, boolean isEndTagClosed) {
|
||||
return endElement(null, name, isEndTagClosed);
|
||||
}
|
||||
|
||||
public XMLBuilder endElement(String name) {
|
||||
return endElement(null, name, true);
|
||||
}
|
||||
|
||||
public XMLBuilder endElement(String prefix, String name) {
|
||||
return endElement(prefix, name, true);
|
||||
}
|
||||
|
||||
public XMLBuilder endElement(String prefix, String name, boolean isEndTagClosed) {
|
||||
append("</");
|
||||
if (prefix != null && !prefix.isEmpty()) {
|
||||
append(prefix);
|
||||
append(":");
|
||||
}
|
||||
if (name != null) {
|
||||
append(name);
|
||||
}
|
||||
if (isEndTagClosed) {
|
||||
append(">");
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public XMLBuilder closeStartElement() {
|
||||
append(">");
|
||||
return this;
|
||||
}
|
||||
|
||||
public XMLBuilder selfCloseElement() {
|
||||
if (isSpaceBeforeEmptyCloseTag() && !isLastLineEmptyOrWhitespace()) {
|
||||
appendSpace();
|
||||
}
|
||||
append("/>");
|
||||
return this;
|
||||
}
|
||||
|
||||
public XMLBuilder addSingleAttribute(DOMAttr attr) {
|
||||
return addSingleAttribute(attr, false, true);
|
||||
}
|
||||
|
||||
public XMLBuilder addSingleAttribute(DOMAttr attr, boolean surroundWithQuotes,
|
||||
boolean prependSpace
|
||||
public XMLBuilder(String whitespacesIndent, String lineDelimiter, BaseXmlFormattingOptions settings
|
||||
) {
|
||||
return addSingleAttribute(attr.getName(), attr.getOriginalValue(), surroundWithQuotes,
|
||||
prependSpace);
|
||||
}
|
||||
|
||||
public XMLBuilder addSingleAttribute(String name, String value, boolean surroundWithQuotes) {
|
||||
return addSingleAttribute(name, value, surroundWithQuotes, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Used when only one attribute is being added to a node.
|
||||
*
|
||||
* <p>It will not perform any linefeeds and only basic indentation.
|
||||
*
|
||||
* @param name attribute name
|
||||
* @param value attribute value
|
||||
* @param surroundWithQuotes true if quotes should be added around originalValue
|
||||
* @return this XML Builder
|
||||
*/
|
||||
private XMLBuilder addSingleAttribute(String name, String value, boolean surroundWithQuotes,
|
||||
boolean prependSpace
|
||||
) {
|
||||
if (prependSpace) {
|
||||
appendSpace();
|
||||
}
|
||||
addAttributeContents(name, true, value, surroundWithQuotes);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add prolog attribute
|
||||
*
|
||||
* <p>It will not perform any linefeeds and only basic indentation.
|
||||
*
|
||||
* @param attr attribute
|
||||
* @return this XML Builder
|
||||
*/
|
||||
public XMLBuilder addPrologAttribute(DOMAttr attr) {
|
||||
appendSpace();
|
||||
addAttributeContents(attr.getName(), attr.hasDelimiter(), attr.getOriginalValue(), false);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Used when you are knowingly adding multiple attributes.
|
||||
*
|
||||
* <p>Does linefeeds and indentation.
|
||||
*
|
||||
* @param name
|
||||
* @param value
|
||||
* @param level
|
||||
* @return
|
||||
*/
|
||||
public XMLBuilder addAttribute(String name, String value, int level, boolean surroundWithQuotes) {
|
||||
if (isSplitAttributes()) {
|
||||
linefeed();
|
||||
indent(level + sharedSettings.getFormattingOptions().getSplitAttributesIndentSize());
|
||||
} else {
|
||||
appendSpace();
|
||||
}
|
||||
|
||||
addAttributeContents(name, true, value, surroundWithQuotes);
|
||||
return this;
|
||||
}
|
||||
|
||||
public XMLBuilder addAttribute(DOMAttr attr, int level) {
|
||||
return addAttribute(attr, level, false);
|
||||
}
|
||||
|
||||
private XMLBuilder addAttribute(DOMAttr attr, int level, boolean surroundWithQuotes) {
|
||||
if (isSplitAttributes()) {
|
||||
linefeed();
|
||||
indent(level + sharedSettings.getFormattingOptions().getSplitAttributesIndentSize());
|
||||
} else {
|
||||
appendSpace();
|
||||
}
|
||||
|
||||
addAttributeContents(attr.getName(), attr.hasDelimiter(), attr.getOriginalValue(),
|
||||
surroundWithQuotes);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the attribute {name, '=', and value}.
|
||||
*
|
||||
* <p>Never puts quotes around unquoted values unless indicated to by 'surroundWithQuotes'
|
||||
*
|
||||
* @param name name of the attribute
|
||||
* @param equalsSign true if equals sign exists, false otherwise
|
||||
* @param originalValue value of the attribute
|
||||
* @param surroundWithQuotes true if quotes should be added around originalValue, false otherwise
|
||||
*/
|
||||
private void addAttributeContents(String name, boolean equalsSign, String originalValue,
|
||||
boolean surroundWithQuotes
|
||||
) {
|
||||
if (name != null) {
|
||||
append(name);
|
||||
}
|
||||
if (equalsSign) {
|
||||
append("=");
|
||||
}
|
||||
if (originalValue != null) {
|
||||
char preferredQuote = getQuotationAsChar();
|
||||
Character quote = null;
|
||||
String valueWithoutQuote = originalValue;
|
||||
if (StringUtils.isQuoted(originalValue)) {
|
||||
if (originalValue.charAt(0) != preferredQuote) {
|
||||
quote = preferredQuote;
|
||||
} else {
|
||||
quote = originalValue.charAt(0);
|
||||
}
|
||||
valueWithoutQuote = StringUtils.convertToQuotelessValue(originalValue);
|
||||
} else if (surroundWithQuotes) {
|
||||
quote = preferredQuote;
|
||||
}
|
||||
formatAttributeValue(valueWithoutQuote, quote);
|
||||
}
|
||||
}
|
||||
|
||||
private void formatAttributeValue(String valueWithoutQuote, Character quote) {
|
||||
if (quote != null) {
|
||||
append(quote);
|
||||
}
|
||||
append(valueWithoutQuote);
|
||||
if (quote != null) {
|
||||
append(quote);
|
||||
}
|
||||
}
|
||||
|
||||
public void append(String str) {
|
||||
xml.append(str);
|
||||
}
|
||||
|
||||
public void append(char c) {
|
||||
xml.append(c);
|
||||
}
|
||||
|
||||
public XMLBuilder linefeed() {
|
||||
append(lineDelimiter);
|
||||
if (whitespacesIndent != null) {
|
||||
append(whitespacesIndent);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns this XMLBuilder with <code>text</code> added
|
||||
*
|
||||
* @param text the text to add
|
||||
* @return this XMLBuilder with <code>text</code> added
|
||||
*/
|
||||
public XMLBuilder addContent(String text) {
|
||||
return addContent(text, false, false, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns this XMLBuilder with <code>text</code> added depending on <code>isWhitespaceContent
|
||||
* </code>, <code>hasSiblings</code> and <code>delimiter</code>
|
||||
*
|
||||
* @param text the proposed text to add
|
||||
* @param isWhitespaceContent whether or not the text contains only whitespace content
|
||||
* @param hasSiblings whether or not the corresponding text node has siblings
|
||||
* @param delimiter line delimiter
|
||||
* @return this XMLBuilder with <code>text</code> added depending on <code>isWhitespaceContent
|
||||
* </code>, <code>hasSiblings</code> and <code>delimiter</code>
|
||||
*/
|
||||
public XMLBuilder addContent(String text, boolean isWhitespaceContent, boolean hasSiblings,
|
||||
String delimiter
|
||||
) {
|
||||
if (!isWhitespaceContent) {
|
||||
if (isJoinContentLines()) {
|
||||
text = StringUtils.normalizeSpace(text);
|
||||
} else if (hasSiblings) {
|
||||
text = text.trim();
|
||||
}
|
||||
if (isTrimTrailingWhitespace()) {
|
||||
text = trimTrailingSpacesEachLine(text);
|
||||
}
|
||||
append(text);
|
||||
} else if (!hasSiblings && isPreserveEmptyContent()) {
|
||||
append(text);
|
||||
} else if (hasSiblings) {
|
||||
int preservedNewLines = getPreservedNewlines();
|
||||
if (preservedNewLines > 0) {
|
||||
int newLineCount = StringUtils.getNumberOfNewLines(text, isWhitespaceContent, delimiter,
|
||||
preservedNewLines);
|
||||
for (int i = 0; i < newLineCount - 1;
|
||||
i++) { // - 1 because the node after will insert a delimiter
|
||||
append(delimiter);
|
||||
}
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public XMLBuilder indent(int level) {
|
||||
for (int i = 0; i < level; i++) {
|
||||
if (isInsertSpaces()) {
|
||||
for (int j = 0; j < getTabSize(); j++) {
|
||||
appendSpace();
|
||||
}
|
||||
} else {
|
||||
append("\t");
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public XMLBuilder startPrologOrPI(String tagName) {
|
||||
append("<?");
|
||||
append(tagName);
|
||||
return this;
|
||||
}
|
||||
|
||||
public XMLBuilder addContentPI(String content) {
|
||||
appendSpace();
|
||||
append(content);
|
||||
return this;
|
||||
}
|
||||
|
||||
public XMLBuilder endPrologOrPI() {
|
||||
append("?>");
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return xml.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Trims the trailing newlines for the current XML StringBuilder
|
||||
*/
|
||||
public void trimFinalNewlines() {
|
||||
int i = xml.length() - 1;
|
||||
while (i >= 0 && (xml.charAt(i) == '\r' || xml.charAt(i) == '\n')) {
|
||||
xml.deleteCharAt(i--);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns <code>str</code> with the trailing spaces from each line removed
|
||||
*
|
||||
* @param str the String
|
||||
* @return <code>str</code> with the trailing spaces from each line removed
|
||||
*/
|
||||
private static String trimTrailingSpacesEachLine(String str) {
|
||||
StringBuilder sb = new StringBuilder(str);
|
||||
int i = str.length() - 1;
|
||||
boolean removeSpaces = true;
|
||||
while (i >= 0) {
|
||||
char curr = sb.charAt(i);
|
||||
if (curr == '\n' || curr == '\r') {
|
||||
removeSpaces = true;
|
||||
} else if (removeSpaces && Character.isWhitespace(curr)) {
|
||||
sb.deleteCharAt(i);
|
||||
} else {
|
||||
removeSpaces = false;
|
||||
}
|
||||
i--;
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public XMLBuilder startCDATA() {
|
||||
append("<![CDATA[");
|
||||
return this;
|
||||
}
|
||||
|
||||
public XMLBuilder addContentCDATA(String content) {
|
||||
if (isJoinCDATALines()) {
|
||||
content = normalizeSpace(content);
|
||||
}
|
||||
append(content);
|
||||
return this;
|
||||
}
|
||||
|
||||
public XMLBuilder endCDATA() {
|
||||
append("]]>");
|
||||
return this;
|
||||
}
|
||||
|
||||
public XMLBuilder startComment(DOMComment comment) {
|
||||
if (comment.isCommentSameLineEndTag()) {
|
||||
appendSpace();
|
||||
}
|
||||
append("<!--");
|
||||
return this;
|
||||
}
|
||||
|
||||
public XMLBuilder addContentComment(String content) {
|
||||
if (isJoinCommentLines()) {
|
||||
appendSpace();
|
||||
append(normalizeSpace(content));
|
||||
appendSpace();
|
||||
} else {
|
||||
append(content);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public XMLBuilder addDeclTagStart(DTDDeclNode tag) {
|
||||
append("<!" + tag.getDeclType());
|
||||
return this;
|
||||
}
|
||||
|
||||
public XMLBuilder addDeclTagStart(String declTagName) {
|
||||
append("<!" + declTagName);
|
||||
return this;
|
||||
}
|
||||
|
||||
public XMLBuilder startDoctype() {
|
||||
append("<!DOCTYPE");
|
||||
return this;
|
||||
}
|
||||
|
||||
public XMLBuilder startDTDElementDecl() {
|
||||
append("<!ELEMENT");
|
||||
return this;
|
||||
}
|
||||
|
||||
public XMLBuilder startDTDAttlistDecl() {
|
||||
append("<!ATTLIST");
|
||||
return this;
|
||||
}
|
||||
|
||||
public XMLBuilder addParameter(String parameter) {
|
||||
return addUnindentedParameter(" " + replaceQuotesIfNeeded(parameter));
|
||||
}
|
||||
|
||||
public XMLBuilder addUnindentedParameter(String parameter) {
|
||||
append(replaceQuotesIfNeeded(parameter));
|
||||
return this;
|
||||
}
|
||||
|
||||
public XMLBuilder startDoctypeInternalSubset() {
|
||||
append(" [");
|
||||
return this;
|
||||
}
|
||||
|
||||
public XMLBuilder startUnindentedDoctypeInternalSubset() {
|
||||
append("[");
|
||||
return this;
|
||||
}
|
||||
|
||||
public XMLBuilder endDoctypeInternalSubset() {
|
||||
append("]");
|
||||
return this;
|
||||
}
|
||||
|
||||
public XMLBuilder endComment() {
|
||||
append("-->");
|
||||
return this;
|
||||
}
|
||||
|
||||
public XMLBuilder endDoctype() {
|
||||
append(">");
|
||||
return this;
|
||||
}
|
||||
|
||||
public boolean isLastLineEmptyOrWhitespace() {
|
||||
if (this.xml.length() == 0) {
|
||||
return true;
|
||||
}
|
||||
int i = this.xml.length() - 1;
|
||||
while (i > 0 && Character.isSpaceChar(this.xml.charAt(i))) {
|
||||
i--;
|
||||
}
|
||||
return i > 0 && (this.xml.charAt(i) == '\r' || this.xml.charAt(i) == '\n');
|
||||
}
|
||||
|
||||
private String replaceQuotesIfNeeded(String str) {
|
||||
if (StringUtils.isQuoted(str)) {
|
||||
String quote = getQuotationAsString();
|
||||
return quote + StringUtils.convertToQuotelessValue(str) + quote;
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
private String getQuotationAsString() {
|
||||
return "\"";
|
||||
}
|
||||
|
||||
private boolean isJoinCommentLines() {
|
||||
return sharedSettings.getFormattingOptions().getJoinCommentLines();
|
||||
}
|
||||
|
||||
private boolean isJoinCDATALines() {
|
||||
return sharedSettings.getFormattingOptions().getJoinCDataLines();
|
||||
}
|
||||
|
||||
private boolean isSplitAttributes() {
|
||||
return sharedSettings.getFormattingOptions().getSplitAttributes();
|
||||
}
|
||||
|
||||
private boolean isInsertSpaces() {
|
||||
return EditorPreferencesKt.getUseSoftTab();
|
||||
}
|
||||
|
||||
private int getTabSize() {
|
||||
return EditorPreferencesKt.getTabSize();
|
||||
}
|
||||
|
||||
private boolean isJoinContentLines() {
|
||||
return sharedSettings.getFormattingOptions().getJoinContentLines();
|
||||
}
|
||||
|
||||
private boolean isPreserveEmptyContent() {
|
||||
return sharedSettings.getFormattingOptions().getPreserveEmptyContent();
|
||||
}
|
||||
|
||||
private boolean isTrimTrailingWhitespace() {
|
||||
return sharedSettings.getFormattingOptions().getTrimTrailingWhitespace();
|
||||
}
|
||||
|
||||
private int getPreservedNewlines() {
|
||||
return sharedSettings.getFormattingOptions().getPreservedNewLines();
|
||||
}
|
||||
|
||||
private boolean isSpaceBeforeEmptyCloseTag() {
|
||||
return sharedSettings.getFormattingOptions().getSpaceBeforeEmptyCloseTag();
|
||||
}
|
||||
|
||||
private char getQuotationAsChar() {
|
||||
return '"';
|
||||
}
|
||||
|
||||
public int length() {
|
||||
return xml.length();
|
||||
}
|
||||
|
||||
public char charAt(int index) {
|
||||
return xml.charAt(index);
|
||||
super(whitespacesIndent, lineDelimiter, settings);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
@Suppress("JavaPluginLanguageLevel")
|
||||
plugins {
|
||||
id("java-library")
|
||||
id("org.jetbrains.kotlin.jvm")
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.eclipse.lemminx.dom.builder
|
||||
|
||||
import org.eclipse.lemminx.dom.builder.EmptyElements.Collapse
|
||||
|
||||
/**
|
||||
* Formatting options for the [XmlBuilder].
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
open class BaseXmlFormattingOptions {
|
||||
|
||||
open val isTrimFinalNewLine: Boolean = false
|
||||
open val isInsertFinalNewLine: Boolean = true
|
||||
open val isSplitAttributes: Boolean = true
|
||||
open val isJoinCDataLines: Boolean = false
|
||||
open val isJoinCommentLines: Boolean = true
|
||||
open val isJoinContentLines: Boolean = false
|
||||
open val isSpaceBeforeEmptyCloseTag: Boolean = true
|
||||
open val isPreserveEmptyContent: Boolean = true
|
||||
open val isPreserveAttributeLineBreaks: Boolean = true
|
||||
open val isClosingBracketNewLine: Boolean = false
|
||||
open val isTrimTrailingWhitespace: Boolean = true
|
||||
open val isUseSoftTab: Boolean = true
|
||||
|
||||
open val tabSize = 4
|
||||
open val maxLineWidth: Int = 80
|
||||
open val preservedNewLines: Int = 2
|
||||
|
||||
open val splitAttributesIndentSize: Int
|
||||
get() = tabSize / 2
|
||||
|
||||
open val emptyElementsBehavior: EmptyElements
|
||||
get() = Collapse
|
||||
}
|
||||
+7
-12
@@ -15,20 +15,15 @@
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.xml.utils
|
||||
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.robolectric.RobolectricTestRunner
|
||||
package org.eclipse.lemminx.dom.builder
|
||||
|
||||
/**
|
||||
* Behavior for empty elements.
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
@RunWith(RobolectricTestRunner::class)
|
||||
class XmlBuilderTest {
|
||||
|
||||
@Test
|
||||
fun `test values res build`() {
|
||||
|
||||
}
|
||||
enum class EmptyElements {
|
||||
Expand,
|
||||
Collapse,
|
||||
Ignore
|
||||
}
|
||||
+103
@@ -0,0 +1,103 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.eclipse.lemminx.dom.builder
|
||||
|
||||
/**
|
||||
* An implementation of [XmlBuilder] which can handle indentations while building the XML content.
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
class IndentedXmlBuilder(private val autoIndent: Boolean = false, whitespacesIndent: String?, lineDelimiter: String?,
|
||||
options: BaseXmlFormattingOptions?
|
||||
) : XmlBuilder(whitespacesIndent, lineDelimiter, options) {
|
||||
|
||||
constructor(autoIndent: Boolean) : this(autoIndent, "", System.lineSeparator(), BaseXmlFormattingOptions())
|
||||
|
||||
constructor() : this(false)
|
||||
|
||||
private var indentLevel = 0
|
||||
|
||||
override fun startElement(prefix: String?, name: String?, close: Boolean
|
||||
): XmlBuilder {
|
||||
if (autoIndent) {
|
||||
lfIndent()
|
||||
}
|
||||
return super.startElement(prefix, name, close).also {
|
||||
if (autoIndent) {
|
||||
incrIndent()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun addAttributeContents(name: String?, equalsSign: Boolean,
|
||||
originalValue: String?,
|
||||
surroundWithQuotes: Boolean
|
||||
) {
|
||||
if (autoIndent) {
|
||||
lfIndent()
|
||||
}
|
||||
super.addAttributeContents(name, equalsSign, originalValue,
|
||||
surroundWithQuotes)
|
||||
}
|
||||
|
||||
override fun selfCloseElement(): XmlBuilder {
|
||||
if (autoIndent) {
|
||||
dcrIndent()
|
||||
}
|
||||
return super.selfCloseElement()
|
||||
}
|
||||
|
||||
override fun endElement(prefix: String?, name: String?,
|
||||
isEndTagClosed: Boolean
|
||||
): XmlBuilder {
|
||||
if (autoIndent) {
|
||||
dcrIndent()
|
||||
lfIndent()
|
||||
}
|
||||
return super.endElement(prefix, name, isEndTagClosed)
|
||||
}
|
||||
|
||||
/**
|
||||
* Increment the indentation level.
|
||||
*/
|
||||
fun incrIndent() {
|
||||
++indentLevel
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrement the indentation level.
|
||||
*/
|
||||
fun dcrIndent() {
|
||||
--indentLevel
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends a line feed and indents the next line.
|
||||
*/
|
||||
fun lfIndent() {
|
||||
linefeed()
|
||||
indent()
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends indentation to the indent line.
|
||||
*/
|
||||
fun indent() {
|
||||
indent(indentLevel)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,559 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.eclipse.lemminx.dom.builder;
|
||||
|
||||
import static org.eclipse.lemminx.utils.StringUtils.normalizeSpace;
|
||||
|
||||
import org.eclipse.lemminx.dom.DOMAttr;
|
||||
import org.eclipse.lemminx.dom.DOMComment;
|
||||
import org.eclipse.lemminx.dom.DTDDeclNode;
|
||||
import org.eclipse.lemminx.utils.StringUtils;
|
||||
|
||||
/**
|
||||
* XML content builder utilities.
|
||||
*/
|
||||
public class XmlBuilder {
|
||||
|
||||
private final BaseXmlFormattingOptions formattingOptions;
|
||||
private final String lineDelimiter;
|
||||
private final StringBuilder xml;
|
||||
private final String whitespacesIndent;
|
||||
|
||||
public XmlBuilder() {
|
||||
this("", System.lineSeparator(), new BaseXmlFormattingOptions());
|
||||
}
|
||||
|
||||
public XmlBuilder(String whitespacesIndent, String lineDelimiter, BaseXmlFormattingOptions options
|
||||
) {
|
||||
this.whitespacesIndent = whitespacesIndent;
|
||||
this.lineDelimiter = lineDelimiter;
|
||||
this.formattingOptions = options;
|
||||
this.xml = new StringBuilder();
|
||||
}
|
||||
|
||||
public XmlBuilder appendSpace() {
|
||||
append(" ");
|
||||
return this;
|
||||
}
|
||||
|
||||
public XmlBuilder startElement(String prefix, String name, boolean close) {
|
||||
append("<");
|
||||
if (prefix != null && !prefix.isEmpty()) {
|
||||
append(prefix);
|
||||
append(":");
|
||||
}
|
||||
append(name);
|
||||
if (close) {
|
||||
closeStartElement();
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public XmlBuilder startElement(String name, boolean close) {
|
||||
return startElement(null, name, close);
|
||||
}
|
||||
|
||||
public XmlBuilder endElement(String name, boolean isEndTagClosed) {
|
||||
return endElement(null, name, isEndTagClosed);
|
||||
}
|
||||
|
||||
public XmlBuilder endElement(String name) {
|
||||
return endElement(null, name, true);
|
||||
}
|
||||
|
||||
public XmlBuilder endElement(String prefix, String name) {
|
||||
return endElement(prefix, name, true);
|
||||
}
|
||||
|
||||
public XmlBuilder endElement(String prefix, String name, boolean isEndTagClosed) {
|
||||
append("</");
|
||||
if (prefix != null && !prefix.isEmpty()) {
|
||||
append(prefix);
|
||||
append(":");
|
||||
}
|
||||
if (name != null) {
|
||||
append(name);
|
||||
}
|
||||
if (isEndTagClosed) {
|
||||
append(">");
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public XmlBuilder closeStartElement() {
|
||||
append(">");
|
||||
return this;
|
||||
}
|
||||
|
||||
public XmlBuilder selfCloseElement() {
|
||||
if (isSpaceBeforeEmptyCloseTag() && !isLastLineEmptyOrWhitespace()) {
|
||||
appendSpace();
|
||||
}
|
||||
append("/>");
|
||||
return this;
|
||||
}
|
||||
|
||||
public XmlBuilder androidAttribute(String name, String value) {
|
||||
return addSingleAttribute("android:" + name, value, true);
|
||||
}
|
||||
|
||||
public XmlBuilder addSingleAttribute(DOMAttr attr) {
|
||||
return addSingleAttribute(attr, false, true);
|
||||
}
|
||||
|
||||
public XmlBuilder addSingleAttribute(DOMAttr attr, boolean surroundWithQuotes,
|
||||
boolean prependSpace
|
||||
) {
|
||||
return addSingleAttribute(attr.getName(), attr.getOriginalValue(), surroundWithQuotes,
|
||||
prependSpace);
|
||||
}
|
||||
|
||||
public XmlBuilder addSingleAttribute(String name, String value) {
|
||||
return addSingleAttribute(name, value, true);
|
||||
}
|
||||
|
||||
public XmlBuilder addSingleAttribute(String name, String value, boolean surroundWithQuotes) {
|
||||
return addSingleAttribute(name, value, surroundWithQuotes, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Used when only one attribute is being added to a node.
|
||||
*
|
||||
* <p>It will not perform any linefeeds and only basic indentation.
|
||||
*
|
||||
* @param name attribute name
|
||||
* @param value attribute value
|
||||
* @param surroundWithQuotes true if quotes should be added around originalValue
|
||||
* @return this XML Builder
|
||||
*/
|
||||
private XmlBuilder addSingleAttribute(String name, String value, boolean surroundWithQuotes,
|
||||
boolean prependSpace
|
||||
) {
|
||||
if (prependSpace) {
|
||||
appendSpace();
|
||||
}
|
||||
addAttributeContents(name, true, value, surroundWithQuotes);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add prolog attribute
|
||||
*
|
||||
* <p>It will not perform any linefeeds and only basic indentation.
|
||||
*
|
||||
* @param attr attribute
|
||||
* @return this XML Builder
|
||||
*/
|
||||
public XmlBuilder addPrologAttribute(DOMAttr attr) {
|
||||
appendSpace();
|
||||
addAttributeContents(attr.getName(), attr.hasDelimiter(), attr.getOriginalValue(), false);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Used when you are knowingly adding multiple attributes.
|
||||
*
|
||||
* <p>Does linefeeds and indentation.
|
||||
*
|
||||
* @param name
|
||||
* @param value
|
||||
* @param level
|
||||
* @return
|
||||
*/
|
||||
public XmlBuilder addAttribute(String name, String value, int level, boolean surroundWithQuotes) {
|
||||
if (isSplitAttributes()) {
|
||||
linefeed();
|
||||
indent(level + formattingOptions.getSplitAttributesIndentSize());
|
||||
} else {
|
||||
appendSpace();
|
||||
}
|
||||
|
||||
addAttributeContents(name, true, value, surroundWithQuotes);
|
||||
return this;
|
||||
}
|
||||
|
||||
public XmlBuilder addAttribute(DOMAttr attr, int level) {
|
||||
return addAttribute(attr, level, false);
|
||||
}
|
||||
|
||||
private XmlBuilder addAttribute(DOMAttr attr, int level, boolean surroundWithQuotes) {
|
||||
if (isSplitAttributes()) {
|
||||
linefeed();
|
||||
indent(level + formattingOptions.getSplitAttributesIndentSize());
|
||||
} else {
|
||||
appendSpace();
|
||||
}
|
||||
|
||||
addAttributeContents(attr.getName(), attr.hasDelimiter(), attr.getOriginalValue(),
|
||||
surroundWithQuotes);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the attribute {name, '=', and value}.
|
||||
*
|
||||
* <p>Never puts quotes around unquoted values unless indicated to by 'surroundWithQuotes'
|
||||
*
|
||||
* @param name name of the attribute
|
||||
* @param equalsSign true if equals sign exists, false otherwise
|
||||
* @param originalValue value of the attribute
|
||||
* @param surroundWithQuotes true if quotes should be added around originalValue, false otherwise
|
||||
*/
|
||||
protected void addAttributeContents(String name, boolean equalsSign, String originalValue,
|
||||
boolean surroundWithQuotes
|
||||
) {
|
||||
if (name != null) {
|
||||
append(name);
|
||||
}
|
||||
if (equalsSign) {
|
||||
append("=");
|
||||
}
|
||||
if (originalValue != null) {
|
||||
char preferredQuote = getQuotationAsChar();
|
||||
Character quote = null;
|
||||
String valueWithoutQuote = originalValue;
|
||||
if (StringUtils.isQuoted(originalValue)) {
|
||||
if (originalValue.charAt(0) != preferredQuote) {
|
||||
quote = preferredQuote;
|
||||
} else {
|
||||
quote = originalValue.charAt(0);
|
||||
}
|
||||
valueWithoutQuote = StringUtils.convertToQuotelessValue(originalValue);
|
||||
} else if (surroundWithQuotes) {
|
||||
quote = preferredQuote;
|
||||
}
|
||||
formatAttributeValue(valueWithoutQuote, quote);
|
||||
}
|
||||
}
|
||||
|
||||
private void formatAttributeValue(String valueWithoutQuote, Character quote) {
|
||||
if (quote != null) {
|
||||
append(quote);
|
||||
}
|
||||
append(valueWithoutQuote);
|
||||
if (quote != null) {
|
||||
append(quote);
|
||||
}
|
||||
}
|
||||
|
||||
public void append(String str) {
|
||||
xml.append(str);
|
||||
}
|
||||
|
||||
public void append(char c) {
|
||||
xml.append(c);
|
||||
}
|
||||
|
||||
public XmlBuilder linefeed() {
|
||||
append(lineDelimiter);
|
||||
if (whitespacesIndent != null) {
|
||||
append(whitespacesIndent);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns this XmlBuilder with <code>text</code> added
|
||||
*
|
||||
* @param text the text to add
|
||||
* @return this XmlBuilder with <code>text</code> added
|
||||
*/
|
||||
public XmlBuilder addContent(String text) {
|
||||
return addContent(text, false, false, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns this XmlBuilder with <code>text</code> added depending on <code>isWhitespaceContent
|
||||
* </code>, <code>hasSiblings</code> and <code>delimiter</code>
|
||||
*
|
||||
* @param text the proposed text to add
|
||||
* @param isWhitespaceContent whether or not the text contains only whitespace content
|
||||
* @param hasSiblings whether or not the corresponding text node has siblings
|
||||
* @param delimiter line delimiter
|
||||
* @return this XmlBuilder with <code>text</code> added depending on <code>isWhitespaceContent
|
||||
* </code>, <code>hasSiblings</code> and <code>delimiter</code>
|
||||
*/
|
||||
public XmlBuilder addContent(String text, boolean isWhitespaceContent, boolean hasSiblings,
|
||||
String delimiter
|
||||
) {
|
||||
if (!isWhitespaceContent) {
|
||||
if (isJoinContentLines()) {
|
||||
text = StringUtils.normalizeSpace(text);
|
||||
} else if (hasSiblings) {
|
||||
text = text.trim();
|
||||
}
|
||||
if (isTrimTrailingWhitespace()) {
|
||||
text = trimTrailingSpacesEachLine(text);
|
||||
}
|
||||
append(text);
|
||||
} else if (!hasSiblings && isPreserveEmptyContent()) {
|
||||
append(text);
|
||||
} else if (hasSiblings) {
|
||||
int preservedNewLines = getPreservedNewlines();
|
||||
if (preservedNewLines > 0) {
|
||||
int newLineCount = StringUtils.getNumberOfNewLines(text, isWhitespaceContent, delimiter,
|
||||
preservedNewLines);
|
||||
for (int i = 0; i < newLineCount - 1;
|
||||
i++) { // - 1 because the node after will insert a delimiter
|
||||
append(delimiter);
|
||||
}
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public XmlBuilder indent(int level) {
|
||||
for (int i = 0; i < level; i++) {
|
||||
if (isInsertSpaces()) {
|
||||
for (int j = 0; j < getTabSize(); j++) {
|
||||
appendSpace();
|
||||
}
|
||||
} else {
|
||||
append("\t");
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public XmlBuilder startPrologOrPI(String tagName) {
|
||||
append("<?");
|
||||
append(tagName);
|
||||
return this;
|
||||
}
|
||||
|
||||
public XmlBuilder addContentPI(String content) {
|
||||
appendSpace();
|
||||
append(content);
|
||||
return this;
|
||||
}
|
||||
|
||||
public XmlBuilder endPrologOrPI() {
|
||||
append("?>");
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return xml.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Trims the trailing newlines for the current XML StringBuilder
|
||||
*/
|
||||
public void trimFinalNewlines() {
|
||||
int i = xml.length() - 1;
|
||||
while (i >= 0 && (xml.charAt(i) == '\r' || xml.charAt(i) == '\n')) {
|
||||
xml.deleteCharAt(i--);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns <code>str</code> with the trailing spaces from each line removed
|
||||
*
|
||||
* @param str the String
|
||||
* @return <code>str</code> with the trailing spaces from each line removed
|
||||
*/
|
||||
private static String trimTrailingSpacesEachLine(String str) {
|
||||
StringBuilder sb = new StringBuilder(str);
|
||||
int i = str.length() - 1;
|
||||
boolean removeSpaces = true;
|
||||
while (i >= 0) {
|
||||
char curr = sb.charAt(i);
|
||||
if (curr == '\n' || curr == '\r') {
|
||||
removeSpaces = true;
|
||||
} else if (removeSpaces && Character.isWhitespace(curr)) {
|
||||
sb.deleteCharAt(i);
|
||||
} else {
|
||||
removeSpaces = false;
|
||||
}
|
||||
i--;
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public XmlBuilder startCDATA() {
|
||||
append("<![CDATA[");
|
||||
return this;
|
||||
}
|
||||
|
||||
public XmlBuilder addContentCDATA(String content) {
|
||||
if (isJoinCDATALines()) {
|
||||
content = normalizeSpace(content);
|
||||
}
|
||||
append(content);
|
||||
return this;
|
||||
}
|
||||
|
||||
public XmlBuilder endCDATA() {
|
||||
append("]]>");
|
||||
return this;
|
||||
}
|
||||
|
||||
public XmlBuilder startComment(DOMComment comment) {
|
||||
if (comment.isCommentSameLineEndTag()) {
|
||||
appendSpace();
|
||||
}
|
||||
append("<!--");
|
||||
return this;
|
||||
}
|
||||
|
||||
public XmlBuilder addContentComment(String content) {
|
||||
if (isJoinCommentLines()) {
|
||||
appendSpace();
|
||||
append(normalizeSpace(content));
|
||||
appendSpace();
|
||||
} else {
|
||||
append(content);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public XmlBuilder addDeclTagStart(DTDDeclNode tag) {
|
||||
append("<!" + tag.getDeclType());
|
||||
return this;
|
||||
}
|
||||
|
||||
public XmlBuilder addDeclTagStart(String declTagName) {
|
||||
append("<!" + declTagName);
|
||||
return this;
|
||||
}
|
||||
|
||||
public XmlBuilder startDoctype() {
|
||||
append("<!DOCTYPE");
|
||||
return this;
|
||||
}
|
||||
|
||||
public XmlBuilder startDTDElementDecl() {
|
||||
append("<!ELEMENT");
|
||||
return this;
|
||||
}
|
||||
|
||||
public XmlBuilder startDTDAttlistDecl() {
|
||||
append("<!ATTLIST");
|
||||
return this;
|
||||
}
|
||||
|
||||
public XmlBuilder addParameter(String parameter) {
|
||||
return addUnindentedParameter(" " + replaceQuotesIfNeeded(parameter));
|
||||
}
|
||||
|
||||
public XmlBuilder addUnindentedParameter(String parameter) {
|
||||
append(replaceQuotesIfNeeded(parameter));
|
||||
return this;
|
||||
}
|
||||
|
||||
public XmlBuilder startDoctypeInternalSubset() {
|
||||
append(" [");
|
||||
return this;
|
||||
}
|
||||
|
||||
public XmlBuilder startUnindentedDoctypeInternalSubset() {
|
||||
append("[");
|
||||
return this;
|
||||
}
|
||||
|
||||
public XmlBuilder endDoctypeInternalSubset() {
|
||||
append("]");
|
||||
return this;
|
||||
}
|
||||
|
||||
public XmlBuilder endComment() {
|
||||
append("-->");
|
||||
return this;
|
||||
}
|
||||
|
||||
public XmlBuilder endDoctype() {
|
||||
append(">");
|
||||
return this;
|
||||
}
|
||||
|
||||
public boolean isLastLineEmptyOrWhitespace() {
|
||||
if (this.xml.length() == 0) {
|
||||
return true;
|
||||
}
|
||||
int i = this.xml.length() - 1;
|
||||
while (i > 0 && Character.isSpaceChar(this.xml.charAt(i))) {
|
||||
i--;
|
||||
}
|
||||
return i > 0 && (this.xml.charAt(i) == '\r' || this.xml.charAt(i) == '\n');
|
||||
}
|
||||
|
||||
private String replaceQuotesIfNeeded(String str) {
|
||||
if (StringUtils.isQuoted(str)) {
|
||||
String quote = getQuotationAsString();
|
||||
return quote + StringUtils.convertToQuotelessValue(str) + quote;
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
private String getQuotationAsString() {
|
||||
return "\"";
|
||||
}
|
||||
|
||||
private boolean isJoinCommentLines() {
|
||||
return formattingOptions.isJoinCommentLines();
|
||||
}
|
||||
|
||||
private boolean isJoinCDATALines() {
|
||||
return formattingOptions.isJoinCDataLines();
|
||||
}
|
||||
|
||||
private boolean isSplitAttributes() {
|
||||
return formattingOptions.isSplitAttributes();
|
||||
}
|
||||
|
||||
private boolean isInsertSpaces() {
|
||||
return formattingOptions.isUseSoftTab();
|
||||
}
|
||||
|
||||
private int getTabSize() {
|
||||
return formattingOptions.getTabSize();
|
||||
}
|
||||
|
||||
private boolean isJoinContentLines() {
|
||||
return formattingOptions.isJoinContentLines();
|
||||
}
|
||||
|
||||
private boolean isPreserveEmptyContent() {
|
||||
return formattingOptions.isPreserveEmptyContent();
|
||||
}
|
||||
|
||||
private boolean isTrimTrailingWhitespace() {
|
||||
return formattingOptions.isTrimTrailingWhitespace();
|
||||
}
|
||||
|
||||
private int getPreservedNewlines() {
|
||||
return formattingOptions.getPreservedNewLines();
|
||||
}
|
||||
|
||||
private boolean isSpaceBeforeEmptyCloseTag() {
|
||||
return formattingOptions.isSpaceBeforeEmptyCloseTag();
|
||||
}
|
||||
|
||||
private char getQuotationAsChar() {
|
||||
return '"';
|
||||
}
|
||||
|
||||
public int length() {
|
||||
return xml.length();
|
||||
}
|
||||
|
||||
public char charAt(int index) {
|
||||
return xml.charAt(index);
|
||||
}
|
||||
}
|
||||
@@ -1,211 +0,0 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright (C) 2016 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.xml.utils;
|
||||
|
||||
import static com.android.SdkConstants.ATTR_LAYOUT_HEIGHT;
|
||||
import static com.android.SdkConstants.ATTR_LAYOUT_WIDTH;
|
||||
import static com.android.SdkConstants.PreferenceTags.PREFERENCE_CATEGORY;
|
||||
import static com.android.SdkConstants.VALUE_MATCH_PARENT;
|
||||
import static com.android.SdkConstants.VALUE_WRAP_CONTENT;
|
||||
|
||||
import com.android.SdkConstants;
|
||||
import com.android.annotations.NonNull;
|
||||
|
||||
/**
|
||||
* Builds XML strings. Arguments are not validated or escaped. This class is designed to replace
|
||||
* hand writing XML snippets in string literals.
|
||||
*/
|
||||
public final class XmlBuilder {
|
||||
private enum Construct {
|
||||
NULL,
|
||||
START_TAG,
|
||||
ATTRIBUTE,
|
||||
CHARACTER_DATA,
|
||||
END_TAG
|
||||
}
|
||||
|
||||
private final StringBuilder stringBuilder = new StringBuilder();
|
||||
|
||||
private Construct lastAppendedConstruct = Construct.NULL;
|
||||
private int indentationLevel;
|
||||
|
||||
@NonNull
|
||||
public XmlBuilder startTag(@NonNull String name) {
|
||||
if (!lastAppendedConstruct.equals(Construct.END_TAG)) {
|
||||
int length = stringBuilder.length();
|
||||
if (length > 0) {
|
||||
stringBuilder.replace(length - 1, length, ">\n");
|
||||
}
|
||||
}
|
||||
|
||||
if (indentationLevel != 0) {
|
||||
stringBuilder.append('\n');
|
||||
}
|
||||
|
||||
indent();
|
||||
|
||||
stringBuilder.append('<').append(name).append('\n');
|
||||
|
||||
indentationLevel++;
|
||||
lastAppendedConstruct = Construct.START_TAG;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public XmlBuilder androidAttribute(@NonNull String name, boolean value) {
|
||||
return androidAttribute(name, Boolean.toString(value));
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public XmlBuilder androidAttribute(@NonNull String name, int value) {
|
||||
return androidAttribute(name, Integer.toString(value));
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public XmlBuilder androidAttribute(@NonNull String name, @NonNull String value) {
|
||||
return attribute(SdkConstants.ANDROID_NS_NAME, name, value);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public XmlBuilder attribute(@NonNull String name, @NonNull String value) {
|
||||
return attribute("", name, value);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public XmlBuilder attribute(
|
||||
@NonNull String namespacePrefix, @NonNull String name, @NonNull String value) {
|
||||
indent();
|
||||
|
||||
if (!namespacePrefix.isEmpty()) {
|
||||
stringBuilder.append(namespacePrefix).append(':');
|
||||
}
|
||||
|
||||
stringBuilder.append(name).append("=\"").append(value).append("\"\n");
|
||||
|
||||
lastAppendedConstruct = Construct.ATTRIBUTE;
|
||||
return this;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public XmlBuilder wrapContent() {
|
||||
return withSize(VALUE_WRAP_CONTENT, VALUE_WRAP_CONTENT);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public XmlBuilder matchParent() {
|
||||
return withSize(VALUE_MATCH_PARENT, VALUE_MATCH_PARENT);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public XmlBuilder withSize(@NonNull String width, @NonNull String height) {
|
||||
androidAttribute(ATTR_LAYOUT_WIDTH, width);
|
||||
androidAttribute(ATTR_LAYOUT_HEIGHT, height);
|
||||
return this;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public XmlBuilder characterData(@NonNull String data) {
|
||||
if (lastAppendedConstruct.equals(Construct.START_TAG)
|
||||
|| lastAppendedConstruct.equals(Construct.ATTRIBUTE)) {
|
||||
int length = stringBuilder.length();
|
||||
stringBuilder.replace(length - 1, length, ">\n");
|
||||
}
|
||||
|
||||
indent();
|
||||
|
||||
stringBuilder.append(data).append('\n');
|
||||
|
||||
lastAppendedConstruct = Construct.CHARACTER_DATA;
|
||||
return this;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public XmlBuilder appendDirectly(String content) {
|
||||
stringBuilder.append(content);
|
||||
return this;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public XmlBuilder endTag(@NonNull String name) {
|
||||
return endTagImpl(name, !name.endsWith("Layout") && !name.equals(PREFERENCE_CATEGORY));
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public XmlBuilder seperateEndTag(@NonNull String name) {
|
||||
return endTagImpl(name, false);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private XmlBuilder endTagImpl(@NonNull String name, boolean useEmptyElementTag) {
|
||||
if (lastAppendedConstruct.equals(Construct.START_TAG)
|
||||
|| lastAppendedConstruct.equals(Construct.ATTRIBUTE)) {
|
||||
int length = stringBuilder.length();
|
||||
|
||||
if (useEmptyElementTag) {
|
||||
stringBuilder.deleteCharAt(length - 1);
|
||||
} else {
|
||||
stringBuilder.replace(length - 1, length, ">\n\n");
|
||||
}
|
||||
}
|
||||
|
||||
indentationLevel--;
|
||||
|
||||
if ((lastAppendedConstruct.equals(Construct.START_TAG)
|
||||
|| lastAppendedConstruct.equals(Construct.ATTRIBUTE))
|
||||
&& useEmptyElementTag) {
|
||||
stringBuilder.append(" />\n");
|
||||
} else {
|
||||
indent();
|
||||
|
||||
stringBuilder.append("</").append(name).append(">\n");
|
||||
}
|
||||
|
||||
lastAppendedConstruct = Construct.END_TAG;
|
||||
return this;
|
||||
}
|
||||
|
||||
private void indent() {
|
||||
for (int i = 0; i < indentationLevel; i++) {
|
||||
stringBuilder.append(" ");
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public String toString() {
|
||||
return stringBuilder.toString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@ dependencies {
|
||||
api(projects.common)
|
||||
api(projects.logger)
|
||||
api(projects.resources)
|
||||
api(projects.subprojects.xmlDom)
|
||||
api(projects.subprojects.xmlUtils)
|
||||
|
||||
api(libs.aapt2.common)
|
||||
|
||||
+16
@@ -25,7 +25,9 @@ import com.itsaky.androidide.templates.base.modules.android.buildGradleSrc
|
||||
import com.itsaky.androidide.templates.base.modules.android.proguardRules
|
||||
import com.itsaky.androidide.templates.base.util.AndroidManifestBuilder
|
||||
import com.itsaky.androidide.templates.base.util.AndroidModuleResManager
|
||||
import com.itsaky.androidide.templates.base.util.stringRes
|
||||
import com.squareup.javapoet.TypeSpec
|
||||
import org.eclipse.lemminx.dom.builder.IndentedXmlBuilder
|
||||
import java.io.File
|
||||
|
||||
class AndroidModuleTemplateBuilder : ModuleTemplateBuilder() {
|
||||
@@ -113,6 +115,20 @@ class AndroidModuleTemplateBuilder : ModuleTemplateBuilder() {
|
||||
manifest.apply {
|
||||
generate(manifestFile())
|
||||
}
|
||||
|
||||
res {
|
||||
data.appName?.let { putStringRes(manifest.appLabelRes, it) }
|
||||
|
||||
if (strings.isNotEmpty()) {
|
||||
createValuesResource("strings") {
|
||||
linefeed()
|
||||
strings.forEach { (name, value) ->
|
||||
stringRes(name, value)
|
||||
}
|
||||
linefeed()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun RecipeExecutor.buildGradle() {
|
||||
|
||||
@@ -109,7 +109,7 @@ fun baseProject(block: ProjectTemplateConfigurator): ProjectTemplate {
|
||||
language = language.value!!, useKts = true)
|
||||
|
||||
setDefaultModuleData(
|
||||
ModuleTemplateData(":app", packageName.value!!, data.moduleNameToDir(":app"),
|
||||
ModuleTemplateData(":app", appName = data.name, packageName.value!!, data.moduleNameToDir(":app"),
|
||||
type = AndroidApp, language = language.value!!, minSdk = minSdk.value!!,
|
||||
useKts = data.useKts))
|
||||
}
|
||||
@@ -144,7 +144,7 @@ fun baseProject(block: ProjectTemplateConfigurator): ProjectTemplate {
|
||||
*
|
||||
* @param block The module configurator.
|
||||
*/
|
||||
fun baseAndroidModule(block: AndroidModuleTemplateConfigurator): ModuleTemplate {
|
||||
fun baseAndroidModule(isLibrary: Boolean = false, block: AndroidModuleTemplateConfigurator): ModuleTemplate {
|
||||
return AndroidModuleTemplateBuilder().apply {
|
||||
|
||||
val moduleName = stringParameter {
|
||||
@@ -153,6 +153,12 @@ fun baseAndroidModule(block: AndroidModuleTemplateConfigurator): ModuleTemplate
|
||||
constraints = listOf(NONEMPTY, MODULE_NAME)
|
||||
}
|
||||
|
||||
val appName = if (isLibrary) null else stringParameter {
|
||||
name = R.string.project_app_name
|
||||
default = "My Application"
|
||||
constraints = listOf(NONEMPTY)
|
||||
}
|
||||
|
||||
val packageName = stringParameter {
|
||||
name = R.string.package_name
|
||||
default = "com.example.myapplication"
|
||||
@@ -177,11 +183,17 @@ fun baseAndroidModule(block: AndroidModuleTemplateConfigurator): ModuleTemplate
|
||||
displayName = Language::lang
|
||||
}
|
||||
|
||||
widgets(TextFieldWidget(moduleName), TextFieldWidget(packageName), SpinnerWidget(minSdk),
|
||||
widgets(TextFieldWidget(moduleName))
|
||||
|
||||
appName?.let {
|
||||
widgets(TextFieldWidget(it))
|
||||
}
|
||||
|
||||
widgets(TextFieldWidget(packageName), SpinnerWidget(minSdk),
|
||||
SpinnerWidget(type), SpinnerWidget(language))
|
||||
|
||||
preRecipe = commonPreRecipe {
|
||||
ModuleTemplateData(name = moduleName.value!!, packageName = packageName.value!!,
|
||||
ModuleTemplateData(name = moduleName.value!!, appName = appName?.value, packageName = packageName.value!!,
|
||||
projectDir = requireProjectData().moduleNameToDir(moduleName.value!!), type = type.value!!,
|
||||
language = language.value!!, minSdk = minSdk.value!!)
|
||||
}
|
||||
|
||||
+32
-15
@@ -31,7 +31,7 @@ import com.itsaky.androidide.templates.RecipeExecutor
|
||||
import com.itsaky.androidide.templates.base.modules.android.ManifestActivity
|
||||
import com.itsaky.androidide.templates.base.modules.android.ManifestIcon
|
||||
import com.itsaky.androidide.xml.permissions.Permission
|
||||
import com.itsaky.androidide.xml.utils.XmlBuilder
|
||||
import org.eclipse.lemminx.dom.builder.IndentedXmlBuilder
|
||||
import java.io.File
|
||||
|
||||
/**
|
||||
@@ -102,29 +102,34 @@ class AndroidManifestBuilder {
|
||||
}
|
||||
|
||||
private fun manifestSrc(): String {
|
||||
return XmlBuilder().apply {
|
||||
return IndentedXmlBuilder(autoIndent = true).apply {
|
||||
buildManifest()
|
||||
}.withXmlDecl()
|
||||
}
|
||||
|
||||
private fun XmlBuilder.buildManifest() {
|
||||
private fun IndentedXmlBuilder.buildManifest() {
|
||||
createElement(TAG_MANIFEST) {
|
||||
attr(name = ANDROID_NS_NAME, value = ANDROID_URI, ns = XMLNS)
|
||||
closeStartElement()
|
||||
|
||||
permissions()
|
||||
application()
|
||||
}
|
||||
}
|
||||
|
||||
private fun XmlBuilder.permissions() {
|
||||
private fun IndentedXmlBuilder.permissions() {
|
||||
if (permissions.isEmpty()) {
|
||||
return
|
||||
}
|
||||
|
||||
for (permission in permissions) {
|
||||
createElement(TAG_USES_PERMISSION) {
|
||||
createElement(TAG_USES_PERMISSION, selfClose = true) {
|
||||
androidAttr("name", permission.constant)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun XmlBuilder.application() {
|
||||
private fun IndentedXmlBuilder.application() {
|
||||
if (isLibrary) {
|
||||
return
|
||||
}
|
||||
@@ -136,18 +141,25 @@ class AndroidManifestBuilder {
|
||||
androidAttr("label", appLabelRes)
|
||||
androidAttr("supportsRtl", rtl.toString())
|
||||
androidAttr("theme", "@style/${themeRes}")
|
||||
closeStartElement()
|
||||
|
||||
activities()
|
||||
}
|
||||
}
|
||||
|
||||
private fun XmlBuilder.activities() {
|
||||
private fun IndentedXmlBuilder.activities() {
|
||||
if (activities.isEmpty()) {
|
||||
return
|
||||
}
|
||||
|
||||
for (activity in activities) {
|
||||
createElement(TAG_ACTIVITY) {
|
||||
androidAttr("name", activity.name(packageName))
|
||||
if (activity.isLauncher || activity.isExported) {
|
||||
androidAttr("exported", "true")
|
||||
}
|
||||
|
||||
closeStartElement()
|
||||
if (activity.isLauncher) {
|
||||
intentFilter()
|
||||
}
|
||||
@@ -155,26 +167,31 @@ class AndroidManifestBuilder {
|
||||
}
|
||||
}
|
||||
|
||||
private fun XmlBuilder.intentFilter() {
|
||||
createElement(TAG_INTENT_FILTER) {
|
||||
private fun IndentedXmlBuilder.intentFilter() {
|
||||
createElement(TAG_INTENT_FILTER, closeStartTag = true) {
|
||||
// action
|
||||
createElement(TAG_ACTION) {
|
||||
createElement(TAG_ACTION, selfClose = true) {
|
||||
androidAttr("name", "android.intent.action.MAIN")
|
||||
}
|
||||
|
||||
// category
|
||||
createElement(TAG_CATEGORY) {
|
||||
createElement(TAG_CATEGORY, selfClose = true) {
|
||||
androidAttr("name", "android.intent.category.LAUNCHER")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun XmlBuilder.androidAttr(name: String, value: String) {
|
||||
private fun IndentedXmlBuilder.androidAttr(name: String, value: String) {
|
||||
androidAttribute(name, value)
|
||||
}
|
||||
|
||||
private fun XmlBuilder.attr(name: String, value: String, ns: String = "") {
|
||||
attribute(ns, name, value)
|
||||
private fun IndentedXmlBuilder.attr(name: String, value: String,
|
||||
ns: String = ""
|
||||
) {
|
||||
if (ns.isNotEmpty()) {
|
||||
addSingleAttribute("${ns}:${name}", value)
|
||||
} else {
|
||||
addSingleAttribute(name, value)
|
||||
}
|
||||
}
|
||||
|
||||
private fun ManifestActivity.name(packageName: String?): String {
|
||||
|
||||
+20
-4
@@ -18,10 +18,12 @@
|
||||
package com.itsaky.androidide.templates.base.util
|
||||
|
||||
import com.android.aaptcompiler.ConfigDescription
|
||||
import com.itsaky.androidide.xml.utils.XmlBuilder
|
||||
import com.itsaky.androidide.templates.SrcSet
|
||||
import com.itsaky.androidide.templates.SrcSet.Main
|
||||
import com.itsaky.androidide.templates.base.AndroidModuleTemplateBuilder
|
||||
import com.itsaky.androidide.templates.base.util.AndroidModuleResManager.ResourceType.VALUES
|
||||
import org.eclipse.lemminx.dom.builder.IndentedXmlBuilder
|
||||
import org.eclipse.lemminx.dom.builder.XmlBuilder
|
||||
import java.io.File
|
||||
|
||||
/**
|
||||
@@ -43,6 +45,18 @@ class AndroidModuleResManager {
|
||||
VALUES("values"),
|
||||
}
|
||||
|
||||
internal val strings = mutableMapOf<String, String>()
|
||||
|
||||
/**
|
||||
* Adds a string resource entry in the `strings.xml` file.
|
||||
*
|
||||
* @param name The name of the string.
|
||||
* @param value The value of the string.
|
||||
*/
|
||||
fun putStringRes(name: String, value: String) {
|
||||
strings[name] = value
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the resource directory the given [resource type][ResourceType] and the [configuration][ConfigDescription].
|
||||
*
|
||||
@@ -73,13 +87,15 @@ class AndroidModuleResManager {
|
||||
* @param config The configuration for the resource type.
|
||||
*/
|
||||
fun AndroidModuleTemplateBuilder.createValuesResource(name: String,
|
||||
type: ResourceType,
|
||||
type: ResourceType = VALUES,
|
||||
srcSet: SrcSet = Main,
|
||||
config: ConfigDescription = ConfigDescription(),
|
||||
selfClose: Boolean = false,
|
||||
configure: XmlBuilder.() -> Unit
|
||||
) {
|
||||
return createXmlResource(name, type, srcSet, config) {
|
||||
createElement("resources", configure)
|
||||
createElement(name = "resources", closeStartTag = true,
|
||||
selfClose = selfClose, configure = configure)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -98,7 +114,7 @@ class AndroidModuleResManager {
|
||||
configure: XmlBuilder.() -> Unit = {}
|
||||
) {
|
||||
val file = File(resDir(type, srcSet, config), "${name}.xml")
|
||||
val builder = XmlBuilder().apply(configure)
|
||||
val builder = IndentedXmlBuilder().apply(configure)
|
||||
executor.save(builder.withXmlDecl(), file)
|
||||
}
|
||||
|
||||
|
||||
+16
-8
@@ -17,17 +17,22 @@
|
||||
|
||||
package com.itsaky.androidide.templates.base.util
|
||||
|
||||
import com.itsaky.androidide.xml.utils.XmlBuilder
|
||||
import org.eclipse.lemminx.dom.builder.IndentedXmlBuilder
|
||||
import org.eclipse.lemminx.dom.builder.XmlBuilder
|
||||
|
||||
/**
|
||||
* Creates a new element in the [XmlBuilder] with the given [name].
|
||||
* The [configure] function is used to configure everything inside the element and then
|
||||
* the element is automatically closed.
|
||||
*/
|
||||
fun XmlBuilder.createElement(name: String, configure: XmlBuilder.() -> Unit) {
|
||||
startTag(name)
|
||||
apply(configure)
|
||||
endTag(name)
|
||||
fun XmlBuilder.createElement(name: String, closeStartTag: Boolean = false, selfClose: Boolean = false, configure: XmlBuilder.() -> Unit) {
|
||||
startElement(name, closeStartTag)
|
||||
configure()
|
||||
if (selfClose) {
|
||||
selfCloseElement()
|
||||
} else {
|
||||
endElement(name)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -36,11 +41,14 @@ fun XmlBuilder.createElement(name: String, configure: XmlBuilder.() -> Unit) {
|
||||
* @param name The name of the string resource.
|
||||
* @param value The value of the XML resource.
|
||||
*/
|
||||
fun XmlBuilder.addStringRes(name: String, value: String) {
|
||||
fun XmlBuilder.stringRes(name: String, value: String, indent: Boolean = true) {
|
||||
indent(1)
|
||||
createElement("string") {
|
||||
attribute("name", name)
|
||||
text(value)
|
||||
addSingleAttribute("name", name)
|
||||
closeStartElement()
|
||||
append(value)
|
||||
}
|
||||
linefeed()
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -148,7 +148,7 @@ class ProjectTemplateData(name: String, projectDir: File, val version: ProjectVe
|
||||
* @property type The type of module.
|
||||
* @property versions Version information for the module.
|
||||
*/
|
||||
class ModuleTemplateData(name: String, val packageName: String, projectDir: File,
|
||||
class ModuleTemplateData(name: String, val appName: String?, val packageName: String, projectDir: File,
|
||||
val type: ModuleType, language: Language, useKts: Boolean = true,
|
||||
minSdk: Sdk, val versions: ModuleVersionData = ModuleVersionData(minSdk)
|
||||
) : BaseTemplateData(name, projectDir, language, useKts) {
|
||||
|
||||
@@ -22,7 +22,7 @@ internal fun simpleMaterial3Theme(themeName: String) : String {
|
||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||
<!-- Base application theme. -->
|
||||
<style name="Base.${themeName}" parent="Theme.Material3.DayNight.NoActionBar">
|
||||
<!-- Customize your light theme here. -->
|
||||
<!-- Customize your theme here. -->
|
||||
<!-- <item name="colorPrimary">@color/my_light_primary</item> -->
|
||||
</style>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user