|
39 | 39 | import re |
40 | 40 | import sys |
41 | 41 | import xml.etree.ElementTree as ET |
| 42 | +import xml.sax |
42 | 43 | from collections import defaultdict, Counter |
43 | 44 | from dataclasses import dataclass |
44 | 45 | from enum import IntEnum |
@@ -77,18 +78,29 @@ class WarningItem: |
77 | 78 | severity: Severity |
78 | 79 |
|
79 | 80 |
|
80 | | -def approximate_message_lines(text: str): |
81 | | - """Yield approximate line numbers for <message> elements.""" |
82 | | - lines = text.splitlines() |
83 | | - cursor = 0 |
84 | | - for _ in range(text.count("<message")): |
85 | | - for i in range(cursor, len(lines)): |
86 | | - if "<message" in lines[i]: |
87 | | - cursor = i + 1 |
88 | | - yield i + 1 |
89 | | - break |
90 | | - else: |
91 | | - yield 0 |
| 81 | +class MessageLocator(xml.sax.ContentHandler): |
| 82 | + """SAX handler to find exact line numbers of <message> elements.""" |
| 83 | + def __init__(self): |
| 84 | + super().__init__() |
| 85 | + self.lines = [] |
| 86 | + self.locator = None |
| 87 | + |
| 88 | + def setDocumentLocator(self, locator): |
| 89 | + self.locator = locator |
| 90 | + |
| 91 | + def startElement(self, name, attrs): |
| 92 | + if name == "message" and self.locator: |
| 93 | + self.lines.append(self.locator.getLineNumber()) |
| 94 | + |
| 95 | + |
| 96 | +def get_exact_message_lines(text: str): |
| 97 | + """Yield exact line numbers for <message> elements using a SAX parser.""" |
| 98 | + handler = MessageLocator() |
| 99 | + try: |
| 100 | + xml.sax.parseString(text.encode("utf-8"), handler) |
| 101 | + except xml.sax.SAXException: |
| 102 | + pass |
| 103 | + yield from handler.lines |
92 | 104 |
|
93 | 105 |
|
94 | 106 | def check_language_header(ts_file: Path, root, file_lang: str): |
@@ -184,7 +196,7 @@ def detect_warnings(ts_file: Path, file_lang: str): |
184 | 196 | return [WarningItem(ts_file, 0, file_lang, f"Error parsing XML: {exc}", Severity.SEVERE)] |
185 | 197 |
|
186 | 198 | warnings = check_language_header(ts_file, root, file_lang) |
187 | | - line_gen = approximate_message_lines(text) |
| 199 | + line_gen = get_exact_message_lines(text) |
188 | 200 | for context in root.findall("context"): |
189 | 201 | warnings.extend(_process_context(ts_file, file_lang, context, line_gen)) |
190 | 202 | return warnings |
|
0 commit comments