Created
July 7, 2023 15:01
-
-
Save VitaliyBelyaev/bc1c95656396e26ddcda54bc40846310 to your computer and use it in GitHub Desktop.
Python script for generating colors.xml and ligth/dark themes.xml from Material 3 json theme file generated by Figma Material Theme Builder Plugin
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"seed": "#6750A4", | |
"description": "TYPE: CUSTOM", | |
"coreColors": { | |
"primary": "#6750A4", | |
"neutralVariant": "#938F99" | |
}, | |
"schemes": { | |
"light": { | |
"primary": "#6750A4", | |
"onPrimary": "#FFFFFF", | |
"primaryContainer": "#E9DDFF", | |
"onPrimaryContainer": "#22005D", | |
"primaryFixed": "#E9DDFF", | |
"onPrimaryFixed": "#22005D", | |
"primaryFixedDim": "#CFBCFF", | |
"onPrimaryFixedVariant": "#4F378A", | |
"secondary": "#625B71", | |
"onSecondary": "#FFFFFF", | |
"secondaryContainer": "#E8DEF8", | |
"onSecondaryContainer": "#1E192B", | |
"secondaryFixed": "#E8DEF8", | |
"onSecondaryFixed": "#1E192B", | |
"secondaryFixedDim": "#CBC2DB", | |
"onSecondaryFixedVariant": "#4A4458", | |
"tertiary": "#7E5260", | |
"onTertiary": "#FFFFFF", | |
"tertiaryContainer": "#FFD9E3", | |
"onTertiaryContainer": "#31101D", | |
"tertiaryFixed": "#FFD9E3", | |
"onTertiaryFixed": "#31101D", | |
"tertiaryFixedDim": "#EFB8C8", | |
"onTertiaryFixedVariant": "#633B48", | |
"error": "#BA1A1A", | |
"onError": "#FFFFFF", | |
"errorContainer": "#FFDAD6", | |
"onErrorContainer": "#410002", | |
"outline": "#79757F", | |
"background": "#FFFBFF", | |
"onBackground": "#1C1B1E", | |
"surface": "#FDF8FD", | |
"onSurface": "#1C1B1E", | |
"surfaceVariant": "#E6E0EC", | |
"onSurfaceVariant": "#48454E", | |
"inverseSurface": "#313033", | |
"inverseOnSurface": "#F4EFF4", | |
"inversePrimary": "#CFBCFF", | |
"shadow": "#000000", | |
"surfaceTint": "#6750A4", | |
"outlineVariant": "#C9C4D0", | |
"scrim": "#000000", | |
"surfaceContainerHighest": "#E6E1E6", | |
"surfaceContainerHigh": "#ECE7EB", | |
"surfaceContainer": "#F2ECF1", | |
"surfaceContainerLow": "#F7F2F7", | |
"surfaceContainerLowest": "#FFFFFF", | |
"surfaceBright": "#FDF8FD", | |
"surfaceDim": "#DDD8DD" | |
}, | |
"dark": { | |
"primary": "#CFBCFF", | |
"onPrimary": "#381E72", | |
"primaryContainer": "#4F378A", | |
"onPrimaryContainer": "#E9DDFF", | |
"primaryFixed": "#E9DDFF", | |
"onPrimaryFixed": "#22005D", | |
"primaryFixedDim": "#CFBCFF", | |
"onPrimaryFixedVariant": "#4F378A", | |
"secondary": "#CBC2DB", | |
"onSecondary": "#332D41", | |
"secondaryContainer": "#4A4458", | |
"onSecondaryContainer": "#E8DEF8", | |
"secondaryFixed": "#E8DEF8", | |
"onSecondaryFixed": "#1E192B", | |
"secondaryFixedDim": "#CBC2DB", | |
"onSecondaryFixedVariant": "#4A4458", | |
"tertiary": "#EFB8C8", | |
"onTertiary": "#4A2532", | |
"tertiaryContainer": "#633B48", | |
"onTertiaryContainer": "#FFD9E3", | |
"tertiaryFixed": "#FFD9E3", | |
"onTertiaryFixed": "#31101D", | |
"tertiaryFixedDim": "#EFB8C8", | |
"onTertiaryFixedVariant": "#633B48", | |
"error": "#FFB4AB", | |
"onError": "#690005", | |
"errorContainer": "#93000A", | |
"onErrorContainer": "#FFDAD6", | |
"outline": "#938F99", | |
"background": "#1C1B1E", | |
"onBackground": "#E6E1E6", | |
"surface": "#141316", | |
"onSurface": "#CAC5CA", | |
"surfaceVariant": "#48454E", | |
"onSurfaceVariant": "#C9C4D0", | |
"inverseSurface": "#E6E1E6", | |
"inverseOnSurface": "#1C1B1E", | |
"inversePrimary": "#6750A4", | |
"shadow": "#000000", | |
"surfaceTint": "#CFBCFF", | |
"outlineVariant": "#48454E", | |
"scrim": "#000000", | |
"surfaceContainerHighest": "#363438", | |
"surfaceContainerHigh": "#2B292D", | |
"surfaceContainer": "#201F22", | |
"surfaceContainerLow": "#1C1B1E", | |
"surfaceContainerLowest": "#0F0E11", | |
"surfaceBright": "#3A383C", | |
"surfaceDim": "#141316" | |
} | |
}, | |
"palettes": { | |
"primary": { | |
"0": "#000000", | |
"5": "#160041", | |
"10": "#22005D", | |
"20": "#381E72", | |
"25": "#432B7E", | |
"30": "#4F378A", | |
"35": "#5B4397", | |
"40": "#6750A4", | |
"50": "#8069BF", | |
"60": "#9A83DB", | |
"70": "#B69DF8", | |
"80": "#CFBCFF", | |
"90": "#E9DDFF", | |
"95": "#F6EEFF", | |
"98": "#FDF7FF", | |
"99": "#FFFBFF", | |
"100": "#FFFFFF" | |
}, | |
"secondary": { | |
"0": "#000000", | |
"5": "#130E20", | |
"10": "#1E192B", | |
"20": "#332D41", | |
"25": "#3E384C", | |
"30": "#4A4458", | |
"35": "#564F64", | |
"40": "#625B71", | |
"50": "#7B748A", | |
"60": "#958DA4", | |
"70": "#B0A7C0", | |
"80": "#CBC2DB", | |
"90": "#E8DEF8", | |
"95": "#F6EEFF", | |
"98": "#FDF7FF", | |
"99": "#FFFBFF", | |
"100": "#FFFFFF" | |
}, | |
"tertiary": { | |
"0": "#000000", | |
"5": "#240612", | |
"10": "#31101D", | |
"20": "#4A2532", | |
"25": "#56303D", | |
"30": "#633B48", | |
"35": "#704654", | |
"40": "#7E5260", | |
"50": "#996A79", | |
"60": "#B58392", | |
"70": "#D29DAD", | |
"80": "#EFB8C8", | |
"90": "#FFD9E3", | |
"95": "#FFECF0", | |
"98": "#FFF8F8", | |
"99": "#FFFBFF", | |
"100": "#FFFFFF" | |
}, | |
"error": { | |
"0": "#000000", | |
"5": "#2D0001", | |
"10": "#410002", | |
"20": "#690005", | |
"25": "#7E0007", | |
"30": "#93000A", | |
"35": "#A80710", | |
"40": "#BA1A1A", | |
"50": "#DE3730", | |
"60": "#FF5449", | |
"70": "#FF897D", | |
"80": "#FFB4AB", | |
"90": "#FFDAD6", | |
"95": "#FFEDEA", | |
"98": "#FFF8F7", | |
"99": "#FFFBFF", | |
"100": "#FFFFFF" | |
}, | |
"neutral": { | |
"0": "#000000", | |
"5": "#121014", | |
"10": "#1C1B1E", | |
"20": "#313033", | |
"25": "#3D3B3E", | |
"30": "#48464A", | |
"35": "#545156", | |
"40": "#605D62", | |
"50": "#79767A", | |
"60": "#938F94", | |
"70": "#AEAAAE", | |
"80": "#CAC5CA", | |
"90": "#E6E1E6", | |
"95": "#F4EFF4", | |
"98": "#FDF8FD", | |
"99": "#FFFBFF", | |
"100": "#FFFFFF" | |
}, | |
"neutralVariant": { | |
"0": "#000000", | |
"5": "#111017", | |
"10": "#1C1A22", | |
"20": "#312F38", | |
"25": "#3D3A43", | |
"30": "#48454E", | |
"35": "#54515A", | |
"40": "#605D66", | |
"50": "#79757F", | |
"60": "#938F99", | |
"70": "#AEA9B4", | |
"80": "#C9C4D0", | |
"90": "#E6E0EC", | |
"95": "#F4EEFA", | |
"98": "#FDF8FF", | |
"99": "#FFFBFF", | |
"100": "#FFFFFF" | |
} | |
}, | |
"styles": { | |
"display": { | |
"large": { | |
"fontFamilyName": "Roboto", | |
"fontFamilyStyle": "Regular", | |
"fontWeight": 400, | |
"fontSize": 57, | |
"lineHeight": 64, | |
"letterSpacing": -0.25 | |
}, | |
"medium": { | |
"fontFamilyName": "Roboto", | |
"fontFamilyStyle": "Regular", | |
"fontWeight": 400, | |
"fontSize": 45, | |
"lineHeight": 52, | |
"letterSpacing": 0 | |
}, | |
"small": { | |
"fontFamilyName": "Roboto", | |
"fontFamilyStyle": "Regular", | |
"fontWeight": 400, | |
"fontSize": 36, | |
"lineHeight": 44, | |
"letterSpacing": 0 | |
} | |
}, | |
"headline": { | |
"large": { | |
"fontFamilyName": "Roboto", | |
"fontFamilyStyle": "Regular", | |
"fontWeight": 400, | |
"fontSize": 32, | |
"lineHeight": 40, | |
"letterSpacing": 0 | |
}, | |
"medium": { | |
"fontFamilyName": "Roboto", | |
"fontFamilyStyle": "Regular", | |
"fontWeight": 400, | |
"fontSize": 28, | |
"lineHeight": 36, | |
"letterSpacing": 0 | |
}, | |
"small": { | |
"fontFamilyName": "Roboto", | |
"fontFamilyStyle": "Regular", | |
"fontWeight": 400, | |
"fontSize": 24, | |
"lineHeight": 32, | |
"letterSpacing": 0 | |
} | |
}, | |
"body": { | |
"large": { | |
"fontFamilyName": "Roboto", | |
"fontFamilyStyle": "Regular", | |
"fontWeight": 400, | |
"fontSize": 16, | |
"lineHeight": 24, | |
"letterSpacing": 0.5 | |
}, | |
"medium": { | |
"fontFamilyName": "Roboto", | |
"fontFamilyStyle": "Regular", | |
"fontWeight": 400, | |
"fontSize": 14, | |
"lineHeight": 20, | |
"letterSpacing": 0.25 | |
}, | |
"small": { | |
"fontFamilyName": "Roboto", | |
"fontFamilyStyle": "Regular", | |
"fontWeight": 400, | |
"fontSize": 12, | |
"lineHeight": 16, | |
"letterSpacing": 0.4000000059604645 | |
} | |
}, | |
"label": { | |
"large": { | |
"fontFamilyName": "Roboto", | |
"fontFamilyStyle": "Medium", | |
"fontWeight": 500, | |
"fontSize": 14, | |
"lineHeight": 20, | |
"letterSpacing": 0.10000000149011612 | |
}, | |
"medium": { | |
"fontFamilyName": "Roboto", | |
"fontFamilyStyle": "Medium", | |
"fontWeight": 500, | |
"fontSize": 12, | |
"lineHeight": 16, | |
"letterSpacing": 0.5 | |
}, | |
"small": { | |
"fontFamilyName": "Roboto", | |
"fontFamilyStyle": "Medium", | |
"fontWeight": 500, | |
"fontSize": 11, | |
"lineHeight": 16, | |
"letterSpacing": 0.5 | |
} | |
}, | |
"title": { | |
"large": { | |
"fontFamilyName": "Roboto", | |
"fontFamilyStyle": "Regular", | |
"fontWeight": 400, | |
"fontSize": 22, | |
"lineHeight": 28, | |
"letterSpacing": 0 | |
}, | |
"medium": { | |
"fontFamilyName": "Roboto", | |
"fontFamilyStyle": "Medium", | |
"fontWeight": 500, | |
"fontSize": 16, | |
"lineHeight": 24, | |
"letterSpacing": 0.15000000596046448 | |
}, | |
"small": { | |
"fontFamilyName": "Roboto", | |
"fontFamilyStyle": "Medium", | |
"fontWeight": 500, | |
"fontSize": 14, | |
"lineHeight": 20, | |
"letterSpacing": 0.10000000149011612 | |
} | |
} | |
}, | |
"extendedColors": [], | |
"name": "material-theme" | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import sys | |
import os | |
import json | |
import xml.etree.ElementTree as ET | |
import xml.dom.minidom as minidom | |
# Helper text message | |
HELP_MESSAGE = """ | |
Usage: python script.py <json_file_path> | |
This script reads a JSON file containing color data and generates XML files 'colors.xml', 'themes.xml' (light), and 'themes.xml' (dark) based on the JSON content. | |
Arguments: | |
<json_file_path> Path to the JSON file to process. | |
""" | |
# Check if a JSON file path is provided as an argument | |
if len(sys.argv) < 2: | |
print(HELP_MESSAGE) | |
sys.exit(1) | |
json_file_path = sys.argv[1] | |
try: | |
# Load JSON content from the file | |
with open(json_file_path) as json_file: | |
data = json.load(json_file) | |
except FileNotFoundError: | |
print("JSON file not found.") | |
sys.exit(1) | |
except json.JSONDecodeError: | |
print("Invalid JSON file.") | |
sys.exit(1) | |
# Create XML root element for colors.xml | |
colors_root = ET.Element("resources") | |
# Access color values | |
schemes = data['schemes'] | |
for mode, colors in schemes.items(): | |
for color_name, color_value in colors.items(): | |
color_item = ET.SubElement(colors_root, "color") | |
color_item.set("name", f"md_theme_{mode}_{color_name}") | |
color_item.text = color_value | |
# Create XML tree for colors.xml | |
colors_tree = ET.ElementTree(colors_root) | |
# Generate a human-readable and well-formatted XML string for colors.xml | |
colors_xml_string = minidom.parseString(ET.tostring(colors_root)).toprettyxml(indent=" ") | |
# Write the colors.xml string to the file | |
colors_output_file_path = "colors.xml" | |
with open(colors_output_file_path, "w") as colors_xml_file: | |
colors_xml_file.write(colors_xml_string) | |
print(f"{colors_output_file_path} file generated successfully.") | |
# Create light and dark folders | |
if not os.path.exists("light"): | |
os.makedirs("light") | |
if not os.path.exists("dark"): | |
os.makedirs("dark") | |
# Generate styles for themes.xml (light and dark) | |
for theme_mode, theme_folder in [("light", "light"), ("dark", "dark")]: | |
# Create XML root element for themes.xml | |
themes_root = ET.Element("resources") | |
# Create style element for AppTheme | |
app_theme = ET.SubElement(themes_root, "style") | |
app_theme.set("name", "AppTheme") | |
app_theme.set("parent", f"Theme.Material3.{theme_mode.capitalize()}.NoActionBar") | |
# Access color values | |
schemes = data['schemes'][theme_mode] | |
for color_name_origin, _ in schemes.items(): | |
if color_name_origin not in ["shadow", "surfaceTint", "scrim"]: | |
color_item = ET.SubElement(app_theme, "item") | |
color_name = color_name_origin | |
if color_name_origin == "inversePrimary": | |
color_name = "primaryInverse" | |
elif color_name_origin == "inverseSurface": | |
color_name = "surfaceInverse" | |
elif color_name_origin == "inverseOnSurface": | |
color_name = "onSurfaceInverse" | |
# Set item name with "color" prefix and the capitalized color name | |
item_name = f"color{color_name[0].upper() + color_name[1:]}" | |
if color_name_origin == "background": | |
item_name = "android:colorBackground" | |
color_item.set("name", item_name) | |
color_item.text = f"@color/md_theme_{theme_mode}_{color_name_origin}" | |
# Create XML tree for themes.xml | |
themes_tree = ET.ElementTree(themes_root) | |
# Generate a human-readable and well-formatted XML string for themes.xml | |
themes_xml_string = minidom.parseString(ET.tostring(themes_root)).toprettyxml(indent=" ") | |
# Write the themes.xml string to the file | |
themes_output_file_path = f"{theme_folder}/themes.xml" | |
with open(themes_output_file_path, "w") as themes_xml_file: | |
themes_xml_file.write(themes_xml_string) | |
print(f"{themes_output_file_path} file generated successfully.") | |
print("All XML files generated successfully.") |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment