mirror of
https://github.com/jakobkordez/call-tester.git
synced 2025-05-16 00:30:27 +00:00
Smaller changes
This commit is contained in:
parent
0f06cb8b83
commit
e968b4d344
@ -1,208 +0,0 @@
|
||||
import xml.etree.ElementTree as ET
|
||||
from sys import argv
|
||||
from collections import defaultdict
|
||||
import re
|
||||
|
||||
root = ET.parse(argv[1]).getroot()
|
||||
|
||||
entities = {}
|
||||
prefixes = []
|
||||
|
||||
|
||||
def capitalize(s: str):
|
||||
s = s.lower()
|
||||
for m in re.findall(r"\b\w+", s):
|
||||
s = s.replace(m, m.capitalize(), 1)
|
||||
return s
|
||||
|
||||
|
||||
for child in root:
|
||||
if child.tag == "entities":
|
||||
for entity in child:
|
||||
name = adif = None
|
||||
for prop in entity:
|
||||
if prop.tag == "name":
|
||||
name = prop.text
|
||||
if prop.tag == "adif":
|
||||
adif = int(prop.text)
|
||||
entities[adif] = capitalize(name)
|
||||
if child.tag == "prefixes":
|
||||
for prefix in child:
|
||||
call = entity = end = None
|
||||
for prop in prefix:
|
||||
if prop.tag == "call":
|
||||
call = prop.text
|
||||
if prop.tag == "adif":
|
||||
entity = int(prop.text)
|
||||
if prop.tag == "end":
|
||||
end = prop.text
|
||||
if end and end < "2024-06-20":
|
||||
continue
|
||||
# if entity != 1:
|
||||
# continue
|
||||
prefixes.append((call, entity))
|
||||
|
||||
# print(len(prefixes), "prefixes found")
|
||||
|
||||
# Build trie
|
||||
|
||||
|
||||
class Node:
|
||||
counter = 0
|
||||
|
||||
def __init__(self):
|
||||
Node.counter += 1
|
||||
self.id = Node.counter
|
||||
self.parent: Node = None
|
||||
self.children: dict[str, Node] = {}
|
||||
self.entity: int = None
|
||||
|
||||
|
||||
root = Node()
|
||||
allNodes = []
|
||||
|
||||
|
||||
# Build trie
|
||||
def insert(node: Node, prefix, entity):
|
||||
if not prefix:
|
||||
if node.entity and node.entity != entity:
|
||||
print(f"Conflict: {node.entity} vs {entity}")
|
||||
node.entity = entity
|
||||
return
|
||||
nextNode = node.children.get(prefix[0])
|
||||
if not nextNode:
|
||||
nextNode = Node()
|
||||
nextNode.parent = node
|
||||
node.children[prefix[0]] = nextNode
|
||||
allNodes.append(nextNode)
|
||||
insert(nextNode, prefix[1:], entity)
|
||||
|
||||
|
||||
for call, entity in prefixes:
|
||||
insert(root, call, entity)
|
||||
|
||||
|
||||
# Merge nodes
|
||||
# print("Merge start,", len(allNodes), "nodes")
|
||||
|
||||
|
||||
def canMerge(node: Node, other: Node):
|
||||
if node.entity != other.entity:
|
||||
return False
|
||||
l = set(node.children.keys()).union(other.children.keys())
|
||||
return all(node.children.get(k) == other.children.get(k) for k in l)
|
||||
|
||||
|
||||
def merge(first: Node, second: Node):
|
||||
# if first.id > second.id:
|
||||
# first, second = second, first
|
||||
for k, c in second.children.items():
|
||||
c.parent = first
|
||||
for k, c in second.parent.children.items():
|
||||
if c == second:
|
||||
second.parent.children[k] = first
|
||||
allNodes.remove(second)
|
||||
|
||||
|
||||
anyChanged = True
|
||||
while anyChanged:
|
||||
anyChanged = False
|
||||
i = 0
|
||||
while i < len(allNodes):
|
||||
node = allNodes[i]
|
||||
for other in allNodes:
|
||||
if node != other and canMerge(node, other):
|
||||
anyChanged = True
|
||||
merge(node, other)
|
||||
break
|
||||
else:
|
||||
i += 1
|
||||
|
||||
for i in range(len(allNodes)):
|
||||
for j in range(i + 1, len(allNodes)):
|
||||
if canMerge(allNodes[i], allNodes[j]):
|
||||
raise Exception("Merge not completed")
|
||||
|
||||
# for n in allNodes:
|
||||
# print(n.children.items())
|
||||
# print("Merge done,", len(allNodes), "nodes left")
|
||||
|
||||
# Save compiled trie
|
||||
|
||||
for node in [root, *allNodes]:
|
||||
if node.entity:
|
||||
print(f"{node.id}={node.entity}")
|
||||
for c in set(node.children.values()):
|
||||
print(
|
||||
f"{node.id}-{''.join(sorted(k for k, v in node.children.items() if v == c))}-{c.id}"
|
||||
)
|
||||
|
||||
# Print trie
|
||||
|
||||
|
||||
def rangeToStr(a: str, b: str):
|
||||
if a == b:
|
||||
return a
|
||||
if ord(b) - ord(a) > 1:
|
||||
return f"{a}-{b}"
|
||||
return f"{a}{b}"
|
||||
|
||||
|
||||
def toRange(s: list[str]):
|
||||
s = sorted(s)
|
||||
ranges = [(s[0], s[0])]
|
||||
for c in s[1:]:
|
||||
if ord(c) == ord(ranges[-1][1]) + 1:
|
||||
ranges[-1] = (ranges[-1][0], c)
|
||||
else:
|
||||
ranges.append((c, c))
|
||||
return "".join(rangeToStr(a, b) for a, b in ranges)
|
||||
|
||||
|
||||
defined = set()
|
||||
|
||||
|
||||
def genGraphvizNode(node: Node):
|
||||
label = "" if not node.entity else entities[node.entity]
|
||||
shape = "circle" if not node.entity else "box"
|
||||
return f' {node.id} [label="{label}" shape="{shape}"];'
|
||||
|
||||
|
||||
def toGraphviz(node: Node, root=True):
|
||||
ret = []
|
||||
if node in defined:
|
||||
return ret
|
||||
defined.add(node)
|
||||
|
||||
ret.append(genGraphvizNode(node))
|
||||
|
||||
dd = defaultdict(list)
|
||||
for k, c in node.children.items():
|
||||
dd[c.id].append(k.replace("/", "_"))
|
||||
ret += toGraphviz(c, False)
|
||||
|
||||
for k in sorted(dd.keys()):
|
||||
ret.append(f' {node.id} -> {k} [label="{toRange(dd[k])}"];')
|
||||
|
||||
if root:
|
||||
curr = node
|
||||
while curr.parent:
|
||||
ret.append(genGraphvizNode(curr.parent))
|
||||
label = toRange(k for k, v in curr.parent.children.items() if v == curr)
|
||||
ret.append(f' {curr.parent.id} -> {curr.id} [label="{label}"];')
|
||||
curr = curr.parent
|
||||
ret = "\n".join(["digraph {", *ret, "}"])
|
||||
|
||||
return ret
|
||||
|
||||
|
||||
def traverse(s: str, node: Node = root):
|
||||
if not s:
|
||||
return node
|
||||
nextNode = node.children.get(s[0])
|
||||
if not nextNode:
|
||||
return None
|
||||
return traverse(s[1:], nextNode)
|
||||
|
||||
|
||||
# print(toGraphviz(traverse("R")))
|
@ -12,6 +12,9 @@ export class TrieNode {
|
||||
else this.id = ++nodeCounter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert a prefix into the trie.
|
||||
*/
|
||||
insert(prefix: string, entity: number): void {
|
||||
if (!prefix) {
|
||||
if (this.entity && this.entity !== entity)
|
||||
@ -27,12 +30,18 @@ export class TrieNode {
|
||||
next.insert(prefix.slice(1), entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Traverse the trie to find the node that matches the prefix.
|
||||
*/
|
||||
findRaw(prefix: string): TrieNode | null {
|
||||
if (!prefix) return this;
|
||||
const next = this.children.get(prefix[0]);
|
||||
return next ? next.findRaw(prefix.slice(1)) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all the nodes in the trie.
|
||||
*/
|
||||
getAllNodes(): Set<TrieNode> {
|
||||
const nodes: TrieNode[] = [this];
|
||||
for (const child of this.children.values()) {
|
||||
@ -41,6 +50,9 @@ export class TrieNode {
|
||||
return new Set(nodes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if this node can be merged with another node.
|
||||
*/
|
||||
canMerge(other: TrieNode): boolean {
|
||||
if (this === other) return false;
|
||||
if (this.entity !== other.entity) return false;
|
||||
@ -54,6 +66,9 @@ export class TrieNode {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an encoded string of the whole trie.
|
||||
*/
|
||||
encodeToString(): string {
|
||||
return [...this.getAllNodes()].map((n) => n._encodeToString()).join('\n');
|
||||
}
|
||||
@ -74,6 +89,9 @@ export class TrieNode {
|
||||
return s.join('\n');
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes a trie from an encoded string.
|
||||
*/
|
||||
static decodeFromString(s: string): TrieNode {
|
||||
let root: TrieNode | null = null;
|
||||
const nodes: Map<number, TrieNode> = new Map();
|
||||
|
@ -1,29 +1,33 @@
|
||||
<script lang="ts">
|
||||
import { getSecondarySuffixDescription, parseCallsign } from '$lib/callsign';
|
||||
import { dxccEntities } from '$lib/dxcc-util';
|
||||
import { dxccEntities, findDxcc } from '$lib/dxcc-util';
|
||||
import CallsignInput from '../components/callsign-input.svelte';
|
||||
|
||||
const baseClass = 'text-red-400';
|
||||
const prefixClass = 'text-blue-400';
|
||||
const suffixClass = 'text-green-400';
|
||||
|
||||
let callsign = '9a/s52kj/p';
|
||||
|
||||
$: callsignData = parseCallsign(callsign);
|
||||
$: rawDxcc = findDxcc(callsign);
|
||||
|
||||
$: suffixPartOf = [null, 'base', 'prefix'].indexOf(callsignData?.suffixPartOf ?? null);
|
||||
|
||||
function styleText(): string {
|
||||
if (!callsignData) return callsign;
|
||||
const baseClass = 'text-red-400';
|
||||
const prefixClass = 'text-blue-400';
|
||||
|
||||
if (!callsignData) {
|
||||
const dxcc = findDxcc(callsign);
|
||||
if (!dxcc) return callsign;
|
||||
return `<span class="${baseClass}">${callsign.slice(0, dxcc.prefixLength)}</span>${callsign.slice(dxcc.prefixLength)}`;
|
||||
}
|
||||
|
||||
const { base, basePrefix, baseSuffix, secondaryPrefix, secondarySuffix } = callsignData;
|
||||
|
||||
// TODO Check if base and prefix same dxcc
|
||||
const suffClass = [suffixClass, baseClass, prefixClass][suffixPartOf];
|
||||
const suffixClass = ['text-green-400', baseClass, prefixClass][suffixPartOf];
|
||||
return [
|
||||
secondaryPrefix ? `<span class="${prefixClass}">${secondaryPrefix}/</span>` : '',
|
||||
basePrefix ? `<span class="${baseClass}">${basePrefix}</span>${baseSuffix}` : base,
|
||||
secondarySuffix ? `<span class="${suffClass}">/${secondarySuffix}</span>` : ''
|
||||
secondarySuffix ? `<span class="${suffixClass}">/${secondarySuffix}</span>` : ''
|
||||
].join('');
|
||||
}
|
||||
</script>
|
||||
@ -32,7 +36,7 @@
|
||||
<h1 class="text-center text-3xl font-medium">Callsign Tester</h1>
|
||||
|
||||
<div>
|
||||
<div class="text-center">Enter a callsign</div>
|
||||
<div class="mb-1 text-center">Enter a callsign</div>
|
||||
<CallsignInput bind:inputText={callsign} generateStyledText={styleText} />
|
||||
</div>
|
||||
|
||||
@ -40,40 +44,49 @@
|
||||
<div class="flex flex-col gap-4 md:flex-row">
|
||||
{#if callsignData.prefixDxcc}
|
||||
<div class="data-box prefix">
|
||||
<h2 class="text-xl">Secondary prefix</h2>
|
||||
<h2>Secondary prefix</h2>
|
||||
<div class="font-mono text-2xl font-medium">{callsignData.secondaryPrefix}</div>
|
||||
<div>{dxccEntities.get(callsignData.prefixDxcc)?.name}</div>
|
||||
<div>{dxccEntities.get(callsignData.prefixDxcc)?.name ?? '?'}</div>
|
||||
</div>
|
||||
{/if}
|
||||
{#if callsignData.baseDxcc}
|
||||
<div class="data-box base">
|
||||
<h2 class="text-xl">Prefix</h2>
|
||||
<h2>Prefix</h2>
|
||||
<div class="font-mono text-2xl font-medium">{callsignData.basePrefix}</div>
|
||||
<div>{dxccEntities.get(callsignData.baseDxcc)?.name}</div>
|
||||
<div>{dxccEntities.get(callsignData.baseDxcc)?.name ?? '?'}</div>
|
||||
</div>
|
||||
{/if}
|
||||
{#if callsignData.secondarySuffix}
|
||||
<div class={`data-box ${['suffix', 'base', 'prefix'][suffixPartOf]}`}>
|
||||
<h2 class="text-xl">Secondary suffix</h2>
|
||||
<h2>Secondary suffix</h2>
|
||||
<div class="font-mono text-2xl font-medium">{callsignData.secondarySuffix}</div>
|
||||
<div>{getSecondarySuffixDescription(callsignData) ?? ''}</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{:else if rawDxcc}
|
||||
<div class="data-box base">
|
||||
<h2>Prefix</h2>
|
||||
<div class="font-mono text-2xl font-medium">{callsign.slice(0, rawDxcc.prefixLength)}</div>
|
||||
<div>{dxccEntities.get(rawDxcc.entity)?.name ?? '?'}</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style lang="postcss">
|
||||
.data-box {
|
||||
@apply flex-1 rounded-xl p-4;
|
||||
@apply flex-1 rounded-xl p-4 text-center;
|
||||
}
|
||||
.data-box > h2 {
|
||||
@apply text-sm;
|
||||
}
|
||||
.data-box.prefix {
|
||||
@apply bg-blue-700/40;
|
||||
@apply bg-blue-600/40;
|
||||
}
|
||||
.data-box.base {
|
||||
@apply bg-red-700/40;
|
||||
@apply bg-red-600/40;
|
||||
}
|
||||
.data-box.suffix {
|
||||
@apply bg-green-700/40;
|
||||
@apply bg-green-600/40;
|
||||
}
|
||||
</style>
|
||||
|
Loading…
x
Reference in New Issue
Block a user