import json import re from dataclasses import dataclass from html.parser import HTMLParser from pathlib import Path from urllib.parse import unquote_plus from exclude import EXCLUDED_WORDS # Einstellungen SOURCE_DIR = Path('data') SOURCE_FILENAME = 'index.txt' OUTPUT_FILE = 'tags.json' TAGS_PER_ARTICLE = 5 JSON_INDENT = 2 EXCLUDED_HTML_TAGS = {'code'} # Wegen Performance vordefinierte Variablen _UPPER_CHECK = re.compile(r'[A-Z]') @dataclass class Tag: name: str score: int class FileScanner(HTMLParser): def __init__(self, file: Path): super().__init__() self.file = file self.texte = [] self._current_html_tag = None def scan_file(self): # Datei einlesen content = read_file(self.file) # HTMLParser aufrufen um HTML-Syntax-Elemente zu entfernen. self.feed(content) words_with_usage = {} words = [] for text in self.texte: # Eventuelle URL-codierte Zeichen in die eigentliche Zeichen umwandeln. (z.B. %2F -> /) text = unquote_plus(text) # Textteile in einzelne Wörter aufteilen words += re.split(r'[ /\-_#\n.?=]', text) # Die Anzahl, der Wörter in der aktuellen Datei, auf der Konsole ausgeben title = self.file.parent.name print(f'\nFile {title} contains {len(words)} words') # Titel in einzelne Wörter aufteilen title_words = set(title.split('-')) for word in words: # Verschiedene Zeichen vom Anfang und Ende der Wörter entfernen. tag_name = word.strip(".,:;!\"'<>()") # Leere Wörter ignorieren if not tag_name: continue # Alle Buchstaben verkleinern, aber gleichzeitig originales Wort merken word = tag_name.lower() # Standard Bewertung für jedes Wort ist 10 score = 10 # Wörter, die in der Liste der ausgeschlossenen Wörter stehen, ignorieren if word in EXCLUDED_WORDS: continue # Wörter, die nur aus Zahlen bestehen, ignorieren if word.isdigit(): continue # Die Bewertung von Wörtern, die im Titel vorkommen, deutlich verbessern. if word in title_words: score *= 4 # Die Bewertung von Wörtern, die kürzer oder gleich lang sind als 3 Buchstaben, # entsprechend der Länge des Wortes verringern. word_length = len(word) if word_length <= 3: score = int(score * word_length / 4) # Die Anzahl der Großbuchstaben in dem originalen Wort zählen ... upper_letters_count = len(_UPPER_CHECK.findall(tag_name)) # ... und die Bewertung entsprechen der Anzahl verbessern. score += upper_letters_count * 5 # Die Bewertung für das Wort speichern. # Wenn das Wort bereits eine Bewertung besitzt werden die beiden Bewertungen zusammen gerechnet. if word not in words_with_usage: words_with_usage[word] = Tag(name=tag_name, score=score) else: words_with_usage[word].score += score # Die Wörter nach ihrer Bewertung sortieren return sorted(words_with_usage.values(), key=lambda tag: tag.score, reverse=True) def handle_starttag(self, tag, attrs): self._current_html_tag = tag # Die Links, die in den 'href' Attributen eines HTML-Elements stehen, mit einbeziehen. if tag != "a": return for attr_name, attr_value in attrs: if attr_name == "href": self.texte.append(attr_value) break def handle_data(self, data): if self._current_html_tag not in EXCLUDED_HTML_TAGS: # Den Text innerhalb eines HTML-Elements mit einbeziehen. self.texte.append(data) def display_tags(tags, min_score): # Die Ergebnisse auf der Konsole ausgeben. for tag in tags: if tag.score <= min_score: continue print(f"Score: {tag.score:>3} Word: {tag.name}") class CustomJsonEncoder(json.JSONEncoder): def default(self, obj): if isinstance(obj, Tag): return obj.name return super().default(obj) def write_tags(tags): # Die Ergebnisse in JSON umwandeln. content = json.dumps(tags, indent=JSON_INDENT, cls=CustomJsonEncoder, ensure_ascii=False) # Das JSON in eine Datei schreiben. with open(OUTPUT_FILE, 'w') as file: file.write(content) def read_file(file: Path) -> str: # Eine Datei einlesen with open(file, 'r') as file: return file.read() def main(): final_tags = {} # Nach allen Quelldateien suchen for file in SOURCE_DIR.glob(f'**/{SOURCE_FILENAME}'): # Die Dateien, deren Ordner mit 'autosave-' beginnen, ignorieren. title = file.parent.name if title.startswith('autosave-'): continue # Die Datei analysieren scanner = FileScanner(file) tags = scanner.scan_file() # Die Ergebnisse auf der Konsole ausgeben display_tags(tags, min_score=20) # Die eingestellte Anzahl an Tags für die Ausgabedatei übernehmen, sofern vorhanden. final_tags[title] = tags[:TAGS_PER_ARTICLE] if len(tags) > TAGS_PER_ARTICLE else tags # Die Ausgabedatei schreiben write_tags(final_tags) if __name__ == '__main__': main()