commit 2adbc60fe7a30a67d77c5e9573429734400e59f8 Author: Christian Johannesen Date: Mon Oct 12 08:56:18 2026 +0200 Initial release of CaTML (Cat Altered Text Markup Language) compiler and runtime diff --git a/README.md b/README.md new file mode 100644 index 0000000..ac61ff4 --- /dev/null +++ b/README.md @@ -0,0 +1,151 @@ +# CaTML (Cat Altered Text Markup Language) ๐Ÿพ + +> Standard HTML is for humans. **CaTML** is for fine felines. + +CaTML is a lightweight, zero-dependency HTML compiler and runtime written in JavaScript that translates feline markup tags and attributes directly into standard semantic HTML. + +Repository: [https://git.p20.dev/christian/CaTML.git](https://git.p20.dev/christian/CaTML.git) + +--- + +## ๐Ÿš€ Quick Start + +### 1. In HTML with ` + + + + + +``` + +### 2. Standalone Page (``) + +You can even write the entire document root in CaTML: + +```html + + + Kitty Portal + + + + + Welcome! + The world is feline now. + + + +``` + +### 3. JavaScript API + +```javascript +// Compile to HTML string +const html = CatML.compile('Meow!'); +console.log(html); //

Meow!

+ +// Render directly into a DOM element +CatML.render('Meow!', document.getElementById('app')); +``` + +--- + +## ๐Ÿ“– Feline Dictionary + +### Core Tags + +| CaTML Tag | HTML Tag | Feline Concept / Description | +|---|---|---| +| `` / `` | `` | The whole cat universe | +| `` | `` | Metadata & sensory inputs | +| `` | `` | Identification | +| `<fur>` | `<body>` | The visible body | +| `<box>` / `<cardboard>` | `<div>` | If it fits, I sits (container) | +| `<feather>` / `<mouse>` | `<span>` | Small things to chase | +| `<meow1>` ... `<meow6>` | `<h1>` ... `<h6>` | Headings (from roar to tiny squeak) | +| `<purr>` | `<p>` | Steady purring paragraph | +| `<scratch>` | `<a>` | Hyperlink (scratch and jump) | +| `<stare>` | `<img>` | Image (what the cat stares at) | +| `<bap>` / `<paw>` | `<button>` | Action button (bap items off tables) | +| `<sniff>` | `<input>` | Input field | +| `<yowl>` | `<textarea>` | Multi-line text field | +| `<bowl>` | `<ul>` | Food bowl / list | +| `<numbered-bowl>` | `<ol>` | Ordered list | +| `<treat>` | `<li>` | Single treat / list item | +| `<claw>` | `<strong>` / `<b>` | Sharp emphasis | +| `<hiss>` | `<em>` / `<i>` | Italic hissed tone | +| `<catnip>` | `<script>` | JavaScript (gives zoomies!) | +| `<groom>` | `<style>` | CSS styling | +| `<litterbox>` | `<footer>` | Bottom footer | +| `<nap>` | `<hr>` | Rest / horizontal rule | +| `<door>` | `<form>` | Form container | +| `<curtain>` | `<header>` | Page header | +| `<shelf>` | `<nav>` | Navigation bar | +| `<hammock>` | `<main>` | Main content | +| `<perch>` | `<section>` | Section | + +### Attributes + +| CaTML Attribute | HTML Attribute | Description | +|---|---|---| +| `catch="..."` | `href="..."` | Destination URL to chase | +| `prey="..."` | `src="..."` | Source image URL | +| `scent="..."` | `alt="..."` | Alt text / scent profile | +| `tag="..."` | `id="..."` | ID attribute | +| `breed="..."` | `class="..."` | CSS class attribute | +| `fluff="..."` | `style="..."` | Inline CSS styles | +| `on-bap="..."` | `onclick="..."` | Click event listener | +| `on-sniff="..."` | `oninput="..."` | Input event listener | +| `on-change="..."` | `onchange="..."` | Change event listener | +| `on-stalk="..."` | `onmouseover="..."` | Mouseover event listener | +| `on-pounce="..."` | `onmousedown="..."` | Mousedown event listener | +| `treat-type="..."` | `type="..."` | Input type (`text`, `number`, etc.) | +| `kibble="..."` | `value="..."` | Input value | +| `curiosity="..."` | `placeholder="..."` | Placeholder text | +| `collar-name="..."` | `name="..."` | Name attribute | + +--- + +## ๐ŸŽฎ Interactive Playground + +Open `index.html` in any browser to experience the **CaTML Playground**: +- Real-time syntax compiler +- Split view: Live preview & compiled raw HTML +- Pre-built templates: + - *Welcome to CaTML* + - *Interactive Bap Counter* + - *Kitten Adoption Form* + - *Sir Pounce-a-Lot Portfolio* +- Built-in interactive feline vocabulary modal + +--- + +## ๐Ÿงช Testing + +Run the automated test suite with Node.js: + +```bash +node test/test.js +``` + +--- + +## ๐Ÿ“„ License + +MIT. Built with ๐Ÿพ purrs and catnip. diff --git a/catml.js b/catml.js new file mode 100644 index 0000000..275033d --- /dev/null +++ b/catml.js @@ -0,0 +1,271 @@ +/** + * CaTML (Cat Altered Text Markup Language) + * A feline-powered HTML compiler & runtime interpreter. + * + * Repository: https://git.p20.dev/christian/CaTML.git + */ + +(function (global) { + 'use strict'; + + // Tag translations: CaTML Tag -> HTML Tag + const TAG_MAP = { + 'catworld': 'html', + 'catml': 'html', + 'whiskers': 'head', + 'collar': 'title', + 'fur': 'body', + 'box': 'div', + 'cardboard': 'div', + 'feather': 'span', + 'mouse': 'span', + 'meow1': 'h1', + 'meow2': 'h2', + 'meow3': 'h3', + 'meow4': 'h4', + 'meow5': 'h5', + 'meow6': 'h6', + 'purr': 'p', + 'scratch': 'a', + 'stare': 'img', + 'bap': 'button', + 'paw': 'button', + 'sniff': 'input', + 'yowl': 'textarea', + 'bowl': 'ul', + 'numbered-bowl': 'ol', + 'treat': 'li', + 'claw': 'strong', + 'hiss': 'em', + 'catnip': 'script', + 'groom': 'style', + 'litterbox': 'footer', + 'nap': 'hr', + 'door': 'form', + 'shelf': 'nav', + 'hammock': 'main', + 'tree': 'article', + 'perch': 'section', + 'curtain': 'header' + }; + + // Attribute translations: CaTML Attribute -> HTML Attribute + const ATTR_MAP = { + 'catch': 'href', + 'prey': 'src', + 'scent': 'alt', + 'tag': 'id', + 'breed': 'class', + 'fluff': 'style', + 'on-bap': 'onclick', + 'on-sniff': 'oninput', + 'on-change': 'onchange', + 'on-stalk': 'onmouseover', + 'on-pounce': 'onmousedown', + 'treat-type': 'type', + 'kibble': 'value', + 'curiosity': 'placeholder', + 'collar-name': 'name', + 'target': 'target', + 'width': 'width', + 'height': 'height' + }; + + /** + * Translates a single CaTML tag name to HTML tag name + */ + function translateTag(tag) { + const lower = tag.toLowerCase(); + return TAG_MAP[lower] || tag; + } + + /** + * Translates a single CaTML attribute name to HTML attribute name + */ + function translateAttr(attr) { + const lower = attr.toLowerCase(); + return ATTR_MAP[lower] || attr; + } + + /** + * Translates an attribute string of a tag + * e.g. `breed="tabby" on-bap="alert(1)"` -> `class="tabby" onclick="alert(1)"` + */ + function compileAttributes(attrString) { + if (!attrString) return ''; + + // Regex matching attribute key="value", key='value', or boolean key + const attrRegex = /([a-zA-Z0-9_-]+)(?:=("[^"]*"|'[^']*'|[^\s"'>]+))?/g; + return attrString.replace(attrRegex, (match, attrName, attrVal) => { + const mappedName = translateAttr(attrName); + if (attrVal !== undefined) { + return `${mappedName}=${attrVal}`; + } + return mappedName; + }); + } + + /** + * Compiles CaTML markup into standard HTML string + * Handles opening tags, closing tags, self-closing tags, and comments. + */ + function compile(catmlSource) { + if (typeof catmlSource !== 'string') return ''; + + // Regex for HTML/CaTML tags: + // Captures: + // 1: '/' if closing tag + // 2: tag name + // 3: attributes + // 4: '/' if self-closing + const tagRegex = /<\s*(\/?)\s*([a-zA-Z0-9_-]+)([\s\S]*?)(\/?)\s*>/g; + + return catmlSource.replace(tagRegex, (fullMatch, isClosing, tagName, attrString, isSelfClosing) => { + // Don't modify standard DOCTYPE or XML headers + if (tagName.toLowerCase() === '!doctype') { + return fullMatch; + } + + const mappedTag = translateTag(tagName); + + if (isClosing) { + return `</${mappedTag}>`; + } + + const compiledAttrs = compileAttributes(attrString); + const trailingSlash = isSelfClosing ? ' /' : ''; + return `<${mappedTag}${compiledAttrs ? compiledAttrs : ''}${trailingSlash}>`; + }); + } + + /** + * Renders compiled CaTML directly into a DOM element or document + */ + function render(catmlSource, targetElement) { + const html = compile(catmlSource); + if (!targetElement) { + return html; + } + + if (typeof targetElement === 'string') { + targetElement = document.querySelector(targetElement); + } + + if (targetElement && targetElement.innerHTML !== undefined) { + targetElement.innerHTML = html; + + // Execute any script tags generated from <catnip> or <script> + const scripts = targetElement.querySelectorAll('script'); + scripts.forEach(oldScript => { + const newScript = document.createElement('script'); + Array.from(oldScript.attributes).forEach(attr => { + newScript.setAttribute(attr.name, attr.value); + }); + newScript.textContent = oldScript.textContent; + oldScript.parentNode.replaceChild(newScript, oldScript); + }); + } + + return html; + } + + /** + * In-place DOM Transformer: + * Scans document DOM for CaTML custom tags and converts them into real HTML elements. + */ + function transformDOM(rootNode) { + const root = rootNode || document.body; + if (!root) return; + + // List of custom tags to replace + const catmlTags = Object.keys(TAG_MAP).filter(t => + !['html', 'head', 'title', 'body', 'script', 'style'].includes(TAG_MAP[t]) + ); + + let foundElements = true; + while (foundElements) { + foundElements = false; + for (const catTag of catmlTags) { + const elements = root.getElementsByTagName(catTag); + if (elements.length > 0) { + foundElements = true; + const el = elements[0]; + const newTagName = TAG_MAP[catTag]; + const replacement = document.createElement(newTagName); + + // Copy and translate attributes + Array.from(el.attributes).forEach(attr => { + const mappedName = translateAttr(attr.name); + replacement.setAttribute(mappedName, attr.value); + }); + + // Move child nodes + while (el.firstChild) { + replacement.appendChild(el.firstChild); + } + + el.parentNode.replaceChild(replacement, el); + break; // restart query selector because DOM mutated + } + } + } + } + + /** + * Automatic Bootstrapper: + * Finds all <script type="text/catml"> and renders them or replaces them. + */ + function autoBoot() { + if (typeof document === 'undefined') return; + + // 1. Process <script type="text/catml"> + const catScripts = document.querySelectorAll('script[type="text/catml"]'); + catScripts.forEach(script => { + const targetId = script.getAttribute('target') || script.getAttribute('data-target'); + const compiledHTML = compile(script.textContent); + + if (targetId) { + const target = document.querySelector(targetId); + if (target) { + render(script.textContent, target); + } + } else { + // Replace the script tag itself with a container or direct elements + const container = document.createElement('div'); + container.className = 'catml-container'; + render(script.textContent, container); + script.parentNode.insertBefore(container, script); + } + }); + + // 2. Transform any custom elements in the body + transformDOM(document.body); + } + + // Public API + const CatML = { + compile: compile, + parse: compile, + render: render, + transformDOM: transformDOM, + autoBoot: autoBoot, + TAG_MAP: TAG_MAP, + ATTR_MAP: ATTR_MAP + }; + + // Auto-run when DOM is ready in browser + if (typeof window !== 'undefined') { + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', autoBoot); + } else { + setTimeout(autoBoot, 0); + } + window.CatML = CatML; + } + + // CommonJS / Node export support + if (typeof module !== 'undefined' && module.exports) { + module.exports = CatML; + } + +})(typeof globalThis !== 'undefined' ? globalThis : this); diff --git a/demo.html b/demo.html new file mode 100644 index 0000000..798d1bc --- /dev/null +++ b/demo.html @@ -0,0 +1,67 @@ +<!DOCTYPE html> +<html lang="en"> +<head> + <meta charset="UTF-8"> + <title>CaTML Standalone Demo + + + + + + + + + + + diff --git a/index.html b/index.html new file mode 100644 index 0000000..15b4c70 --- /dev/null +++ b/index.html @@ -0,0 +1,571 @@ + + + + + + CaTML - Cat Altered Text Markup Language Playground + + + + + +
+
+

