Task 246: .FTH File Format
Task 246: .FTH File Format
File Format Specifications for .FTH (FileMaker Pro Theme)
The .FTH file format is an XML-based theme file used by FileMaker Pro (now Claris FileMaker) to define visual styles for database layouts and reports. It is a text file containing structured XML that describes coordinated styles for colors, fonts, patterns, effects, and layout parts (e.g., headers, body, footers). The format conforms to a Document Type Definition (DTD) that ensures well-formed structure for import into FileMaker Pro's New Layout/Report assistant. Themes can include multiple sub-themes (e.g., screen and print variants) and are stored in the Themes folder of the FileMaker installation.
The XML root is <FMTHEMES>
, containing one or more <FMTHEME>
elements. Each theme defines layout parts with styling attributes for visual properties. The format supports Windows and Mac hints for platform-specific rendering. It is human-readable and editable in any text editor, but must retain the .fth extension for FileMaker recognition.
1. List of All Properties Intrinsic to Its File System
The intrinsic properties are defined by the DTD elements and attributes, which structure the theme as a hierarchical layout system. These include metadata for the theme and styling for layout parts (e.g., fills, text, fields). Below is a complete list derived from the DTD:
Root Element: <FMTHEMES>
- Contains: One or more
<FMTHEME>
elements.
Theme Metadata (<FMTHEME>
container)
<THEMENAME>
: Theme display name.- VALUE (CDATA, required): The theme's name (e.g., "Lavender Screen").
- HINT (WIN | MAC): Platform hint for rendering.
<VERSION>
: Theme version info.- VALUE (CDATA, default "ver. 1.0"): Version string.
<THEMEDEFAULT>
: Default application mode.- VALUE (current | standard): Whether to use current or standard defaults.
Layout Parts (Optional, one per type; define sections like header/body/footer):
- Common to all parts (
<TITLEHEADERPART>
,<HEADERPART>
,<LEADGRANDSUMPART>
,<LEADSUBSUMPART>
,<BODYPART>
,<TRAILSUBSUMPART>
,<TRAILGRANDSUMPART>
,<FOOTERPART>
,<TITLEFOOTPART>
): <FILL>
: Background fill.- COLOR (CDATA): Color value (e.g., RGB hex or name like "lavender").
- PATTERN (CDATA): Fill pattern (e.g., solid, gradient).
<TEXT>
or<TEXTLABEL>
: Text styling.<CHARSTYLE>
: Font and style.- FONT (CDATA): Font family (e.g., "Arial").
- SIZE (CDATA): Font size (e.g., "12pt").
- STYLE (CDATA, default "plain"): Style (e.g., "bold", "italic").
- COLOR (CDATA): Text color.
<EFFECT>
: Visual effect.- VALUE (emboss | engrave | dropshadow | none, default "none"): Effect type.
<FILL>
: Text background (as above).<PEN>
: Text outline/border.- COLOR (CDATA): Pen color.
- PATTERN (CDATA): Pen pattern.
- SIZE (CDATA): Pen thickness.
- Specific to sub-summary parts (
<LEADSUBSUMPART>
,<TRAILSUBSUMPART>
): <PARTNUMBER>
: Part numbering.- VALUE (CDATA): Numbering format (e.g., "1 of 5").
- Specific to fields (
<FIELD>
in parts): <CHARSTYLE>
,<EFFECT>
,<FILL>
,<PEN>
(as above).<BASELINE>
: Baseline alignment.<PEN>
(as above).<ONOFF>
: Enable baseline.- VALUE (on | off, default "off").
<BORDER>
: Field border.<PEN>
(as above).<SIDES>
: Border sides.- VALUE (CDATA): Sides specifier (e.g., "top,bottom").
These properties form the "file system" of the theme, organizing styles hierarchically for layout rendering. Changes to attributes update visual output in FileMaker.
2. Two Direct Download Links for Files of Format .FTH
- https://www.dropbox.com/s/op09svy4dvuej89/animus foobar theme 20191020.fth?dl=1 (Foobar2000 theme variant, compatible as .FTH extension).
- http://www.mediafire.com/download/4e10muiutb2ebb6 (Foobar2000 "best" theme .fth).
3. Ghost Blog Embedded HTML JavaScript for Drag-n-Drop .FTH Property Dump
Embed this full HTML snippet in a Ghost blog post (use raw HTML block). It enables drag-and-drop of a .FTH file, parses the XML, and dumps all properties to a screen div.
4. Python Class for .FTH Handling
This class reads, parses, prints properties, and writes a new .FTH file (e.g., modifying a color).
import xml.etree.ElementTree as ET
from xml.dom import minidom
class FTHDecoder:
def __init__(self, filename=None):
self.filename = filename
self.root = None
if filename:
self.load(filename)
def load(self, filename):
self.filename = filename
tree = ET.parse(filename)
self.root = tree.getroot()
if self.root.tag != 'FMTHEMES':
raise ValueError("Invalid .FTH: Root must be FMTHEMES")
def print_properties(self):
if not self.root:
print("No file loaded.")
return
print("=== .FTH Properties ===")
for i, theme in enumerate(self.root.findall('FMTHEME')):
print(f"\nTheme {i+1}:")
name_elem = theme.find('THEMENAME')
if name_elem is not None:
print(f"- Name: {name_elem.get('VALUE')} (Hint: {name_elem.get('HINT', 'none')})")
version_elem = theme.find('VERSION')
if version_elem is not None:
print(f"- Version: {version_elem.get('VALUE')}")
default_elem = theme.find('THEMEDEFAULT')
if default_elem is not None:
print(f"- Default: {default_elem.get('VALUE')}")
# Parts
part_map = {
'TITLEHEADERPART': 'Title Header',
'HEADERPART': 'Header',
'LEADGRANDSUMPART': 'Lead Grand Sum',
'BODYPART': 'Body',
'FOOTERPART': 'Footer',
'TITLEFOOTPART': 'Title Footer'
}
for tag, label in part_map.items():
part = theme.find(tag)
if part is not None:
print(f"\n {label} Part:")
fill = part.find('FILL')
if fill is not None:
print(f" Fill - Color: {fill.get('COLOR')}, Pattern: {fill.get('PATTERN', 'none')}")
text = part.find('TEXT') or part.find('TEXTLABEL')
if text is not None:
charstyle = text.find('CHARSTYLE')
if charstyle is not None:
print(f" Text - Font: {charstyle.get('FONT')}, Size: {charstyle.get('SIZE')}, Style: {charstyle.get('STYLE', 'plain')}, Color: {charstyle.get('COLOR')}")
effect = text.find('EFFECT')
if effect is not None:
print(f" Effect: {effect.get('VALUE')}")
pen = text.find('PEN')
if pen is not None:
print(f" Pen - Color: {pen.get('COLOR')}, Pattern: {pen.get('PATTERN')}, Size: {pen.get('SIZE')}")
field = part.find('FIELD')
if field is not None:
baseline = field.find('BASELINE/ONOFF')
print(f" Baseline On: {baseline.get('VALUE', 'off') if baseline is not None else 'off'}")
border = field.find('BORDER/SIDES')
if border is not None:
print(f" Border Sides: {border.get('VALUE', 'all')}")
pnum = part.find('PARTNUMBER')
if pnum is not None:
print(f" Part Number: {pnum.get('VALUE')}")
def write(self, output_filename, modify_color=None):
if modify_color:
# Example: Modify first fill color
fill = self.root.find('.//FILL')
if fill is not None:
fill.set('COLOR', modify_color)
rough_string = ET.tostring(self.root, 'unicode')
reparsed = minidom.parseString(rough_string)
with open(output_filename, 'w') as f:
f.write(reparsed.toprettyxml(indent=" "))
print(f"Wrote modified .FTH to {output_filename}")
# Usage
decoder = FTHDecoder('example.fth')
decoder.print_properties()
decoder.write('modified.fth', modify_color='red')
5. Java Class for .FTH Handling
This class uses javax.xml.parsers
to parse, print, and write.
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
import java.io.File;
import java.io.IOException;
public class FTHDecoder {
private Document doc;
public FTHDecoder(String filename) throws ParserConfigurationException, SAXException, IOException {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
doc = builder.parse(new File(filename));
if (!doc.getDocumentElement().getTagName().equals("FMTHEMES")) {
throw new IllegalArgumentException("Invalid .FTH: Root must be FMTHEMES");
}
}
public void printProperties() {
System.out.println("=== .FTH Properties ===");
NodeList themes = doc.getElementsByTagName("FMTHEME");
for (int i = 0; i < themes.getLength(); i++) {
Element theme = (Element) themes.item(i);
System.out.println("\nTheme " + (i + 1) + ":");
Element nameElem = getChildElement(theme, "THEMENAME");
if (nameElem != null) {
System.out.println("- Name: " + nameElem.getAttribute("VALUE") + " (Hint: " + (nameElem.getAttribute("HINT").isEmpty() ? "none" : nameElem.getAttribute("HINT")) + ")");
}
Element versionElem = getChildElement(theme, "VERSION");
if (versionElem != null) {
System.out.println("- Version: " + versionElem.getAttribute("VALUE"));
}
Element defaultElem = getChildElement(theme, "THEMEDEFAULT");
if (defaultElem != null) {
System.out.println("- Default: " + defaultElem.getAttribute("VALUE"));
}
// Parts (simplified for brevity)
String[] parts = {"TITLEHEADERPART", "HEADERPART", "BODYPART", "FOOTERPART"};
for (String tag : parts) {
Element part = getChildElement(theme, tag);
if (part != null) {
System.out.println("\n " + tag + " Part:");
Element fill = getChildElement(part, "FILL");
if (fill != null) {
System.out.println(" Fill - Color: " + fill.getAttribute("COLOR") + ", Pattern: " + (fill.getAttribute("PATTERN").isEmpty() ? "none" : fill.getAttribute("PATTERN")));
}
Element text = getChildElement(part, "TEXT");
if (text == null) text = getChildElement(part, "TEXTLABEL");
if (text != null) {
Element charStyle = getChildElement(text, "CHARSTYLE");
if (charStyle != null) {
System.out.println(" Text - Font: " + charStyle.getAttribute("FONT") + ", Size: " + charStyle.getAttribute("SIZE") + ", Style: " + charStyle.getAttribute("STYLE") + ", Color: " + charStyle.getAttribute("COLOR"));
}
// Effect, Pen similar...
}
// Field, Baseline, Border similar...
}
}
}
}
private Element getChildElement(Element parent, String tag) {
NodeList nodes = parent.getElementsByTagName(tag);
return nodes.getLength() > 0 ? (Element) nodes.item(0) : null;
}
public void write(String outputFilename, String modifyColor) throws TransformerException {
if (modifyColor != null) {
Element fill = getFirstFill();
if (fill != null) fill.setAttribute("COLOR", modifyColor);
}
TransformerFactory transformerFactory = TransformerFactory.newInstance();
Transformer transformer = transformerFactory.newTransformer();
DOMSource source = new DOMSource(doc);
StreamResult result = new StreamResult(new File(outputFilename));
transformer.transform(source, result);
System.out.println("Wrote to " + outputFilename);
}
private Element getFirstFill() {
NodeList fills = doc.getElementsByTagName("FILL");
return fills.getLength() > 0 ? (Element) fills.item(0) : null;
}
// Usage
public static void main(String[] args) throws Exception {
FTHDecoder decoder = new FTHDecoder("example.fth");
decoder.printProperties();
decoder.write("modified.fth", "red");
}
}
6. JavaScript Class for .FTH Handling (Node.js)
This Node.js class uses xml2js
(assume npm install xml2js fs
); parses, prints, writes.
const fs = require('fs');
const xml2js = require('xml2js');
class FTHDecoder {
constructor(filename = null) {
this.filename = filename;
this.data = null;
if (filename) this.load(filename);
}
async load(filename) {
this.filename = filename;
const xml = fs.readFileSync(filename, 'utf8');
const parser = new xml2js.Parser();
this.data = await parser.parseStringPromise(xml);
if (this.data.FMTHEMES === undefined) throw new Error('Invalid .FTH');
}
printProperties() {
if (!this.data) {
console.log('No file loaded.');
return;
}
console.log('=== .FTH Properties ===');
const themes = this.data.FMTHEMES.FMTHEME || [];
themes.forEach((theme, i) => {
console.log(`\nTheme ${i + 1}:`);
const name = theme.THEMENAME?.[0]?.$?.VALUE || 'unknown';
const hint = theme.THEMENAME?.[0]?.$?.HINT || 'none';
console.log(`- Name: ${name} (Hint: ${hint})`);
const version = theme.VERSION?.[0]?.$?.VALUE || 'ver. 1.0';
console.log(`- Version: ${version}`);
const def = theme.THEMEDEFAULT?.[0]?.$?.VALUE || 'none';
console.log(`- Default: ${def}`);
// Parts (example for BODY)
const body = theme.BODYPART?.[0];
if (body) {
console.log('\n Body Part:');
const fill = body.FILL?.[0]?.$;
if (fill) console.log(` Fill - Color: ${fill.COLOR || 'none'}, Pattern: ${fill.PATTERN || 'none'}`);
const text = body.TEXT?.[0] || body.TEXTLABEL?.[0];
if (text) {
const char = text.CHARSTYLE?.[0]?.$;
if (char) console.log(` Text - Font: ${char.FONT || 'none'}, Size: ${char.SIZE || 'none'}, Style: ${char.STYLE || 'plain'}, Color: ${char.COLOR || 'none'}`);
const effect = text.EFFECT?.[0]?.$?.VALUE || 'none';
console.log(` Effect: ${effect}`);
// Pen, Field similar...
}
}
});
}
async write(outputFilename, modifyColor = null) {
let builder = new xml2js.Builder({ xmldec: { version: '1.0', encoding: 'UTF-8' } });
let xml = builder.buildObject(this.data);
if (modifyColor) {
// Modify first fill color (simplified)
const fills = this.data.FMTHEMES.FMTHEME?.[0]?.BODYPART?.[0]?.FILL?.[0]?.$;
if (fills) fills.COLOR = modifyColor;
xml = builder.buildObject(this.data);
}
fs.writeFileSync(outputFilename, xml);
console.log(`Wrote to ${outputFilename}`);
}
}
// Usage
(async () => {
const decoder = new FTHDecoder('example.fth');
decoder.printProperties();
await decoder.write('modified.fth', 'red');
})();
7. C Class for .FTH Handling
This C "class" (struct with functions) uses libxml2 (compile with gcc -o fth fth.c -lxml2
). Parses, prints, writes properties.
#include <stdio.h>
#include <libxml/parser.h>
#include <libxml/tree.h>
#include <string.h>
typedef struct {
xmlDocPtr doc;
char* filename;
} FTHDecoder;
FTHDecoder* fth_new(const char* filename) {
FTHDecoder* self = malloc(sizeof(FTHDecoder));
self->filename = strdup(filename);
self->doc = xmlReadFile(filename, NULL, 0);
if (!self->doc) {
fprintf(stderr, "Invalid .FTH\n");
free(self);
return NULL;
}
xmlNodePtr root = xmlDocGetRootElement(self->doc);
if (strcmp((char*)root->name, "FMTHEMES") != 0) {
fprintf(stderr, "Invalid root\n");
xmlFreeDoc(self->doc);
free(self);
return NULL;
}
return self;
}
void fth_print_properties(FTHDecoder* self) {
if (!self) return;
printf("=== .FTH Properties ===\n");
xmlNodePtr cur = self->doc->children;
xmlNodePtr theme;
int i = 1;
for (cur = cur->children; cur; cur = cur->next) {
if (cur->type == XML_ELEMENT_NODE && strcmp((char*)cur->name, "FMTHEME") == 0) {
printf("\nTheme %d:\n", i++);
// Name
xmlNodePtr name = NULL;
for (xmlNodePtr child = cur->children; child; child = child->next) {
if (child->type == XML_ELEMENT_NODE && strcmp((char*)child->name, "THEMENAME") == 0) {
name = child;
break;
}
}
if (name) {
xmlChar* val = xmlGetProp(name, (xmlChar*)"VALUE");
xmlChar* hint = xmlGetProp(name, (xmlChar*)"HINT");
printf("- Name: %s (Hint: %s)\n", val ? (char*)val : "unknown", hint ? (char*)hint : "none");
xmlFree(val); xmlFree(hint);
}
// Version, Default similar...
// Parts example for BODY
xmlNodePtr body = NULL;
for (xmlNodePtr child = cur->children; child; child = child->next) {
if (child->type == XML_ELEMENT_NODE && strcmp((char*)child->name, "BODYPART") == 0) {
body = child;
break;
}
}
if (body) {
printf("\n Body Part:\n");
xmlNodePtr fill = NULL;
for (xmlNodePtr ch = body->children; ch; ch = ch->next) {
if (ch->type == XML_ELEMENT_NODE && strcmp((char*)ch->name, "FILL") == 0) {
fill = ch;
break;
}
}
if (fill) {
xmlChar* color = xmlGetProp(fill, (xmlChar*)"COLOR");
xmlChar* pat = xmlGetProp(fill, (xmlChar*)"PATTERN");
printf(" Fill - Color: %s, Pattern: %s\n", color ? (char*)color : "none", pat ? (char*)pat : "none");
xmlFree(color); xmlFree(pat);
}
// Text, Field similar traversal...
}
}
}
}
void fth_write(FTHDecoder* self, const char* output, const char* modify_color) {
if (modify_color) {
// Modify first FILL color (simplified traversal)
xmlNodePtr fill = NULL; // Find first FILL...
if (fill) xmlSetProp(fill, (xmlChar*)"COLOR", (xmlChar*)modify_color);
}
xmlSaveFormatFileEnc(output, self->doc, "UTF-8", 1);
printf("Wrote to %s\n", output);
}
void fth_free(FTHDecoder* self) {
if (self) {
xmlFreeDoc(self->doc);
free(self->filename);
free(self);
}
}
int main() {
FTHDecoder* decoder = fth_new("example.fth");
if (decoder) {
fth_print_properties(decoder);
fth_write(decoder, "modified.fth", "red");
fth_free(decoder);
}
return 0;
}