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