Skip to content

Commit 284528c

Browse files
committed
auto legend generator
0 parents  commit 284528c

File tree

1 file changed

+214
-0
lines changed

1 file changed

+214
-0
lines changed

Diff for: index.html

+214
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,214 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<meta charset="UTF-8">
5+
<title>MapCSS Color Legend Generator</title>
6+
<link href="https://fonts.googleapis.com/css2?family=Montserrat:wght@400;700&display=swap" rel="stylesheet">
7+
<style>
8+
body {
9+
font-family: 'Montserrat', sans-serif;
10+
margin: 0;
11+
padding: 0;
12+
background-color: #f4f4f4;
13+
}
14+
.container {
15+
max-width: 1200px;
16+
margin: 0 auto;
17+
padding: 20px;
18+
}
19+
h1 {
20+
text-align: center;
21+
color: #333;
22+
margin-bottom: 20px;
23+
}
24+
table {
25+
border-collapse: collapse;
26+
width: 100%;
27+
margin: 10px auto;
28+
background: #fff;
29+
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
30+
}
31+
colgroup col:first-child {
32+
width: 40%;
33+
}
34+
colgroup col:last-child {
35+
width: 60%;
36+
}
37+
th, td {
38+
border: 1px solid #ddd;
39+
padding: 12px 15px;
40+
text-align: left;
41+
overflow-wrap: break-word;
42+
}
43+
th {
44+
background-color: #d11e54;
45+
color: #fff;
46+
font-weight: 600;
47+
}
48+
tr:nth-child(even) {
49+
background-color: #f9f9f9;
50+
}
51+
.color-swatch {
52+
display: inline-block;
53+
width: 1.5em;
54+
height: 1.5em;
55+
vertical-align: middle;
56+
margin-right: 0.5em;
57+
border: 1px solid #333;
58+
}
59+
pre {
60+
background: #f8f8f8;
61+
padding: 0.5em;
62+
margin: 0;
63+
font-family: inherit;
64+
}
65+
.meta-section {
66+
width: 100%;
67+
margin: 20px auto;
68+
padding: 20px;
69+
border: 1px dashed #aaa;
70+
background: #fff;
71+
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
72+
}
73+
.meta-section h2 {
74+
margin-top: 0;
75+
color: #333;
76+
}
77+
#output {
78+
margin-top: 20px;
79+
text-align: left;
80+
}
81+
</style>
82+
</head>
83+
<body>
84+
<div class="container">
85+
<h1>MapCSS Color Legend</h1>
86+
<div id="output">Loading legend...</div>
87+
</div>
88+
<script>
89+
const mapcssUrl = "https://raw.githubusercontent.com/open-energy-transition/grid-mapping-starter-kit/refs/heads/main/josm-config/transmission_grid_mapping_style.mapcss";
90+
function splitSelectorsRobust(selectorsRaw) {
91+
const selectors = [];
92+
let current = '';
93+
let bracketDepth = 0, parenDepth = 0, braceDepth = 0;
94+
let inSingleQuote = false, inDoubleQuote = false, escape = false;
95+
for (let i = 0; i < selectorsRaw.length; i++) {
96+
const char = selectorsRaw[i];
97+
if (escape) { current += char; escape = false; continue; }
98+
if (char === '\\') { escape = true; current += char; continue; }
99+
if (char === "'" && !inDoubleQuote) { inSingleQuote = !inSingleQuote; current += char; continue; }
100+
if (char === '"' && !inSingleQuote) { inDoubleQuote = !inDoubleQuote; current += char; continue; }
101+
if (inSingleQuote || inDoubleQuote) { current += char; continue; }
102+
if (char === '[') { bracketDepth++; }
103+
else if (char === ']') { bracketDepth--; }
104+
else if (char === '(') { parenDepth++; }
105+
else if (char === ')') { parenDepth--; }
106+
else if (char === '{') { braceDepth++; }
107+
else if (char === '}') { braceDepth--; }
108+
if (char === ',' && bracketDepth === 0 && parenDepth === 0 && braceDepth === 0) {
109+
const selectorStr = current.trim();
110+
if (selectorStr) selectors.push(selectorStr);
111+
current = '';
112+
} else { current += char; }
113+
}
114+
const lastSelector = current.trim();
115+
if (lastSelector) selectors.push(lastSelector);
116+
return selectors;
117+
}
118+
function parseMapCSSContent(content) {
119+
const ruleRegex = /([^{}]+)\s*\{\s*([^}]*)\s*\}/g;
120+
const rules = [];
121+
let match;
122+
while ((match = ruleRegex.exec(content)) !== null) {
123+
const selectorsRaw = match[1].trim();
124+
const stylesRaw = match[2].trim();
125+
const selectors = splitSelectorsRobust(selectorsRaw);
126+
const propRegex = /([a-zA-Z0-9_\-\_]+)\s*:\s*([^;]+)\s*;/g;
127+
const properties = {};
128+
let propMatch;
129+
while ((propMatch = propRegex.exec(stylesRaw)) !== null) {
130+
const key = propMatch[1].trim();
131+
const value = propMatch[2].trim();
132+
properties[key] = value;
133+
}
134+
rules.push({ selectors, properties });
135+
}
136+
return rules;
137+
}
138+
function separateMetaRules(rules) {
139+
const metaRules = [];
140+
const normalRules = [];
141+
rules.forEach(rule => {
142+
if (rule.selectors.some(s => s.trim().toLowerCase() === "meta"))
143+
metaRules.push(rule);
144+
else normalRules.push(rule);
145+
});
146+
return { metaRules, normalRules };
147+
}
148+
function buildMetaSection(metaRules) {
149+
if (metaRules.length === 0) return "";
150+
const combinedProps = {};
151+
metaRules.forEach(rule => {
152+
for (const key in rule.properties) {
153+
combinedProps[key] = rule.properties[key];
154+
}
155+
});
156+
let metaHtml = Object.keys(combinedProps).length > 0
157+
? "<ul>" + Object.entries(combinedProps).map(([key, value]) => `<li><strong>${key}:</strong> ${value}</li>`).join("") + "</ul>"
158+
: "<p><em>No metadata properties</em></p>";
159+
return `<div class="meta-section">
160+
<h2>Metadata</h2>
161+
${metaHtml}
162+
</div>`;
163+
}
164+
function generateLegendHTML(rules) {
165+
const { metaRules, normalRules } = separateMetaRules(rules);
166+
let rowsHtml = "";
167+
normalRules.forEach(rule => {
168+
const selectorsStr = rule.selectors.join("<br>");
169+
const colorProps = {};
170+
for (const key in rule.properties) {
171+
if (key.toLowerCase().includes("color"))
172+
colorProps[key] = rule.properties[key];
173+
}
174+
let colorPropsHtml = Object.keys(colorProps).length === 0
175+
? "<em>No color properties</em>"
176+
: Object.entries(colorProps).map(([key, value]) => {
177+
const colorSwatch = `<span class="color-swatch" style="background:${value};"></span>`;
178+
return `${colorSwatch} <strong>${key}</strong>: ${value}`;
179+
}).join("<br>");
180+
rowsHtml += `<tr>
181+
<td><pre>${selectorsStr}</pre></td>
182+
<td>${colorPropsHtml}</td>
183+
</tr>`;
184+
});
185+
const tableHtml = `<table>
186+
<colgroup>
187+
<col style="width: 40%;">
188+
<col style="width: 60%;">
189+
</colgroup>
190+
<tr>
191+
<th>Selectors</th>
192+
<th>Color Properties</th>
193+
</tr>
194+
${rowsHtml}
195+
</table>`;
196+
const metaSectionHtml = buildMetaSection(metaRules);
197+
return `${metaSectionHtml}${tableHtml}`;
198+
}
199+
async function generateLegend() {
200+
try {
201+
const response = await fetch(mapcssUrl);
202+
if (!response.ok) throw new Error("Network response was not ok");
203+
const content = await response.text();
204+
const rules = parseMapCSSContent(content);
205+
const legendHTML = generateLegendHTML(rules);
206+
document.getElementById("output").innerHTML = legendHTML;
207+
} catch (error) {
208+
document.getElementById("output").innerHTML = `<p>Error fetching or parsing MapCSS: ${error}</p>`;
209+
}
210+
}
211+
window.addEventListener("load", generateLegend);
212+
</script>
213+
</body>
214+
</html>

0 commit comments

Comments
 (0)