Skip to content

Instantly share code, notes, and snippets.

@VitaliyBelyaev
Created July 7, 2023 15:01
Show Gist options
  • Save VitaliyBelyaev/bc1c95656396e26ddcda54bc40846310 to your computer and use it in GitHub Desktop.
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
{
"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"
}
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