1import { readXMLAsync, XMLObject } from '../utils/XML'; 2 3export type ResourceGroupXML = { 4 $: { 5 name: string; 6 parent: string; 7 }; 8 item: ResourceItemXML[]; 9}; 10 11export type ResourceXML = { 12 resources: { 13 $?: { 14 'xmlns:tools'?: string; 15 }; 16 color?: ResourceItemXML[]; 17 string?: ResourceItemXML[]; 18 style?: ResourceGroupXML[]; 19 // Add more if needed... 20 }; 21}; 22 23export type ResourceItemXML = { 24 _: string; 25 $: { 26 name: string; 27 'tools:targetApi'?: string; 28 translatable?: string; 29 }; 30}; 31/** 32 * Name of the resource folder. 33 */ 34export type ResourceKind = 35 | 'values' 36 | 'values-night' 37 | 'values-v23' 38 | 'values-night-v23' 39 | 'drawable'; 40 41const fallbackResourceString = `<?xml version="1.0" encoding="utf-8"?><resources></resources>`; 42 43/** 44 * Read an XML file while providing a default fallback for resource files. 45 * 46 * @param options path to the XML file, returns a fallback XML if the path doesn't exist. 47 */ 48export async function readResourcesXMLAsync({ 49 path, 50 fallback = fallbackResourceString, 51}: { 52 path: string; 53 fallback?: string | null; 54}): Promise<ResourceXML> { 55 const xml = await readXMLAsync({ path, fallback }); 56 // Ensure the type is expected. 57 if (!xml.resources) { 58 xml.resources = {}; 59 } 60 return xml as ResourceXML; 61} 62 63/** 64 * Ensure the provided xml has a `resources` object (the expected shape). 65 * 66 * @param xml 67 */ 68export function ensureDefaultResourceXML(xml: XMLObject): ResourceXML { 69 if (!xml) { 70 xml = { resources: {} }; 71 } 72 if (!xml.resources) { 73 xml.resources = {}; 74 } 75 76 return xml as ResourceXML; 77} 78 79/** 80 * Build a `ResourceItemXML` given its `name` and `value`. This makes things a bit more readable. 81 * 82 * - JSON: `{ $: { name }, _: value }` 83 * - XML: `<item name="NAME">VALUE</item>` 84 * 85 * @param props name and value strings. 86 */ 87export function buildResourceItem({ 88 name, 89 value, 90 targetApi, 91 translatable, 92}: { 93 name: string; 94 value: string; 95 targetApi?: string; 96 translatable?: boolean; 97}): ResourceItemXML { 98 const item: ResourceItemXML = { $: { name }, _: value }; 99 if (targetApi) { 100 item.$['tools:targetApi'] = targetApi; 101 } 102 if (translatable !== undefined) { 103 item.$['translatable'] = String(translatable); 104 } 105 return item; 106} 107 108export function buildResourceGroup(parent: { 109 name: string; 110 parent: string; 111 items?: ResourceItemXML[]; 112}): ResourceGroupXML { 113 return { 114 $: { name: parent.name, parent: parent.parent }, 115 item: parent.items ?? [], 116 }; 117} 118 119export function findResourceGroup( 120 xml: ResourceGroupXML[] | undefined, 121 group: { name: string; parent?: string } 122): ResourceGroupXML | null { 123 const app = xml?.filter?.(({ $: head }) => { 124 let matches = head.name === group.name; 125 if (group.parent != null && matches) { 126 matches = head.parent === group.parent; 127 } 128 return matches; 129 })?.[0]; 130 return app ?? null; 131} 132 133/** 134 * Helper to convert a basic XML object into a simple k/v pair. 135 * 136 * @param xml 137 * @returns 138 */ 139export function getResourceItemsAsObject(xml: ResourceItemXML[]): Record<string, string> | null { 140 return xml.reduce( 141 (prev, curr) => ({ 142 ...prev, 143 [curr.$.name]: curr._, 144 }), 145 {} 146 ); 147} 148 149/** 150 * Helper to convert a basic k/v object to a ResourceItemXML array. 151 * 152 * @param xml 153 * @returns 154 */ 155export function getObjectAsResourceItems(obj: Record<string, string>): ResourceItemXML[] { 156 return Object.entries(obj).map(([name, value]) => ({ 157 $: { name }, 158 _: value, 159 })); 160} 161 162export function getObjectAsResourceGroup(group: { 163 name: string; 164 parent: string; 165 item: Record<string, string>; 166}): ResourceGroupXML { 167 return { 168 $: { 169 name: group.name, 170 parent: group.parent, 171 }, 172 item: getObjectAsResourceItems(group.item), 173 }; 174} 175