File indexing completed on 2024-12-01 13:47:51
0001 # -*- coding: utf-8 -*- 0002 0003 """ 0004 Report info, warning and error messages. 0005 0006 Functions for Pology tools to report PO messages to the user at runtime, 0007 in different contexts and scenario. May colorize some output. 0008 0009 @author: Chusslove Illich (Часлав Илић) <caslav.ilic@gmx.net> 0010 @author: Nick Shaforostoff (Николай Шафоростов) <shaforostoff@kde.ru> 0011 @license: GPLv3 0012 """ 0013 0014 # NOTE: These functions are not in pology.report module, 0015 # as that would cause circular module dependencies. 0016 0017 from copy import deepcopy 0018 import os 0019 import re 0020 import sys 0021 0022 from pology import _, n_ 0023 from pology.message import Message 0024 from pology.colors import ColorString, cjoin, cinterp 0025 from pology.diff import adapt_spans 0026 from pology.escape import escape_c as escape 0027 from pology.monitored import Monpair 0028 from pology.report import report, warning, error, format_item_list 0029 0030 0031 # FIXME: Make this a public function in getfunc module. 0032 _modules_on_request = {} 0033 def _get_module (name, cmsg=None): 0034 0035 if name not in _modules_on_request: 0036 try: 0037 _modules_on_request[name] = __import__(name) 0038 except: 0039 if cmsg: 0040 warning(_("@info", 0041 "Cannot import module '%(mod)s'; consequence:\n" 0042 "%(msg)s", 0043 mod=name, msg=cmsg)) 0044 else: 0045 warning(_("@info", 0046 "Cannot import module '%(mod)s'.", 0047 mod=name)) 0048 _modules_on_request[name] = None 0049 0050 return _modules_on_request[name] 0051 0052 0053 def report_on_msg (text, msg, cat, subsrc=None, file=sys.stdout): 0054 """ 0055 Report on a PO message. 0056 0057 Outputs the message reference (catalog name and message position), 0058 along with the report text. 0059 0060 @param text: text to report 0061 @type text: string 0062 @param msg: the message for which the text is reported 0063 @type msg: L{Message_base} 0064 @param cat: the catalog where the message lives 0065 @type cat: L{Catalog} 0066 @param subsrc: more detailed source of the message 0067 @type subsrc: C{None} or string 0068 @param file: send output to this file descriptor 0069 @type file: C{file} 0070 """ 0071 0072 posinfo = _msg_pos_fmt(cat.filename, msg.refline, msg.refentry) 0073 text = cinterp("%s: %s", posinfo, text) 0074 report(text, subsrc=subsrc, showcmd=False) 0075 0076 0077 def warning_on_msg (text, msg, cat, subsrc=None, file=sys.stderr): 0078 """ 0079 Warning on a PO message. 0080 0081 Outputs the message reference (catalog name and the message position), 0082 along with the warning text. 0083 0084 @param text: text to report 0085 @type text: string 0086 @param msg: the message for which the text is reported 0087 @type msg: L{Message_base} 0088 @param cat: the catalog where the message lives 0089 @type cat: L{Catalog} 0090 @param subsrc: more detailed source of the message 0091 @type subsrc: C{None} or string 0092 @param file: send output to this file descriptor 0093 @type file: C{file} 0094 """ 0095 0096 posinfo = _msg_pos_fmt(cat.filename, msg.refline, msg.refentry) 0097 text = cinterp("%s: %s", posinfo, text) 0098 warning(text, subsrc=subsrc, showcmd=False) 0099 0100 0101 def error_on_msg (text, msg, cat, code=1, subsrc=None, file=sys.stderr): 0102 """ 0103 Error on a PO message (aborts the execution). 0104 0105 Outputs the message reference (catalog name and message position), 0106 along with the error text. Aborts execution with the given code. 0107 0108 @param text: text to report 0109 @type text: string 0110 @param msg: the message for which the text is reported 0111 @type msg: L{Message_base} 0112 @param cat: the catalog where the message lives 0113 @type cat: L{Catalog} 0114 @param code: the exit code 0115 @type code: int 0116 @param subsrc: more detailed source of the message 0117 @type subsrc: C{None} or string 0118 @param file: send output to this file descriptor 0119 @type file: C{file} 0120 """ 0121 0122 posinfo = _msg_pos_fmt(cat.filename, msg.refline, msg.refentry) 0123 text = cinterp("%s: %s", posinfo, text) 0124 error(text, code=code, subsrc=subsrc, showcmd=True) 0125 0126 0127 def report_on_msg_hl (highlight, msg, cat, fmsg=None, 0128 subsrc=None, file=sys.stdout): 0129 """ 0130 Report on parts of a PO message. 0131 0132 For each of the spans found in the L{highlight<report_msg_content>} 0133 specification which have a note attached, outputs the position reference 0134 (catalog name, message position, spanned segment) and the span note. 0135 The highlight can be relative to a somewhat modified, filtered message 0136 instead of the original one. 0137 0138 @param highlight: highlight specification 0139 @type highlight: L{highlight<report_msg_content>} 0140 @param msg: the message for which the text is reported 0141 @type msg: L{Message_base} 0142 @param cat: the catalog where the message lives 0143 @type cat: L{Catalog} 0144 @param fmsg: filtered message to which the highlight corresponds 0145 @type fmsg: L{Message_base} 0146 @param subsrc: more detailed source of the message 0147 @type subsrc: C{None} or string 0148 @param file: send output to this file descriptor 0149 @type file: C{file} 0150 """ 0151 0152 refpos = _msg_pos_fmt(cat.filename, msg.refline, msg.refentry) 0153 0154 if not fmsg: # use original message as filtered if not given 0155 fmsg = msg 0156 0157 for hspec in highlight: 0158 name, item, spans = hspec[:3] 0159 0160 if name == "msgctxt": 0161 text = msg.msgctxt or "" 0162 ftext = fmsg.msgctxt or "" 0163 elif name == "msgid": 0164 text = msg.msgid 0165 ftext = fmsg.msgid 0166 elif name == "msgid_plural": 0167 text = msg.msgid_plural or "" 0168 ftext = fmsg.msgid_plural or "" 0169 elif name == "msgstr": 0170 text = msg.msgstr[item] 0171 ftext = fmsg.msgstr[item] 0172 # TODO: Add more fields. 0173 else: 0174 warning(_("@info", 0175 "Unknown field '%(field)s' " 0176 "in highlighting specification.", 0177 field=name)) 0178 continue 0179 0180 if len(hspec) > 3: 0181 # Override filtered text from filtered message 0182 # by filtered text from the highlight spec. 0183 ftext = hspec[3] 0184 0185 spans = adapt_spans(text, ftext, spans, merge=False) 0186 0187 if msg.msgid_plural is not None and name == "msgstr": 0188 name = "%s_%d" % (name, item) 0189 0190 for span in spans: 0191 if len(span) < 3: 0192 continue 0193 start, end, snote = span 0194 if isinstance(start, int) and isinstance(end, int): 0195 seglen = end - start 0196 if seglen > 0: 0197 segtext = text[start:end] 0198 if len(segtext) > 30: 0199 segtext = segtext[:27] + "..." 0200 posinfo = "%s:%d:\"%s\"" % (name, start, escape(segtext)) 0201 else: 0202 posinfo = "%s:%d" % (name, start) 0203 else: 0204 posinfo = "%s" % name 0205 posinfo = ColorString("<green>%s</green>") % posinfo 0206 0207 rtext = cinterp("%s[%s]: %s", refpos, posinfo, snote) 0208 report(rtext, subsrc=subsrc, showcmd=False) 0209 0210 0211 def report_msg_to_lokalize (msg, cat, report=None): 0212 """ 0213 Open catalog in Lokalize and jump to message. 0214 0215 Lokalize is a CAT tool for KDE 4, U{http://userbase.kde.org/Lokalize}. 0216 This function opens the catalog in Lokalize (if not already open) 0217 and jumps to the given message within it. 0218 0219 If the message is obsolete, it will be ignored. 0220 0221 @param msg: the message which should be jumped to in Lokalize 0222 @type msg: L{Message_base} 0223 @param cat: the catalog in which the message resides 0224 @type cat: L{Catalog} 0225 @param report: simple text or highlight specification 0226 @type report: string or L{highlight<report_msg_content>} 0227 """ 0228 0229 dbus = _get_module("dbus", 0230 _("@info", 0231 "Communication with Lokalize not possible. " 0232 "Try installing the '%(pkg)s' package.", 0233 pkg="python-dbus")) 0234 if not dbus: return 0235 0236 if msg.obsolete: return 0237 0238 # If report is a highlight specification, 0239 # flatten it into lines of notes by spans. 0240 if isinstance(report, list): 0241 notes=[] 0242 for hspec in report: 0243 for span in hspec[2]: 0244 if len(span) > 2: 0245 notes.append(span[2]) 0246 report = cjoin(notes, "\n") 0247 0248 try: 0249 try: globals()['lokalizeobj'] 0250 except: 0251 bus = dbus.SessionBus() 0252 lokalize_dbus_instances=lambda:[name for name in bus.list_names() if name.startswith('org.kde.lokalize')] 0253 for lokalize_dbus_instance in lokalize_dbus_instances(): 0254 try: 0255 globals()['lokalizeinst']=lokalize_dbus_instance 0256 globals()['lokalizeobj']=bus.get_object(globals()['lokalizeinst'],'/ThisIsWhatYouWant') 0257 globals()['openFileInEditor']=globals()['lokalizeobj'].get_dbus_method('openFileInEditor','org.kde.Lokalize.MainWindow') 0258 globals()['visitedcats']={} 0259 except: 0260 pass 0261 if 'openFileInEditor' not in globals(): 0262 return 0263 0264 index=globals()['openFileInEditor'](os.path.abspath(cat.filename)) 0265 editorobj=dbus.SessionBus().get_object(globals()['lokalizeinst'],'/ThisIsWhatYouWant/Editor/%d' % index) 0266 0267 if cat.filename not in globals()['visitedcats']: 0268 globals()['visitedcats'][cat.filename]=1 0269 0270 setEntriesFilteredOut=editorobj.get_dbus_method('setEntriesFilteredOut','org.kde.Lokalize.Editor') 0271 setEntriesFilteredOut(True) 0272 0273 setEntryFilteredOut=editorobj.get_dbus_method('setEntryFilteredOut','org.kde.Lokalize.Editor') 0274 setEntryFilteredOut(msg.refentry-1,False) 0275 0276 gotoEntry=editorobj.get_dbus_method('gotoEntry','org.kde.Lokalize.Editor') 0277 gotoEntry(msg.refentry-1) 0278 0279 if report: 0280 addTemporaryEntryNote=editorobj.get_dbus_method('addTemporaryEntryNote','org.kde.Lokalize.Editor') 0281 addTemporaryEntryNote(msg.refentry-1,report.resolve(ctype="none")) 0282 0283 except: 0284 return 0285 0286 0287 def report_msg_content (msg, cat, 0288 wrapf=None, force=False, 0289 note=None, delim=None, highlight=None, 0290 showmsg=True, fmsg=None, showfmsg=False, 0291 subsrc=None, file=sys.stdout): 0292 """ 0293 Report the content of a PO message. 0294 0295 Provides the message reference, consisting of the catalog name and 0296 the message position within it, the message contents, 0297 and any notes on particular segments. 0298 0299 Parts of the message can be highlighted using colors. 0300 Parameter C{highlight} provides the highlighting specification, as 0301 list of tuples where each tuple consists of: name of the message element 0302 to highlight, element index (used when the element is a list of values), 0303 list of spans, and optionally the filtered text of the element value. 0304 For example, to highlight spans C{(5, 10)} and C{(15, 25)} in the C{msgid}, 0305 and C{(30, 40)} in C{msgstr}, the highlighting specification would be:: 0306 0307 [("msgid", 0, [(5, 10), (15, 25)]), ("msgstr", 0, [(30, 40)])] 0308 0309 Names of the elements that can presently be highlighted are: C{"msgctxt"}, 0310 C{"msgid"}, C{"msgid_plural"}, C{"msgstr"}, C{"manual_comment"}, 0311 C{"auto_comment"}, C{"source"}, C{"flag"}. 0312 For unique fields the element index is not used, but 0 should be given 0313 for consistency (may be enforced later). 0314 Span tuples can have a third element, following the indices, which is 0315 the note about why the particular span is highlighted; 0316 there may be more elements after the note, and these are all ignored. 0317 If start or end index of a span is not an integer, 0318 then the note is taken as relating to the complete field. 0319 0320 Sometimes the match to which the spans correspond has been made on a 0321 filtered value of the message field (e.g. after accelerator markers 0322 or tags have been removed). In that case, the filtered text can be 0323 given as the fourth element of the tuple, after the list of spans, and 0324 the function will try to fit spans from filtered onto original text. 0325 More globally, if the complete highlight is relative to a modified, 0326 filtered version of the message, this message can be given as 0327 C{fmsg} parameter. 0328 0329 The display of content can be controlled by C{showmsg} parameter; 0330 if it is C{False}, only the message reference and span notes are shown. 0331 Similarly for the C{showfmsg} parameter, which controls the display 0332 of the content of filtered message (if given by C{fmsg}). 0333 To show the filtered message may be useful for debugging filtering 0334 in cases when it is not straightforward, or it is user-defined. 0335 0336 @param msg: the message to report the content for 0337 @type msg: L{Message_base} 0338 @param cat: the catalog where the message lives 0339 @type cat: L{Catalog} or C{None} 0340 @param wrapf: 0341 the function used for wrapping message fields in output. 0342 See L{to_lines()<message.Message_base.to_lines>} method 0343 of message classes for details. 0344 If not given, it will be taken from the catalog 0345 (see L{Catalog.wrapf<catalog.Catalog.wrapf>}). 0346 @type wrapf: (string)->[string...] 0347 @param force: whether to force reformatting of cached message content 0348 @type force: bool 0349 @param note: note about why the content is being reported 0350 @type note: string 0351 @param delim: text to print on the line following the message 0352 @type delim: C{None} or string 0353 @param highlight: highlighting specification of message elements 0354 @type highlight: (see description) 0355 @param showmsg: show content of the message 0356 @type showmsg: bool 0357 @param fmsg: filtered message 0358 @type fmsg: L{Message_base} 0359 @param showfmsg: show content of the filtered message, if any 0360 @type showfmsg: bool 0361 @param subsrc: more detailed source of the message 0362 @type subsrc: C{None} or string 0363 @param file: output stream 0364 @type file: file 0365 """ 0366 0367 rsegs = [] 0368 0369 wrapf = wrapf or cat.wrapf() 0370 0371 notes_data = [] 0372 if highlight: 0373 msg = Message(msg) # must work on copy, highlight modifies it 0374 ffmsg = fmsg or msg # use original message as filtered if not given 0375 0376 # Unify spans for same parts, to have single coloring pass per part 0377 # (otherwise markup can get corrupted). 0378 highlightd = {} 0379 for hspec in highlight: 0380 name, item, spans = hspec[:3] 0381 pkey = (name, item) 0382 phspec = highlightd.get(pkey) 0383 if phspec is None: 0384 # Make needed copies in order not to modify 0385 # the original highlight when adding stuff later. 0386 highlightd[pkey] = list(hspec) 0387 highlightd[pkey][2] = list(spans) 0388 else: 0389 phspec[2].extend(spans) 0390 # Take filtered text if available and not already taken. 0391 if len(hspec) > 3 and len(phspec) <= 3: 0392 phspec.append(hspec[3]) 0393 highlight = list(highlightd.values()) 0394 0395 for hspec in highlight: 0396 name, item, spans = hspec[:3] 0397 0398 def hl (text, ftext): 0399 if len(hspec) > 3: 0400 # Override filtered text from filtered message 0401 # by filtered text from the highlight spec. 0402 ftext = hspec[3] 0403 aspans = adapt_spans(text, ftext, spans, merge=False) 0404 notes_data.append((text, name, item, aspans)) 0405 text = _highlight_spans(text, spans, "red", ftext=ftext) 0406 return text 0407 0408 if name == "msgctxt": 0409 if msg.msgctxt or ffmsg.msgctxt: 0410 msg.msgctxt = hl(msg.msgctxt or "", ffmsg.msgctxt or "") 0411 elif name == "msgid": 0412 msg.msgid = hl(msg.msgid, ffmsg.msgid) 0413 elif name == "msgid_plural": 0414 msg.msgid_plural = hl(msg.msgid_plural or "", 0415 ffmsg.msgid_plural or "") 0416 elif name == "msgstr": 0417 msg.msgstr[item] = hl(msg.msgstr[item], ffmsg.msgstr[item]) 0418 elif name == "manual_comment": 0419 msg.manual_comment[item] = hl(msg.manual_comment[item], 0420 ffmsg.manual_comment[item]) 0421 elif name == "auto_comment": 0422 msg.auto_comment[item] = hl(msg.auto_comment[item], 0423 ffmsg.auto_comment[item]) 0424 elif name == "source": 0425 msg.source[item] = Monpair((hl(msg.source[item][0], 0426 ffmsg.source[item][0]), 0427 msg.source[item][1])) 0428 elif name == "flag": 0429 pass # FIXME: How to do this? 0430 else: 0431 warning(_("@info", 0432 "Unknown field '%(field)s' " 0433 "in highlighting specification.", 0434 field=name)) 0435 0436 # Report the message. 0437 msegs = [] 0438 if cat is not None: 0439 msegs += [_msg_pos_fmt(cat.filename, msg.refline, msg.refentry) + "\n"] 0440 if showmsg: 0441 msgstr = msg.to_string(wrapf=wrapf, force=force, colorize=1) 0442 msegs += [msgstr.rstrip() + "\n"] 0443 if msegs: 0444 rsegs.append(cjoin(msegs).rstrip()) 0445 0446 # Report notes. 0447 if note is not None: # global 0448 notestr = _("@info", 0449 "<bold>[note]</bold> %(msg)s", 0450 msg=note) 0451 rsegs.append(notestr) 0452 if notes_data: # span notes 0453 note_ord = 1 0454 for text, name, item, spans in notes_data: 0455 if msg.msgid_plural is not None and name == "msgstr": 0456 name = "%s_%d" % (name, item) 0457 for span in spans: 0458 if len(span) < 3: 0459 continue 0460 start, end, snote = span 0461 if isinstance(start, int) and isinstance(end, int): 0462 seglen = end - start 0463 if seglen > 0: 0464 segtext = text[start:end] 0465 if len(segtext) > 30: 0466 segtext = _("@item:intext shortened longer text", 0467 "%(snippet)s...", 0468 snippet=segtext[:27]) 0469 posinfo = "%s:%d:\"%s\"" % (name, start, escape(segtext)) 0470 else: 0471 posinfo = "%s:%d" % (name, start) 0472 else: 0473 posinfo = "%s" % name 0474 posinfo = ColorString("<green>%s</green>") % posinfo 0475 rsegs.append(_("@info", 0476 "[%(pos)s]: %(msg)s", 0477 pos=posinfo, msg=snote)) 0478 note_ord += 1 0479 0480 # Report the filtered message, if given and requested. 0481 if fmsg and showfmsg: 0482 fmtnote = (ColorString("<green>%s</green>") 0483 % _("@info", ">>> Filtered message was:")) 0484 rsegs.append(fmtnote) 0485 fmsgstr = fmsg.to_string(wrapf=wrapf, force=force, colorize=1) 0486 mstr = fmsgstr.rstrip() + "\n" 0487 rsegs.append(mstr.rstrip()) 0488 0489 if delim: 0490 rsegs.append(delim) 0491 0492 rtext = cjoin(rsegs, "\n").rstrip() 0493 report(rtext, subsrc=subsrc, file=file) 0494 0495 0496 def rule_error(msg, cat, rule, highlight=None, fmsg=None, showmsg=True, 0497 predelim=False): 0498 """ 0499 Print formated rule error message on screen. 0500 0501 @param msg: pology.message.Message object 0502 @param cat: pology.catalog.Catalog object 0503 @param rule: pology.rules.Rule object 0504 @param highlight: highlight specification (see L{report_msg_content}) 0505 @param fmsg: filtered message which the rule really matched 0506 @param showmsg: whether to show contents of message (either filtered or original) 0507 @param predelim: whether to also print delimiter before the rule error 0508 """ 0509 0510 # Some info on the rule. 0511 rinfo = _("@info", 0512 "rule %(rule)s <bold><red>==></red></bold> " 0513 "<bold>%(msg)s</bold>", 0514 rule=rule.displayName, msg=rule.hint) 0515 0516 if showmsg: 0517 delim = "-" * 40 0518 if predelim: 0519 report(delim) 0520 report_msg_content(msg, cat, 0521 highlight=highlight, 0522 fmsg=fmsg, showfmsg=(fmsg is not None), 0523 note=rinfo, delim=delim) 0524 else: 0525 report_on_msg(rinfo, msg, cat) 0526 report_on_msg_hl(highlight, msg, cat, fmsg) 0527 0528 0529 def multi_rule_error (msg, cat, rspec, showmsg=True, predelim=False): 0530 """ 0531 Print formated rule error messages on screen. 0532 0533 Like L{rule_error}, but reports multiple failed rules at once. 0534 Contents of the matched message is shown only once for all rules, 0535 with all highlights embedded, and all rule information following. 0536 This holds unless there are several different filtered messages, 0537 when rule failures are reported in groups by filtered message. 0538 0539 @param msg: the message matched by rules 0540 @type msg: Message 0541 @param cat: the catalog in which the message resides 0542 @type cat: Catalog 0543 @param rspec: specification of failed rules. This is a list in which 0544 each element can be one of: 0545 - rule 0546 - tuple of rule and highlight specification (see 0547 L{report_msg_content} for details on highlight specifications). 0548 Highlight can be None. 0549 - tuple of rule, highlight, and filtered message which 0550 the rule really matched. 0551 Highlight and filtered message can be None. 0552 @type rspec: [(Rule|(Rule, highlight)|(Rule, highlight, Message))*] 0553 @param showmsg: whether to show contents of message 0554 (both original and filtered if given) 0555 @type showmsg: bool 0556 @param predelim: whether to also print delimiter before the first error 0557 @type predelim: bool 0558 """ 0559 0560 # Expand elements in rule specification to full lengths. 0561 rspec_mod = [] 0562 for el in rspec: 0563 if not isinstance(el, tuple): 0564 el = (el,) 0565 el_mod = el + tuple(None for i in range(3 - len(el))) 0566 rspec_mod.append(el_mod) 0567 rspec = rspec_mod 0568 0569 # Split into groups by distinct filtered messages, 0570 # or make one dummy group if content display not requested. 0571 if showmsg: 0572 rspec_groups = [] 0573 for rule, hl, fmsg in rspec: 0574 rlhls = None 0575 for ofmsg, rlhls in rspec_groups: 0576 if fmsg == ofmsg: # check for apparent equality 0577 break 0578 if rlhls is None: 0579 rlhls = [] 0580 rspec_groups.append((fmsg, rlhls)) 0581 rlhls.append((rule, hl)) 0582 else: 0583 rlhls = [] 0584 rspec_groups = [(None, rlhls)] 0585 for rule, hl, fmsg in rspec: 0586 rlhls.append((rule, hl)) 0587 0588 # Report each rule group. 0589 for fmsg, rlhls in rspec_groups: 0590 rinfos = [] 0591 highlight = [] 0592 for rule, hl in rlhls: 0593 rinfos.append(_("@info", 0594 "rule %(rule)s <bold><red>==></red></bold> " 0595 "<bold>%(msg)s</bold>", 0596 rule=rule.displayName, msg=rule.hint)) 0597 highlight.extend(hl) 0598 if len(rinfos) > 1: 0599 note = cjoin([""] + rinfos, "\n") 0600 elif rinfos: 0601 note = rinfos[0] 0602 if showmsg: 0603 delim = "-" * 40 0604 if predelim: 0605 report(delim) 0606 report_msg_content(msg, cat, 0607 highlight=highlight, 0608 fmsg=fmsg, showfmsg=(fmsg is not None), 0609 note=note, delim=delim) 0610 else: 0611 report_on_msg(note, msg, cat) 0612 report_on_msg_hl(highlight, msg, cat, fmsg) 0613 0614 0615 def rule_xml_error(msg, cat, rule, span, pluralId=0): 0616 """Create and returns rule error message in XML format 0617 @param msg: pology.message.Message object 0618 @param cat: pology.catalog.Catalog object 0619 @param span: list of 2-tuple (start, end) of offending spans 0620 @param rule: pology.rules.Rule object 0621 @param pluralId: msgstr count in case of plural form. Default to 0 0622 @return: XML message as a list of unicode string""" 0623 xmlError=[] 0624 xmlError.append("\t<error>\n") 0625 xmlError.append("\t\t<line>%s</line>\n" % msg.refline) 0626 xmlError.append("\t\t<refentry>%s</refentry>\n" % msg.refentry) 0627 xmlError.append("\t\t<msgctxt><![CDATA[%s]]></msgctxt>\n" % _escapeCDATA(msg.msgctxt or "")) 0628 xmlError.append("\t\t<msgid><![CDATA[%s]]></msgid>\n" % _escapeCDATA(msg.msgid)) 0629 xmlError.append("\t\t<msgstr><![CDATA[%s]]></msgstr>\n" % _escapeCDATA(msg.msgstr[pluralId])) 0630 for begin, end in span: 0631 if isinstance(begin, int) and isinstance(end, int): 0632 xmlError.append("\t\t<highlight begin='%s' end='%s'/>\n" % (begin, end)) 0633 #xmlError.append("\t\t<start>%s</start>\n" % span[0]) 0634 #xmlError.append("\t\t<end>%s</end>\n" % span[1]) 0635 xmlError.append("\t\t<pattern><![CDATA[%s]]></pattern>\n" % rule.rawPattern) 0636 xmlError.append("\t\t<hint><![CDATA[%s]]></hint>\n" % rule.hint) 0637 xmlError.append("\t</error>\n") 0638 return xmlError 0639 0640 0641 def spell_error(msg, cat, faultyWord, suggestions): 0642 """Print formated rule error message on screen 0643 @param msg: pology.message.Message object 0644 @param cat: pology.catalog.Catalog object 0645 @param faultyWord: badly spelled word 0646 @param suggestions : list of correct words to suggest""" 0647 report("-"*40) 0648 report(ColorString("<bold>%s:%d(%d)</bold>") 0649 % (cat.filename, msg.refline, msg.refentry)) 0650 if msg.msgctxt: 0651 report(_("@info", 0652 "<bold>Context:</bold> %(snippet)s", 0653 snippet=msg.msgctxt)) 0654 #TODO: color in red part of context that make the mistake 0655 report(_("@info", 0656 "<bold>Faulty word:</bold> <red>%(word)s</red>", 0657 word=faultyWord)) 0658 if suggestions: 0659 report(_("@info", 0660 "<bold>Suggestions:</bold> %(wordlist)s", 0661 wordlist=format_item_list(suggestions))) 0662 0663 0664 def spell_xml_error(msg, cat, faultyWord, suggestions, pluralId=0): 0665 """Create and returns spell error message in XML format 0666 @param msg: pology.message.Message object 0667 @param cat: pology.catalog.Catalog object 0668 @param faultyWord: badly spelled word 0669 @param suggestions : list of correct words to suggest 0670 @param pluralId: msgstr count in case of plural form. Default to 0 0671 @return: XML message as a list of unicode string""" 0672 xmlError=[] 0673 xmlError.append("\t<error>\n") 0674 xmlError.append("\t\t<line>%s</line>\n" % msg.refline) 0675 xmlError.append("\t\t<refentry>%s</refentry>\n" % msg.refentry) 0676 xmlError.append("\t\t<msgctxt><![CDATA[%s]]></msgctxt>\n" % _escapeCDATA(msg.msgctxt or "")) 0677 xmlError.append("\t\t<msgid><![CDATA[%s]]></msgid>\n" % _escapeCDATA(msg.msgid)) 0678 xmlError.append("\t\t<msgstr><![CDATA[%s]]></msgstr>\n" % _escapeCDATA(msg.msgstr[pluralId])) 0679 xmlError.append("\t\t<faulty>%s</faulty>\n" % faultyWord) 0680 for suggestion in suggestions: 0681 xmlError.append("\t\t<suggestion>%s</suggestion>\n" % suggestion) 0682 xmlError.append("\t</error>\n") 0683 return xmlError 0684 0685 0686 # Format string for message reference, based on the file descriptor. 0687 def _msg_pos_fmt (path, line, col): 0688 0689 return (ColorString("<cyan>%s</cyan>:<purple>%d</purple>" 0690 "(<purple>#%d</purple>)") % (path, line, col)) 0691 0692 0693 def _escapeCDATA(text): 0694 """Escape CDATA tags to allow inclusion into CDATA 0695 @param text: text to convert 0696 @type text: str or unicode 0697 @return: modified string""" 0698 text=text.replace("<![CDATA[", "<_!_[CDATA[") 0699 text=text.replace("]]>", "]_]_>") 0700 return text 0701 0702 0703 def _highlight_spans (text, spans, color, ftext=None): 0704 """ 0705 Adds colors around highlighted spans in text. 0706 0707 Spans are given as list of index tuples C{[(start1, end1), ...]} where 0708 start and end index have standard Python semantics. 0709 Span tuples can have more than two elements, with indices followed by 0710 additional elements, which are ignored by this function. 0711 If start or end index in a span is not an integer, the span is ignored. 0712 0713 The C{color} parameter is one of the color tags available in 0714 L{ColorString<colors.ColorString>} markup. 0715 0716 If C{ftext} is not C{None}, spans are understood as relative to it, 0717 and the function will try to adapt them to the main text 0718 (see L{pology.diff.adapt_spans}). 0719 0720 @param text: text to be highlighted 0721 @type text: string 0722 @param spans: spans to highlight 0723 @type spans: list of tuples 0724 @param color: color tag 0725 @type color: string 0726 @param ftext: text to which spans are actually relative 0727 @type ftext: string 0728 0729 @returns: highlighted text 0730 @rtype: string 0731 """ 0732 0733 if not spans or color is None: 0734 return text 0735 0736 # Adapt spans regardless if filtered text has been given or not, 0737 # to fix any overlapping and put into expected ordering. 0738 if ftext is None: 0739 ftext = text 0740 spans = adapt_spans(text, ftext, spans, merge=True) 0741 if not spans: 0742 return text 0743 0744 ctext = "" 0745 cstart = 0 0746 for span in spans: 0747 if not isinstance(span[0], int) or not isinstance(span[1], int): 0748 continue 0749 ctext += text[cstart:span[0]] 0750 ctext += (ColorString("<%s>%%s</%s>" % (color, color)) 0751 % text[span[0]:span[1]]) # outside, to have auto-escaping 0752 cstart = span[1] 0753 ctext += text[span[1]:] 0754 0755 return ctext 0756