1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
|
<!DOCTYPE html>
<html>
<head>
<title>Lrama syntax diagrams</title>
<style>
<%= output.default_style %>
.diagram-header {
display: inline-block;
font-weight: bold;
font-size: 18px;
margin-bottom: -8px;
text-align: center;
}
svg {
width: 100%;
}
svg.railroad-diagram g.non-terminal text {
cursor: pointer;
}
h2.hover-header {
background-color: #90ee90;
}
svg.railroad-diagram g.non-terminal.hover-g rect {
fill: #eded91;
stroke: 5;
}
svg.railroad-diagram g.terminal.hover-g rect {
fill: #eded91;
stroke: 5;
}
</style>
</head>
<body align="center">
<%= output.diagrams %>
<script>
document.addEventListener("DOMContentLoaded", () => {
function addHoverEffect(selector, hoverClass, relatedSelector, relatedHoverClass, getTextElements) {
document.querySelectorAll(selector).forEach(element => {
element.addEventListener("mouseenter", () => {
element.classList.add(hoverClass);
getTextElements(element).forEach(textEl => {
if (!relatedSelector) return;
getElementsByText(relatedSelector, textEl.textContent).forEach(related => {
related.classList.add(relatedHoverClass);
});
});
});
element.addEventListener("mouseleave", () => {
element.classList.remove(hoverClass);
if (!relatedSelector) return;
getTextElements(element).forEach(textEl => {
getElementsByText(relatedSelector, textEl.textContent).forEach(related => {
related.classList.remove(relatedHoverClass);
});
});
});
});
}
function getElementsByText(selector, text) {
return [...document.querySelectorAll(selector)].filter(el => el.textContent.trim() === text.trim());
}
function getParentElementsByText(selector, text) {
return [...document.querySelectorAll(selector)].filter(el =>
[...el.querySelectorAll("text")].some(textEl => textEl.textContent.trim() === text.trim())
);
}
function scrollToMatchingHeader() {
document.querySelectorAll("g.non-terminal").forEach(element => {
element.addEventListener("click", () => {
const textElements = [...element.querySelectorAll("text")];
for (const textEl of textElements) {
const targetHeader = getElementsByText("h2", textEl.textContent)[0];
if (targetHeader) {
targetHeader.scrollIntoView({ behavior: "smooth", block: "start" });
break;
}
}
});
});
}
addHoverEffect("h2", "hover-header", "g.non-terminal", "hover-g", element => [element]);
addHoverEffect("g.non-terminal", "hover-g", "h2", "hover-header",
element => [...element.querySelectorAll("text")]
);
addHoverEffect("g.terminal", "hover-g", "", "", element => [element]);
scrollToMatchingHeader();
});
</script>
</body>
</html>
|