๐Ÿพ CaTML Playground

+ Cat Altered Text Markup Language +
+
+ + + +
+
+ +
+ +
+
+ CaTML Code Input + Type in CaTML - updates automatically +
+
+ +
+
+ + +
+
+ Output +
+ + +
+
+
+
+

+      
+
+
+ + + + + + + diff --git a/test/test.js b/test/test.js new file mode 100644 index 0000000..8b9ecb9 --- /dev/null +++ b/test/test.js @@ -0,0 +1,75 @@ +const assert = require('assert'); +const CatML = require('../catml.js'); + +console.log('๐Ÿพ Running CaTML Test Suite...\n'); + +// Test 1: Tag Translations +console.log('Test 1: Basic tag translations'); +const source1 = 'Hello world'; +const expected1 = '

Hello world

'; +assert.strictEqual(CatML.compile(source1), expected1); +console.log('โœ“ Passed: Basic tags'); + +// Test 2: Headings and lists +console.log('Test 2: Headings and food bowls'); +const source2 = 'Grand MeowTunaSalmon'; +const expected2 = '

Grand Meow

  • Tuna
  • Salmon
'; +assert.strictEqual(CatML.compile(source2), expected2); +console.log('โœ“ Passed: Headings and lists'); + +// Test 3: Attributes (catch, prey, scent, breed, tag, fluff) +console.log('Test 3: Attribute translation'); +const source3 = ''; +const compiled3 = CatML.compile(source3); +assert(compiled3.includes('Catch!')); +assert(compiled4.includes(', , , , ) +console.log('Test 5: Full document translation'); +const source5 = ` + + + My Cat Page + + + Header + + Content + + Footer + + +`.trim(); + +const compiled5 = CatML.compile(source5); +assert(compiled5.includes('')); +assert(compiled5.includes('')); +assert(compiled5.includes('My Cat Page')); +assert(compiled5.includes('')); +assert(compiled5.includes('

Header

')); +assert(compiled5.includes('
')); +assert(compiled5.includes('

Footer

')); +assert(compiled5.includes('')); +console.log('โœ“ Passed: Full document structure'); + +// Test 6: Script / Style elements (, ) +console.log('Test 6: Script & styles'); +const source6 = 'box { color: orange; }console.log("purr");'; +const compiled6 = CatML.compile(source6); +assert.strictEqual(compiled6, ''); +console.log('โœ“ Passed: Styles and scripts'); + +console.log('\n๐ŸŽ‰ ALL CaTML TESTS PASSED! Purrfection achieved! ๐Ÿฑ');