Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 44 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,9 +81,11 @@ val poet = ResourcesPoet.create()
.addIntegerArray("numbers", numbers)
.addPlurals("songs", plurals)
.addString("app_name", "Test")
.addStringArray("stuff", strings)
.addStringArray("stuff", strings, translatable = false)
.addStyle("AppTheme.Dark", "Base.AppTheme.Dark")
.addTypedArray("some_typed_array", typedArray)
.addTypedArray("some_typed_array", typedArray, translatable = false)
.addFraction("width", "50%p")
.addReference("my_ref", Reference("drawable", "logo"))
```

We do not allow configuration of more complicated resources like `drawable` and `anim` in the creation sense.
Expand Down Expand Up @@ -124,6 +126,46 @@ val poet = ResourcesPoet.create()

Top-level and per-element ignores can be combined — the top-level applies to all children, and per-element ignores override or add to it.

## Additional Features

### Translatable Attribute on Arrays

Mark string-arrays and typed-arrays as non-translatable:

```kotlin
val poet = ResourcesPoet.create()
.addStringArray("countries", listOf("US", "UK"), translatable = false)
.addTypedArray("items", listOf("@string/a", "@string/b"), translatable = false)
```

### Style Format Attribute

Add a `format` attribute to styles:

```kotlin
val poet = ResourcesPoet.create()
.addStyle("MyStyle", parentRef = "Base.Style",
styleItems = listOf(StyleItem("android:background", "@color/white")),
format = "string|reference")
```

### Build to ByteArray

Get the XML as a `ByteArray` for in-memory processing:

```kotlin
val bytes: ByteArray = poet.buildBytes()
```

### Load Font-Family Files

Use `create(file, indent, elementType)` to load existing font-family XML files:

```kotlin
val file = File("res/font/my_fonts.xml")
val poet = ResourcesPoet.create(file, indent = true, elementType = ResourcesPoet.ELEMENT.FONT_FAMILIES)
```

License
--------

Expand Down
17 changes: 17 additions & 0 deletions src/main/kotlin/com/commit451/resourcespoet/Reference.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.commit451.resourcespoet

/**
* Represents an Android resource reference, e.g. `@drawable/logo`
* @param type the reference type (e.g. "drawable", "color")
* @param name the resource name
*/
data class Reference(
val type: String,
val name: String
) {
/**
* Format the reference as an Android resource reference string
* e.g. "@drawable/logo" or "@color/primary" or "@string/app_name"
*/
fun toValue(): String = "@$type/$name"
}
100 changes: 85 additions & 15 deletions src/main/kotlin/com/commit451/resourcespoet/ResourcesPoet.kt
Original file line number Diff line number Diff line change
Expand Up @@ -64,11 +64,13 @@ class ResourcesPoet private constructor(
* Creates a builder on top of the current resources XML file
*
* @param file the resources file you want to add to
* @param indent whether to use indentation
* @param elementType the type of resource element
* @return poet
*/
fun create(file: File, indent: Boolean = INDENT_DEFAULT): ResourcesPoet {
fun create(file: File, indent: Boolean = INDENT_DEFAULT, elementType: ELEMENT = ELEMENT.RESOURCES): ResourcesPoet {
try {
return create(FileInputStream(file), indent)
return create(FileInputStream(file), indent, elementType)
} catch (e: FileNotFoundException) {
throw IllegalStateException(
"Unable to parse the resource file you passed. Make sure it is properly formatted",
Expand Down Expand Up @@ -158,13 +160,7 @@ class ResourcesPoet private constructor(
val element = document.createElement(Type.ATTR.toString())
element.setAttribute("name", attr.name)
if (!attr.formats.isEmpty()) {
var formatString = ""
for (format in attr.formats) {
formatString = formatString + format.toString() + "|"
}
//remove last |
formatString = formatString.substring(0, formatString.length - 1)
element.setAttribute("format", formatString)
element.setAttribute("format", attr.formats.joinToString("|") { it.toString() })
}
setToolsIgnore(element, toolsIgnore)
resourceElement.appendChild(element)
Expand Down Expand Up @@ -352,7 +348,6 @@ class ResourcesPoet private constructor(
element.setAttribute("name", name)
setToolsIgnore(element, toolsIgnore)
for (value in values) {
//Does this mess up the ordering?
val valueElement = document.createElement("item")
valueElement.appendChild(document.createTextNode(value))
element.appendChild(valueElement)
Expand All @@ -379,7 +374,6 @@ class ResourcesPoet private constructor(
element.setAttribute("name", name)
setToolsIgnore(element, toolsIgnore)
for (plural in plurals) {
//Does this mess up the ordering?
val valueElement = document.createElement("item")
valueElement.setAttribute("quantity", plural.quantity.toString())
valueElement.appendChild(document.createTextNode(plural.value))
Expand Down Expand Up @@ -417,19 +411,22 @@ class ResourcesPoet private constructor(
*
* @param name the name
* @param values the value
* @param translatable whether this array should be translatable
* @param toolsIgnore lint rule names to suppress (e.g., "UnusedResource")
* @return poet
*/
fun addStringArray(name: String, values: List<String>, toolsIgnore: String? = null): ResourcesPoet {
fun addStringArray(name: String, values: List<String>, translatable: Boolean = true, toolsIgnore: String? = null): ResourcesPoet {
//<string-array name="country_names">
// <item>Country</item>
// <item>United States</item>
// </string-array>
val element = document.createElement(Type.STRING_ARRAY.toString())
element.setAttribute("name", name)
if (!translatable) {
element.setAttribute("translatable", "false")
}
setToolsIgnore(element, toolsIgnore)
for (value in values) {
//Does this mess up the ordering?
val valueElement = document.createElement("item")
valueElement.appendChild(document.createTextNode(value))
element.appendChild(valueElement)
Expand All @@ -444,16 +441,20 @@ class ResourcesPoet private constructor(
* @param name the name
* @param parentRef a ref to the style parent
* @param styleItems list of style items
* @param format the format attribute for the style
* @param toolsIgnore lint rule names to suppress (e.g., "UnusedResource")
* @return poet
*/
fun addStyle(name: String, parentRef: String? = null, styleItems: List<StyleItem>? = null, toolsIgnore: String? = null): ResourcesPoet {
fun addStyle(name: String, parentRef: String? = null, styleItems: List<StyleItem>? = null, format: String? = null, toolsIgnore: String? = null): ResourcesPoet {
//<style name="AppTheme.Dark" parent="Base.AppTheme.Dark"/>
val element = document.createElement(Type.STYLE.toString())
element.setAttribute("name", name)
if (parentRef != null) {
element.setAttribute("parent", parentRef)
}
if (format != null) {
element.setAttribute("format", format)
}
setToolsIgnore(element, toolsIgnore)
if (styleItems != null) {
for (item in styleItems) {
Expand All @@ -472,16 +473,20 @@ class ResourcesPoet private constructor(
*
* @param name the name
* @param values the value
* @param translatable whether this array should be translatable
* @param toolsIgnore lint rule names to suppress (e.g., "UnusedResource")
* @return poet
*/
fun addTypedArray(name: String, values: List<String>, toolsIgnore: String? = null): ResourcesPoet {
fun addTypedArray(name: String, values: List<String>, translatable: Boolean = true, toolsIgnore: String? = null): ResourcesPoet {
//<array name="country_names">
// <item>Country</item>
// <item>United States</item>
// </array>
val element = document.createElement(Type.TYPED_ARRAY.toString())
element.setAttribute("name", name)
if (!translatable) {
element.setAttribute("translatable", "false")
}
setToolsIgnore(element, toolsIgnore)
for (value in values) {
val valueElement = document.createElement("item")
Expand Down Expand Up @@ -509,6 +514,57 @@ class ResourcesPoet private constructor(
return this
}

/**
* Add a fraction to the XML file
*
* @param name the name
* @param value the value (e.g. "50%p")
* @param toolsIgnore lint rule names to suppress (e.g., "UnusedResource")
* @return poet
*/
fun addFraction(name: String, value: String, toolsIgnore: String? = null): ResourcesPoet {
//<fraction name="fraction_value">50%p</fraction>
val element = document.createElement(Type.FRACTION.toString())
element.setAttribute("name", name)
setToolsIgnore(element, toolsIgnore)
element.appendChild(document.createTextNode(value))
resourceElement.appendChild(element)
return this
}

/**
* Add a reference to the XML file
*
* @param name the name
* @param reference the reference to add
* @param toolsIgnore lint rule names to suppress (e.g., "UnusedResource")
* @return poet
*/
fun addReference(name: String, reference: Reference, toolsIgnore: String? = null): ResourcesPoet {
// <item type="reference" name="name">@type/name</item>
val element = document.createElement("item")
element.setAttribute("type", "reference")
element.setAttribute("name", name)
element.appendChild(document.createTextNode(reference.toValue()))
setToolsIgnore(element, toolsIgnore)
resourceElement.appendChild(element)
return this
}

/**
* Add a reference to the XML file
*
* @param name the name
* @param type the reference type (e.g. "drawable", "color")
* @param refName the reference name
* @param toolsIgnore lint rule names to suppress (e.g., "UnusedResource")
* @return poet
*/
fun addReference(name: String, type: String, refName: String, toolsIgnore: String? = null): ResourcesPoet {
addReference(name, Reference(type, refName), toolsIgnore)
return this
}

/**
* Add type to the XML file by its type. Currently supported types:
*
Expand All @@ -524,6 +580,8 @@ class ResourcesPoet private constructor(
*
* [Type.STRING]
*
* [Type.FRACTION]
*
* Any other type will throw an [IllegalArgumentException], as they have a special configuration
*
* @param type the type of the resource you wish to add
Expand All @@ -540,6 +598,7 @@ class ResourcesPoet private constructor(
Type.DRAWABLE -> addDrawable(name, value, toolsIgnore)
Type.INTEGER -> addInteger(name, value, toolsIgnore)
Type.STRING -> addString(name, value, toolsIgnore = toolsIgnore)
Type.FRACTION -> addFraction(name, value, toolsIgnore)
else -> throw IllegalArgumentException("Cannot add type $type. It has a special configuration")
}
}
Expand Down Expand Up @@ -607,6 +666,17 @@ class ResourcesPoet private constructor(
return writer.toString()
}

/**
* Build the XML to a [ByteArray]
*
* @return the xml as a UTF-8 byte array
*/
fun buildBytes(): ByteArray {
val writer = ByteArrayOutputStream()
build(writer)
return writer.toByteArray()
}

/**
* Build the XML to a file. You should call [File.createNewFile] or validate that the file exists
* before calling
Expand Down
3 changes: 2 additions & 1 deletion src/main/kotlin/com/commit451/resourcespoet/Type.kt
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ enum class Type(private val xmlName: String) {
STRING_ARRAY("string-array"),
STYLE("style"),
TYPED_ARRAY("array"),
FONT("font");
FONT("font"),
FRACTION("fraction");

override fun toString() = xmlName
}
35 changes: 35 additions & 0 deletions src/test/kotlin/com/commit451/resourcespoet/BuildByteArrayTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package com.commit451.resourcespoet

import org.junit.Assert.assertTrue
import org.junit.Test
import java.nio.charset.StandardCharsets

/**
* Tests buildBytes() returning a valid UTF-8 ByteArray
*/
class BuildByteArrayTest {

@Test
fun buildBytesReturnsValidXml() {
val poet = ResourcesPoet.create()
.addString("app_name", "Test")

val bytes = poet.buildBytes()
val xml = String(bytes, StandardCharsets.UTF_8)

assertTrue(xml.contains("<?xml"))
assertTrue(xml.contains("<resources>"))
assertTrue(xml.contains("<string name=\"app_name\">Test</string>"))
}

@Test
fun buildBytesContainsUtf8Encoding() {
val poet = ResourcesPoet.create()
.addString("test", "Test")

val bytes = poet.buildBytes()
val xml = String(bytes, StandardCharsets.UTF_8)

assertTrue(xml.contains("encoding=\"utf-8\""))
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package com.commit451.resourcespoet

import org.junit.Assert.assertTrue
import org.junit.Test
import java.io.File

/**
* Tests create(File, indent, elementType)
*/
class CreateFileWithElementTypeTest {

@Test
fun createFileWithElementType() {
val file = File(javaClass.classLoader.getResource("create_file_with_element_type.xml")!!.file)
val poet = ResourcesPoet.create(file, indent = true)
.addColor("color_secondary", "#00FF00")

val result = poet.build()
assertTrue(result.contains("<color name=\"color_primary\">#FF0000</color>"))
assertTrue(result.contains("<color name=\"color_secondary\">#00FF00</color>"))
}

@Test
fun createFileWithElementTypeFontFamilies() {
val file = File(javaClass.classLoader.getResource("fonts.xml")!!.file)
val poet = ResourcesPoet.create(file, indent = true, elementType = ResourcesPoet.Companion.ELEMENT.FONT_FAMILIES)
.addFontFamily(FontFamily("italic", "700", "@font/roboto_italic"))

val result = poet.build()
assertTrue(result.contains("<font-family"))
assertTrue(result.contains("@font/lobster_regular"))
assertTrue(result.contains("@font/roboto_italic"))
}
}
25 changes: 25 additions & 0 deletions src/test/kotlin/com/commit451/resourcespoet/FractionTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.commit451.resourcespoet

import org.junit.Test

/**
* Tests fraction resource creation
*/
class FractionTest {

@Test
fun fractionTest() {
val poet = ResourcesPoet.create()
.addFraction("fraction_value", "50%p")

TestUtil.assertEquals("fraction.xml", poet)
}

@Test
fun fractionWithToolsIgnore() {
val poet = ResourcesPoet.create()
.addFraction("fraction_value", "50%p", toolsIgnore = "UnusedResource")

TestUtil.assertEquals("fraction_tools_ignore.xml", poet)
}
}
Loading
Loading