Files
context-continuity/venv.old-py38/lib/python3.9/site-packages/trilium_py/utils/html_util.py
T
Master 943acbc573 feat: referentiel projets dynamique + validation nommage by design
Durcit la convention de nommage des projets (dérive constatée : 'Sliding
Automation', 'code_versioning'... au lieu des formes canoniques).

- trilium_api.py : projets_canoniques() lit le référentiel = valeurs du label
  projet sur les notes de type=projet (source unique, pas de constante en dur).
  Note-projet CodeVersioning créée (manquait).
- mcp_server.py : _valider_projet() branché dans les 6 tools de création
  (add_decision/history/backlog, new_conversation, create_entite, add_skill).
  Refuse un projet non canonique (suggestion si faute) ou inconnu (renvoi au
  processus de création de projet). Ne verrouille pas si référentiel illisible.
- lint_audit.py : VAL-nommage aligné sur le référentiel (attrape casse, espace
  ET snake_case ; l'ancien 'contient un espace' ratait code_versioning).
- Données : 79 notes ré-étiquetées vers les 3 formes canoniques.

Quality by design : l'erreur de nommage devient impossible à l'écriture, le
Lint n'est plus que le filet de sécurité.
2026-07-17 14:59:33 +02:00

157 lines
5.3 KiB
Python

import locale
import re
import warnings
from bs4 import BeautifulSoup, MarkupResemblesLocatorWarning
# Disable MarkupResemblesLocatorWarning globally
warnings.filterwarnings('ignore', category=MarkupResemblesLocatorWarning)
TAG_LEVELS = {'h1': 1, 'h2': 2, 'h3': 3, 'h4': 4, 'h5': 5, 'h6': 6}
def sort_h_tags_with_hierarchy(data, locale_str='zh_CN.UTF-8'):
"""
sort HTML content based on the names of its headings (h1, h2, h3, etc.) in hierarchical order
following the rules of the input language (specified by locale_str)
:param data:
:param locale_str:
:return:
"""
def add_node(parent, node):
if 'children' not in parent:
parent['children'] = []
parent['children'].append(node)
def sort_node_by_name(node, locale_str):
# sort with respect to local language
locale.setlocale(locale.LC_COLLATE, locale_str)
if 'children' in node:
node['children'] = sorted(node['children'], key=lambda x: locale.strxfrm(x['name']))
for child in node['children']:
sort_node_by_name(child, locale_str)
def dfs_concat_data(node):
result = ""
if 'data' in node:
result += node['data']
if 'children' in node:
for child in node['children']:
result += dfs_concat_data(child)
return result
def convert_to_tree(data):
root = {'name': 'root', 'children': []}
current_tags = {1: root}
for item in data:
soup = BeautifulSoup(item, 'html.parser')
tag_name = soup.find(re.compile(r'^h[1-6]$'))
if not tag_name:
continue
tag_level = TAG_LEVELS[tag_name.name]
node = {'name': tag_name.text, 'data': item, 'children': []}
parent_tag_level = tag_level - 1
parent = current_tags.get(parent_tag_level)
if not parent:
# If parent doesn't exist, add the node to the root
add_node(root, node)
else:
add_node(parent, node)
# Update current_tags with the new node
current_tags[tag_level] = node
return root
# Convert data to tree
tree = convert_to_tree(data)
# Sort nodes by name
sort_node_by_name(tree, locale_str)
# Concatenate data in a depth-first search manner
html_string = dfs_concat_data(tree)
return html_string
def add_internal_links(
html_content, keyword_note_id_list, current_note_id=None, exclude_headings=True
):
"""
Adds internal links to the HTML content by replacing keywords with anchor tags.
Args:
html_content (str): The HTML content to process.
keyword_note_id_list (list of tuples): List of (keyword, note_id).
exclude_headings (bool): Whether to exclude heading tags from processing.
current_note_id (str): The ID of the current note to prevent self-referencing.
Returns:
tuple: A tuple containing the updated HTML content and a boolean indicating if replacements were made.
"""
# Use BeautifulSoup to parse the HTML content
soup = BeautifulSoup(html_content, "html.parser")
replaced = False # Flag to check if any replacement happens
# Precompile the keywords and links into a dictionary, excluding self-referencing notes
keyword_to_link = {
keyword: f'<a class="reference-link" href="#root/{note_id}">{keyword}</a>'
for keyword, note_id in keyword_note_id_list
if note_id != current_note_id # Exclude the current note's ID
}
# Create a regex pattern to match any keyword
if not keyword_to_link:
return str(soup), replaced # No keywords to process
keyword_pattern = re.compile(
r'\b(' + '|'.join(re.escape(k) for k in keyword_to_link.keys()) + r')\b'
)
# Tags to exclude from replacement
exclude_tags = ['a']
if exclude_headings:
exclude_tags.extend(['h2', 'h3', 'h4', 'h5', 'h6'])
# Traverse all text nodes once
for text_node in soup.find_all(string=True):
# Skip nodes inside tags that shouldn't contain links
if text_node.parent.name in exclude_tags:
continue
# Replace keywords in the text
def replace_keyword(match):
replaced_keyword = match.group(0)
return keyword_to_link[replaced_keyword]
new_text = keyword_pattern.sub(replace_keyword, text_node)
if new_text != text_node: # If the text has actually changed
text_node.replace_with(BeautifulSoup(new_text, "html.parser"))
replaced = True # Mark that replacement has occurred
return str(soup), replaced
if __name__ == '__main__':
# Example input HTML content
html_content = """
<p> Only root can see this. <a href="#root/python_programming">Python</a> is a widely used programming language. Python has a simple syntax and supports multiple paradigms.</p>
"""
# List of keywords and their corresponding note ids
data = [
["Python", "python_programming"],
["programming language", "programming_language"],
["simple syntax", "simple_syntax"],
["root", "root"],
]
updated_html, updated = add_internal_links(html_content, data)
print(f'content updated {updated}')
if updated:
# Output the modified HTML
print(updated_html)