| Comment: | merge trunk | 
|---|---|
| Downloads: | Tarball | ZIP archive | SQL archive | 
| Timelines: | family | ancestors | descendants | both | multid | 
| Files: | files | file ages | folders | 
| SHA3-256: | 
af32e20bdb0bcb20ac7d61d1aa9e467d | 
| User & Date: | olr on 2018-02-23 21:00:52 | 
| Other Links: | branch diff | manifest | tags | 
| 
   2018-02-25 
 | ||
| 08:47 | [lo] lexicon editor check-in: 5a1baacbbb user: olr tags: new_feature, lo, multid | |
| 
   2018-02-23 
 | ||
| 21:00 | merge trunk check-in: af32e20bdb user: olr tags: multid | |
| 20:46 | [lo][bug] enumerator: valuemax of progressbar check-in: 1cb2c395f6 user: olr tags: trunk, lo | |
| 
   2018-02-19 
 | ||
| 18:06 | [lo] dictionaries options: remove print check-in: ba7c03de83 user: olr tags: lo, multid | |
Modified compile_rules.py from [9bd1433006] to [b3cfeb04f1].
| ︙ | ︙ | |||
430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450  | 
    print("  parsing rules...")
    global dDEF
    lLine = []
    lRuleLine = []
    lTest = []
    lOpt = []
    zBookmark = re.compile("^!!+")
    for i, sLine in enumerate(lRules, 1):
        if sLine.startswith('#END'):
            printBookmark(0, "BREAK BY #END", i)
            break
        elif sLine.startswith("#"):
            pass
        elif sLine.startswith("DEF:"):
            m = re.match("DEF: +([a-zA-Z_][a-zA-Z_0-9]*) +(.+)$", sLine.strip())
            if m:
                dDEF["{"+m.group(1)+"}"] = m.group(2)
            else:
                print("Error in definition: ", end="")
                print(sLine.strip())
 | > > > > > >  | 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456  | 
    print("  parsing rules...")
    global dDEF
    lLine = []
    lRuleLine = []
    lTest = []
    lOpt = []
    zBookmark = re.compile("^!!+")
    zGraphLink = re.compile(r"^@@@@GRAPHLINK>(\w+)@@@@")
    for i, sLine in enumerate(lRules, 1):
        if sLine.startswith('#END'):
            printBookmark(0, "BREAK BY #END", i)
            break
        elif sLine.startswith("#"):
            pass
        elif sLine.startswith("@@@@"):
            m = re.match(r"^@@@@GRAPHLINK>(\w+)@@@@", sLine.strip())
            if m:
                #lRuleLine.append(["@GRAPHLINK", m.group(1)])
                printBookmark(1, "@GRAPHLINK: " + m.group(1), i)
        elif sLine.startswith("DEF:"):
            m = re.match("DEF: +([a-zA-Z_][a-zA-Z_0-9]*) +(.+)$", sLine.strip())
            if m:
                dDEF["{"+m.group(1)+"}"] = m.group(2)
            else:
                print("Error in definition: ", end="")
                print(sLine.strip())
 | 
| ︙ | ︙ | 
Modified gc_core/py/__init__.py from [a7ffc6f8bf] to [aeadedff14].
> >  | 1 2  | from .grammar_checker import *  | 
Added gc_core/py/grammar_checker.py version [79ce1061e8].
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > >  | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73  | 
# Grammalecte
# Main class: wrapper
import importlib
import json
from . import text
class GrammarChecker:
    def __init__ (self, sLangCode, sContext="Python"):
        self.sLangCode = sLangCode
        # Grammar checker engine
        self.gce = importlib.import_module("."+sLangCode, "grammalecte")
        self.gce.load(sContext)
        # Spell checker
        self.oSpellChecker = self.gce.getSpellChecker()
        # Lexicographer
        self.oLexicographer = None
        # Text formatter
        self.oTextFormatter = None
    def getGCEngine (self):
        return self.gce
    def getSpellChecker (self):
        return self.oSpellChecker
    def getTextFormatter (self):
        if self.oTextFormatter == None:
            self.tf = importlib.import_module("."+self.sLangCode+".textformatter", "grammalecte")
        self.oTextFormatter = self.tf.TextFormatter()
        return self.oTextFormatter
    def getLexicographer (self):
        if self.oLexicographer == None:
            self.lxg = importlib.import_module("."+self.sLangCode+".lexicographe", "grammalecte")
        self.oLexicographer = self.lxg.Lexicographe(self.oSpellChecker)
        return self.oLexicographer
    def displayGCOptions (self):
        self.gce.displayOptions()
    def getParagraphErrors (self, sText, dOptions=None, bContext=False, bSpellSugg=False, bDebug=False):
        "returns a tuple: (grammar errors, spelling errors)"
        aGrammErrs = self.gce.parse(sText, "FR", bDebug=bDebug, dOptions=dOptions, bContext=bContext)
        aSpellErrs = self.oSpellChecker.parseParagraph(sText, bSpellSugg)
        return aGrammErrs, aSpellErrs
    def generateText (self, sText, bEmptyIfNoErrors=False, bSpellSugg=False, nWidth=100, bDebug=False):
        pass
    def generateTextAsJSON (self, sText, bContext=False, bEmptyIfNoErrors=False, bSpellSugg=False, bReturnText=False, bDebug=False):
        pass
    def generateParagraph (self, sText, dOptions=None, bEmptyIfNoErrors=False, bSpellSugg=False, nWidth=100, bDebug=False):
        aGrammErrs, aSpellErrs = self.getParagraphErrors(sText, dOptions, False, bSpellSugg, bDebug)
        if bEmptyIfNoErrors and not aGrammErrs and not aSpellErrs:
            return ""
        return text.generateParagraph(sText, aGrammErrs, aSpellErrs, nWidth)
    def generateParagraphAsJSON (self, iIndex, sText, dOptions=None, bContext=False, bEmptyIfNoErrors=False, bSpellSugg=False, bReturnText=False, lLineSet=None, bDebug=False):
        aGrammErrs, aSpellErrs = self.getParagraphErrors(sText, dOptions, bContext, bSpellSugg, bDebug)
        aGrammErrs = list(aGrammErrs)
        if bEmptyIfNoErrors and not aGrammErrs and not aSpellErrs:
            return ""
        if lLineSet:
            aGrammErrs, aSpellErrs = text.convertToXY(aGrammErrs, aSpellErrs, lLineSet)
            return json.dumps({ "lGrammarErrors": aGrammErrs, "lSpellingErrors": aSpellErrs }, ensure_ascii=False)
        if bReturnText:
            return json.dumps({ "iParagraph": iIndex, "sText": sText, "lGrammarErrors": aGrammErrs, "lSpellingErrors": aSpellErrs }, ensure_ascii=False)
        return json.dumps({ "iParagraph": iIndex, "lGrammarErrors": aGrammErrs, "lSpellingErrors": aSpellErrs }, ensure_ascii=False)
 | 
Modified gc_lang/fr/config.ini from [576d6e6c29] to [87fa24eb7e].
| ︙ | ︙ | |||
90 91 92 93 94 95 96 97 98 99 100 101  | oxt/ContextMenu/ContextMenu.py = ContextMenu.py oxt/ContextMenu/jobs.xcu = config/jobs.xcu # TextFormatter oxt/TextFormatter/TextFormatter.py = pythonpath/TextFormatter.py oxt/TextFormatter/tf_strings.py = pythonpath/tf_strings.py oxt/TextFormatter/tf_options.py = pythonpath/tf_options.py oxt/TextFormatter/tf_tabrep.py = pythonpath/tf_tabrep.py # Conjugueur oxt/Conjugueur/Conjugueur.py = pythonpath/Conjugueur.py # Modify author oxt/ChangeAuthor/Author.py = pythonpath/Author.py oxt/ChangeAuthor/ca_strings.py = pythonpath/ca_strings.py  | > > >  | 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104  | oxt/ContextMenu/ContextMenu.py = ContextMenu.py oxt/ContextMenu/jobs.xcu = config/jobs.xcu # TextFormatter oxt/TextFormatter/TextFormatter.py = pythonpath/TextFormatter.py oxt/TextFormatter/tf_strings.py = pythonpath/tf_strings.py oxt/TextFormatter/tf_options.py = pythonpath/tf_options.py oxt/TextFormatter/tf_tabrep.py = pythonpath/tf_tabrep.py # Lexicographer oxt/Lexicographer/Enumerator.py = pythonpath/Enumerator.py oxt/Lexicographer/enum_strings.py = pythonpath/enum_strings.py # Conjugueur oxt/Conjugueur/Conjugueur.py = pythonpath/Conjugueur.py # Modify author oxt/ChangeAuthor/Author.py = pythonpath/Author.py oxt/ChangeAuthor/ca_strings.py = pythonpath/ca_strings.py  | 
Modified gc_lang/fr/oxt/AppLauncher.py from [851c2d6eab] to [91f766cacd].
| ︙ | ︙ | |||
54 55 56 57 58 59 60 61 62 63 64 65 66 67  | 
                import Author
                xDialog = Author.Author(self.ctx)
                xDialog.run(self.sLang)
            elif sCmd == "OP":
                import Options
                xDialog = Options.GC_Options(self.ctx)
                xDialog.run(self.sLang)
            elif sCmd.startswith("FA/"):
                findAll(sCmd[6:], (sCmd[3:4] == "y"), (sCmd[4:5] == "y"))
            # elif sCmd.startswith("URL/"):
            #     # Call from context menu to launch URL?
            #     # http://opengrok.libreoffice.org/xref/core/sw/source/ui/lingu/olmenu.cxx#785
            #     xSystemShellExecute = self.ctx.getServiceManager().createInstanceWithContext('com.sun.star.system.SystemShellExecute', self.ctx)
            #     xSystemShellExecute.execute(url, "", uno.getConstantByName("com.sun.star.system.SystemShellExecuteFlags.URIS_ONLY"))
 | > > > >  | 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71  | 
                import Author
                xDialog = Author.Author(self.ctx)
                xDialog.run(self.sLang)
            elif sCmd == "OP":
                import Options
                xDialog = Options.GC_Options(self.ctx)
                xDialog.run(self.sLang)
            elif sCmd == "EN":
                import Enumerator
                xDialog = Enumerator.Enumerator(self.ctx)
                xDialog.run(self.sLang)
            elif sCmd.startswith("FA/"):
                findAll(sCmd[6:], (sCmd[3:4] == "y"), (sCmd[4:5] == "y"))
            # elif sCmd.startswith("URL/"):
            #     # Call from context menu to launch URL?
            #     # http://opengrok.libreoffice.org/xref/core/sw/source/ui/lingu/olmenu.cxx#785
            #     xSystemShellExecute = self.ctx.getServiceManager().createInstanceWithContext('com.sun.star.system.SystemShellExecute', self.ctx)
            #     xSystemShellExecute.execute(url, "", uno.getConstantByName("com.sun.star.system.SystemShellExecuteFlags.URIS_ONLY"))
 | 
| ︙ | ︙ | 
Added gc_lang/fr/oxt/Lexicographer/Enumerator.py version [753041f6b5].
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > >  | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304  | 
# Dictionary Options
# by Olivier R.
# License: MPL 2
import unohelper
import uno
import traceback
import helpers
import enum_strings
import grammalecte.graphspell as sc
from com.sun.star.task import XJobExecutor
from com.sun.star.awt import XActionListener
from com.sun.star.beans import PropertyValue
def hexToRBG (sHexa):
    r = int(sHexa[:2], 16)
    g = int(sHexa[2:4], 16)
    b = int(sHexa[4:], 16)
    return (r & 255) << 16 | (g & 255) << 8 | (b & 255)
def _waitPointer (funcDecorated):
    def wrapper (*args, **kwargs):
        # self is the first parameter if the decorator is applied on a object
        self = args[0]
        # before
        xPointer = self.xSvMgr.createInstanceWithContext("com.sun.star.awt.Pointer", self.ctx)
        xPointer.setType(uno.getConstantByName("com.sun.star.awt.SystemPointer.WAIT"))
        xWindowPeer = self.xContainer.getPeer()
        xWindowPeer.setPointer(xPointer)
        for x in xWindowPeer.Windows:
            x.setPointer(xPointer)
        # processing
        result = funcDecorated(*args, **kwargs)
        # after
        xPointer.setType(uno.getConstantByName("com.sun.star.awt.SystemPointer.ARROW"))
        xWindowPeer.setPointer(xPointer)
        for x in xWindowPeer.Windows:
            x.setPointer(xPointer)
        self.xContainer.setVisible(True) # seems necessary to refresh the dialog box and text widgets (why?)
        # return
        return result
    return wrapper
class Enumerator (unohelper.Base, XActionListener, XJobExecutor):
    def __init__ (self, ctx):
        self.ctx = ctx
        self.xSvMgr = self.ctx.ServiceManager
        self.xDesktop = self.xSvMgr.createInstanceWithContext("com.sun.star.frame.Desktop", self.ctx)
        self.xDocument = self.xDesktop.getCurrentComponent()
        self.xContainer = None
        self.xDialog = None
        self.oSpellChecker = None
    def _addWidget (self, name, wtype, x, y, w, h, **kwargs):
        xWidget = self.xDialog.createInstance('com.sun.star.awt.UnoControl%sModel' % wtype)
        xWidget.Name = name
        xWidget.PositionX = x
        xWidget.PositionY = y
        xWidget.Width = w
        xWidget.Height = h
        for k, w in kwargs.items():
            setattr(xWidget, k, w)
        self.xDialog.insertByName(name, xWidget)
        return xWidget
    def _addGrid (self, name, x, y, w, h, columns, **kwargs):
        xGridModel = self.xDialog.createInstance('com.sun.star.awt.grid.UnoControlGridModel')
        xGridModel.Name = name
        xGridModel.PositionX = x
        xGridModel.PositionY = y
        xGridModel.Width = w
        xGridModel.Height = h
        xColumnModel = xGridModel.ColumnModel
        for e in columns:
            xCol = xColumnModel.createColumn()
            for k, w in e.items():
                setattr(xCol, k, w)
            xColumnModel.addColumn(xCol)
        for k, w in kwargs.items():
            setattr(xGridModel, k, w)
        self.xDialog.insertByName(name, xGridModel)
        return xGridModel
    def run (self, sLang):
        self.dUI = enum_strings.getUI(sLang)
        # dialog
        self.xDialog = self.xSvMgr.createInstanceWithContext('com.sun.star.awt.UnoControlDialogModel', self.ctx)
        self.xDialog.Width = 240
        self.xDialog.Height = 280
        self.xDialog.Title = self.dUI.get('title', "#title#")
        xWindowSize = helpers.getWindowSize()
        self.xDialog.PositionX = int((xWindowSize.Width / 2) - (self.xDialog.Width / 2))
        self.xDialog.PositionY = int((xWindowSize.Height / 2) - (self.xDialog.Height / 2))
        # fonts
        xFDTitle = uno.createUnoStruct("com.sun.star.awt.FontDescriptor")
        xFDTitle.Height = 9
        xFDTitle.Weight = uno.getConstantByName("com.sun.star.awt.FontWeight.BOLD")
        xFDTitle.Name = "Verdana"
        
        xFDSubTitle = uno.createUnoStruct("com.sun.star.awt.FontDescriptor")
        xFDSubTitle.Height = 8
        xFDSubTitle.Weight = uno.getConstantByName("com.sun.star.awt.FontWeight.BOLD")
        xFDSubTitle.Name = "Verdana"
        # widget
        nX = 10
        nY1 = 5
        nY2 = nY1 + 225
        nWidth = self.xDialog.Width - 20
        nHeight = 10
        # List
        self._addWidget("list_section", 'FixedLine', nX, nY1, nWidth, nHeight, Label = self.dUI.get("list_section", "#err"), FontDescriptor = xFDTitle)
        self._addWidget('count_button', 'Button', nX, nY1+12, 70, 10, Label = self.dUI.get('count_button', "#err"))
        self._addWidget('count2_button', 'Button', nX+75, nY1+12, 70, 10, Label = self.dUI.get('count2_button', "#err"))
        self._addWidget('unknown_button', 'Button', nX+150, nY1+12, 70, 10, Label = self.dUI.get('unknown_button', "#err"))
        self.xGridModel = self._addGrid("list_grid", nX, nY1+25, nWidth, 180, [
            {"Title": self.dUI.get("words", "#err"), "ColumnWidth": 175},
            {"Title": "Occurrences", "ColumnWidth": 45}
        ])
        self._addWidget('num_of_entries', 'FixedText', nX, nY1+210, 60, nHeight, Label = self.dUI.get('num_of_entries', "#err"), Align = 2)
        self.xNumWord = self._addWidget('num_of_entries_res', 'FixedText', nX+65, nY1+210, 30, nHeight, Label = "—")
        self._addWidget('tot_of_entries', 'FixedText', nX+100, nY1+210, 60, nHeight, Label = self.dUI.get('tot_of_entries', "#err"), Align = 2)
        self.xTotWord = self._addWidget('tot_of_entries_res', 'FixedText', nX+165, nY1+210, 30, nHeight, Label = "—")
        
        # Tag
        # Note: the only way to group RadioButtons is to create them successively
        self._addWidget("dformat_section", 'FixedLine', nX, nY2, 90, nHeight, Label = self.dUI.get("dformat_section", "#err"), FontDescriptor = xFDTitle)
        self._addWidget("charstyle_section", 'FixedLine', nX+100, nY2, 90, nHeight, Label = self.dUI.get("charstyle_section", "#err"), FontDescriptor = xFDTitle)
        self.xUnderline = self._addWidget('underline', 'RadioButton', nX, nY2+12, 40, nHeight, Label = self.dUI.get('underline', "#err"))
        self.xNoUnderline = self._addWidget('nounderline', 'RadioButton', nX+50, nY2+12, 40, nHeight, Label = self.dUI.get('nounderline', "#err"))
        self.xAccent = self._addWidget('accentuation', 'RadioButton', nX+100, nY2+12, 50, nHeight, Label = self.dUI.get('accentuation', "#err"))
        self.xNoAccent = self._addWidget('noaccentuation', 'RadioButton', nX+155, nY2+12, 40, nHeight, Label = self.dUI.get('noaccentuation', "#err"))
        self.xTag = self._addWidget('tag_button', 'Button', self.xDialog.Width-40, nY2+10, 30, 11, Label = self.dUI.get('tag_button', "#err"), FontDescriptor = xFDTitle, TextColor = 0x005500)
        
        # Progress bar
        self.xProgressBar = self._addWidget('progress_bar', 'ProgressBar', nX, self.xDialog.Height-25, 160, 14)
        self.xProgressBar.ProgressValueMin = 0
        self.xProgressBar.ProgressValueMax = 1 # to calculate
        # Close
        self._addWidget('close_button', 'Button', self.xDialog.Width-60, self.xDialog.Height-25, 50, 14, Label = self.dUI.get('close_button', "#err"), FontDescriptor = xFDTitle, TextColor = 0x550000)
        # container
        self.xContainer = self.xSvMgr.createInstanceWithContext('com.sun.star.awt.UnoControlDialog', self.ctx)
        self.xContainer.setModel(self.xDialog)
        self.xGridControl = self.xContainer.getControl('list_grid')
        self.xContainer.getControl('count_button').addActionListener(self)
        self.xContainer.getControl('count_button').setActionCommand('Count')
        self.xContainer.getControl('count2_button').addActionListener(self)
        self.xContainer.getControl('count2_button').setActionCommand('CountByLemma')
        self.xContainer.getControl('unknown_button').addActionListener(self)
        self.xContainer.getControl('unknown_button').setActionCommand('UnknownWords')
        self.xContainer.getControl('tag_button').addActionListener(self)
        self.xContainer.getControl('tag_button').setActionCommand('Tag')
        self.xContainer.getControl('close_button').addActionListener(self)
        self.xContainer.getControl('close_button').setActionCommand('Close')
        self.xContainer.setVisible(False)
        xToolkit = self.xSvMgr.createInstanceWithContext('com.sun.star.awt.ExtToolkit', self.ctx)
        self.xContainer.createPeer(xToolkit, None)
        self.xContainer.execute()
    # XActionListener
    def actionPerformed (self, xActionEvent):
        try:
            if xActionEvent.ActionCommand == "Count":
                self.count(self.dUI.get("words", "#err"))
                self.xTag.Enabled = True
            elif xActionEvent.ActionCommand == "CountByLemma":
                self.count(self.dUI.get("lemmas", "#err"), bByLemma=True)
                self.xTag.Enabled = False
            elif xActionEvent.ActionCommand == "UnknownWords":
                self.count(self.dUI.get("unknown_words", "#err"), bOnlyUnknownWords=True)
                self.xTag.Enabled = True
            elif xActionEvent.ActionCommand == "Tag":
                nRow = self.xGridControl.getCurrentRow()
                if nRow == -1:
                    return
                sWord = self.xGridModel.GridDataModel.getCellData(0, nRow)
                if not sWord:
                    return
                sAction = ""
                if self.xUnderline.State:
                    sAction = "underline"
                elif self.xNoUnderline.State:
                    sAction = "nounderline"
                elif self.xAccent.State:
                    sAction = "accentuation"
                elif self.xNoAccent.State:
                    sAction = "noaccentuation"
                self.tagText(sWord, sAction)
            elif xActionEvent.ActionCommand == "Close":
                self.xContainer.endExecute()
        except:
            traceback.print_exc()
    
    # XJobExecutor
    def trigger (self, args):
        try:
            xDialog = Enumerator(self.ctx)
            xDialog.run()
        except:
            traceback.print_exc()
    # Code
    def _setTitleOfFirstColumn (self, sTitle):
        xColumnModel = self.xGridModel.ColumnModel
        xColumn = xColumnModel.getColumn(0)
        xColumn.Title = sTitle
    def _getParagraphsFromText (self):
        "generator: returns full document text paragraph by paragraph"
        xCursor = self.xDocument.Text.createTextCursor()
        xCursor.gotoStart(False)
        xCursor.gotoEndOfParagraph(True)
        yield xCursor.getString()
        while xCursor.gotoNextParagraph(False):
            xCursor.gotoEndOfParagraph(True)
            yield xCursor.getString()
    def _countParagraph (self):
        i = 1
        xCursor = self.xDocument.Text.createTextCursor()
        xCursor.gotoStart(False)
        while xCursor.gotoNextParagraph(False):
            i += 1
        return i
    @_waitPointer
    def count (self, sTitle, bByLemma=False, bOnlyUnknownWords=False):
        if not self.oSpellChecker:
            self.oSpellChecker = sc.SpellChecker("fr")
        self._setTitleOfFirstColumn(sTitle)
        self.xProgressBar.ProgressValueMax = self._countParagraph() * 2
        self.xProgressBar.ProgressValue = 0
        xGridDataModel = self.xGridModel.GridDataModel
        xGridDataModel.removeAllRows()
        dWord = {}
        for sParagraph in self._getParagraphsFromText():
            dWord = self.oSpellChecker.countWordsOccurrences(sParagraph, bByLemma, bOnlyUnknownWords, dWord)
            self.xProgressBar.ProgressValue += 1
        self.xProgressBar.ProgressValueMax += len(dWord)
        i = 0
        nTotOccur = 0
        for k, w in sorted(dWord.items(), key=lambda t: t[1], reverse=True):
            xGridDataModel.addRow(i, (k, w))
            self.xProgressBar.ProgressValue += 1
            i += 1
            nTotOccur += w
        self.xProgressBar.ProgressValue = self.xProgressBar.ProgressValueMax
        self.xNumWord.Label = str(i)
        self.xTotWord.Label = nTotOccur
    @_waitPointer
    def tagText (self, sWord, sAction=""):
        if not sAction:
            return
        self.xProgressBar.ProgressValueMax = self._countParagraph()
        self.xProgressBar.ProgressValue = 0
        xCursor = self.xDocument.Text.createTextCursor()
        #helpers.xray(xCursor)
        xCursor.gotoStart(False)
        xCursor.gotoEndOfParagraph(True)
        sParagraph = xCursor.getString()
        if sWord in sParagraph:
            self._tagParagraph(sWord, xCursor, sAction)
        self.xProgressBar.ProgressValue += 1
        while xCursor.gotoNextParagraph(False):
            xCursor.gotoEndOfParagraph(True)
            sParagraph = xCursor.getString()
            if sWord in sParagraph:
                self._tagParagraph(sWord, xCursor, sAction)
            self.xProgressBar.ProgressValue += 1
        self.xProgressBar.ProgressValue = self.xProgressBar.ProgressValueMax
    def _tagParagraph (self, sWord, xCursor, sAction):
        xCursor.gotoStartOfParagraph(False)
        while xCursor.gotoNextWord(False):
            if xCursor.isStartOfWord():
                xCursor.gotoEndOfWord(True)
                if sWord == xCursor.getString():
                    if sAction == "underline":
                        xCursor.CharBackColor = hexToRBG("AA0000")
                    elif sAction == "nounderline":
                        xCursor.CharBackColor = hexToRBG("FFFFFF")
                    elif sAction == "accentuation":
                        xCursor.CharStyleName = "Emphasis"
                    elif sAction == "noaccentuation":
                        #xCursor.CharStyleName = "Default Style"     # doesn’t work
                        xCursor.setPropertyToDefault("CharStyleName")
#g_ImplementationHelper = unohelper.ImplementationHelper()
#g_ImplementationHelper.addImplementation(Enumerator, 'net.grammalecte.enumerator', ('com.sun.star.task.Job',))
 | 
Added gc_lang/fr/oxt/Lexicographer/enum_strings.py version [fd05286777].
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > >  | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55  | 
def getUI (sLang):
    if sLang in dStrings:
        return dStrings[sLang]
    return dStrings["fr"]
dStrings = {
    "fr": {
        "title": "Grammalecte · Recenseur de mots",
        
        "list_section": "Calcul des occurrences des mots",
        "count_button": "Compter tout",
        "count2_button": "Compter par lemme",
        "unknown_button": "Mots inconnus",
        "num_of_entries": "Nombre d’entrées :",
        "tot_of_entries": "Total des entrées :",
        "words": "Mots",
        "lemmas": "Lemmes",
        "unknown_words": "Mots inconnus",
        "dformat_section": "Formatage direct",
        "charstyle_section": "Style de caractères",
        "underline": "Surligner",
        "nounderline": "Effacer",
        "accentuation": "Accentuation",
        "noaccentuation": "Aucun",
        "tag_button": "Taguer",
        "close_button": "Fermer",
    },
    "en": {
        "title": "Grammalecte · Enumerator of Words",
        "list_section": "Words",
        "count_button": "Count all",
        "count2_button": "Count by lemma",
        "unknown_button": "Unknown words",
        "num_of_entries": "Number of entries:",
        "tot_of_entries": "Total of words:",
        "words": "Words",
        "lemmas": "Lemmas",
        "unknown_words": "Unknown words",
        "dformat_section": "Direct format",
        "charstyle_section": "Character style",
        "underline": "Underline",
        "nounderline": "Erase",
        "accentuation": "Accentuation",
        "noaccentuation": "None",
        "tag_button": "Tag",
        "close_button": "Close",
    },
}
 | 
Modified gc_lang/fr/oxt/addons.xcu from [3b803f02e0] to [9b5b4f4c15].
| ︙ | ︙ | |||
67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91  | 
                        <value>_self</value>
                    </prop>
                    <prop oor:name="Context" oor:type="xs:string">
                        <value>com.sun.star.text.TextDocument,com.sun.star.text.GlobalDocument,com.sun.star.text.WebDocument,com.sun.star.presentation.PresentationDocument</value>
                    </prop>
                </node>
                <node oor:name="m3" oor:op="replace">
                    <!--<prop oor:name="URL" oor:type="xs:string">
                        <value>vnd.sun.star.script:basiclib.Module1.EditAuthorField?language=Basic&location=application</value>
                    </prop>-->
                    <prop oor:name="URL" oor:type="xs:string">
                        <value>service:net.grammalecte.AppLauncher?MA</value>
                    </prop>
                    <prop oor:name="Title" oor:type="xs:string">
                        <value/>
                        <value xml:lang="fr">~Modifier le champ “Auteur”…</value>
                        <value xml:lang="en-US">~Modify the field “Author”…</value>
                    </prop>
                    <prop oor:name="Target" oor:type="xs:string">
                        <value>_self</value>
                    </prop>
                    <prop oor:name="Context" oor:type="xs:string">
                        <value>com.sun.star.text.TextDocument,com.sun.star.text.GlobalDocument,com.sun.star.text.WebDocument</value>
                    </prop>
                </node>
 | > > > > > > > > > > > > > > > > | |  | 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123  | 
                        <value>_self</value>
                    </prop>
                    <prop oor:name="Context" oor:type="xs:string">
                        <value>com.sun.star.text.TextDocument,com.sun.star.text.GlobalDocument,com.sun.star.text.WebDocument,com.sun.star.presentation.PresentationDocument</value>
                    </prop>
                </node>
                <node oor:name="m3" oor:op="replace">
                    <prop oor:name="URL" oor:type="xs:string">
                        <value>service:net.grammalecte.AppLauncher?EN</value>
                    </prop>
                    <prop oor:name="Title" oor:type="xs:string">
                        <value/>
                        <value xml:lang="fr">~Recenseur de mots…</value>
                        <value xml:lang="en-US">~Enumerator of words…</value>
                    </prop>
                    <prop oor:name="Target" oor:type="xs:string">
                        <value>_self</value>
                    </prop>
                    <prop oor:name="Context" oor:type="xs:string">
                        <value>com.sun.star.text.TextDocument,com.sun.star.text.GlobalDocument,com.sun.star.text.WebDocument,com.sun.star.presentation.PresentationDocument</value>
                    </prop>
                </node>
                <node oor:name="m4" oor:op="replace">
                    <!--<prop oor:name="URL" oor:type="xs:string">
                        <value>vnd.sun.star.script:basiclib.Module1.EditAuthorField?language=Basic&location=application</value>
                    </prop>-->
                    <prop oor:name="URL" oor:type="xs:string">
                        <value>service:net.grammalecte.AppLauncher?MA</value>
                    </prop>
                    <prop oor:name="Title" oor:type="xs:string">
                        <value/>
                        <value xml:lang="fr">~Modifier le champ “Auteur”…</value>
                        <value xml:lang="en-US">~Modify the field “Author”…</value>
                    </prop>
                    <prop oor:name="Target" oor:type="xs:string">
                        <value>_self</value>
                    </prop>
                    <prop oor:name="Context" oor:type="xs:string">
                        <value>com.sun.star.text.TextDocument,com.sun.star.text.GlobalDocument,com.sun.star.text.WebDocument</value>
                    </prop>
                </node>
                <node oor:name="m5" oor:op="replace">
                    <prop oor:name="URL" oor:type="xs:string">
                        <value>private:separator</value>
                    </prop>
                    <prop oor:name="Context" oor:type="xs:string">
                        <value>com.sun.star.text.TextDocument,com.sun.star.text.GlobalDocument,com.sun.star.text.WebDocument,com.sun.star.presentation.PresentationDocument</value>
                    </prop>
                </node>
                <node oor:name="m6" oor:op="replace">
                    <prop oor:name="URL" oor:type="xs:string">
                        <value>service:net.grammalecte.AppLauncher?OP</value>
                    </prop>
                    <!--<prop oor:name="URL" oor:type="xs:string">
                        <value>.uno:OptionsTreeDialog?OptionsPageURL:string=%origin%/dialog/fr.xdl</value>
                    </prop>-->
                    <prop oor:name="Title" oor:type="xs:string">
 | 
| ︙ | ︙ | |||
115 116 117 118 119 120 121  | 
                    <prop oor:name="Context" oor:type="xs:string">
                        <value>com.sun.star.text.TextDocument,com.sun.star.text.GlobalDocument,com.sun.star.text.WebDocument</value>
                    </prop>
                    <prop oor:name="ImageIdentifier" oor:type="xs:string">
                        <value>org.dicollecte.images:Grammalecte</value>
                    </prop>
                </node>
 | | | | |  | 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191  | 
                    <prop oor:name="Context" oor:type="xs:string">
                        <value>com.sun.star.text.TextDocument,com.sun.star.text.GlobalDocument,com.sun.star.text.WebDocument</value>
                    </prop>
                    <prop oor:name="ImageIdentifier" oor:type="xs:string">
                        <value>org.dicollecte.images:Grammalecte</value>
                    </prop>
                </node>
                <node oor:name="m7" oor:op="replace">
                    <prop oor:name="URL" oor:type="xs:string">
                        <value>service:net.grammalecte.AppLauncher?DI</value>
                    </prop>
                    <prop oor:name="Title" oor:type="xs:string">
                        <value/>
                        <value xml:lang="fr">~Options des dictionnaires…</value>
                        <value xml:lang="en-US">Dictionaries ~options…</value>
                    </prop>
                    <prop oor:name="Target" oor:type="xs:string">
                        <value>_self</value>
                    </prop>
                    <prop oor:name="Context" oor:type="xs:string">
                        <value>com.sun.star.text.TextDocument,com.sun.star.text.GlobalDocument,com.sun.star.text.WebDocument,com.sun.star.presentation.PresentationDocument</value>
                    </prop>
                    <prop oor:name="ImageIdentifier" oor:type="xs:string">
                        <value>org.dicollecte.images:Frenchflag</value>
                    </prop>
                </node>
                <node oor:name="m8" oor:op="replace">
                    <prop oor:name="URL" oor:type="xs:string">
                        <value>service:net.grammalecte.AppLauncher?DS</value>
                    </prop>
                    <prop oor:name="Title" oor:type="xs:string">
                        <value/>
                        <value xml:lang="fr">~Options orthographiques…</value>
                        <value xml:lang="en-US">Spelling ~options…</value>
                    </prop>
                    <prop oor:name="Target" oor:type="xs:string">
                        <value>_self</value>
                    </prop>
                    <prop oor:name="Context" oor:type="xs:string">
                        <value>com.sun.star.text.TextDocument,com.sun.star.text.GlobalDocument,com.sun.star.text.WebDocument,com.sun.star.presentation.PresentationDocument</value>
                    </prop>
                    <prop oor:name="ImageIdentifier" oor:type="xs:string">
                        <value>org.dicollecte.images:Frenchflag</value>
                    </prop>
                </node>
                <node oor:name="m9" oor:op="replace">
                    <prop oor:name="URL" oor:type="xs:string">
                        <value>private:separator</value>
                    </prop>
                    <prop oor:name="Context" oor:type="xs:string">
                        <value>com.sun.star.text.TextDocument,com.sun.star.text.GlobalDocument,com.sun.star.text.WebDocument,com.sun.star.presentation.PresentationDocument</value>
                    </prop>
                </node>
                <node oor:name="m10" oor:op="replace">
                    <prop oor:name="URL" oor:type="xs:string">
                        <value>service:net.grammalecte.AppLauncher?About</value>
                    </prop>
                    <prop oor:name="Title" oor:type="xs:string">
                        <value/>
                        <value xml:lang="fr">À ~propos de Grammalecte…</value>
                        <value xml:lang="en-US">~About Grammalecte…</value>
 | 
| ︙ | ︙ | 
Modified gc_lang/fr/perf_memo.txt from [15962af16c] to [b880c3770c].
| ︙ | ︙ | |||
19 20 21 22 23 24 25  | 0.5.12 2016.10.14 18:58 4.51895 1.0843 0.772805 0.22387 0.249411 0.261593 0.628802 0.339303 0.0570326 0.00805416 0.5.15 2017.01.22 11:44 4.85204 1.16134 0.770762 0.227874 0.244574 0.253305 0.58831 0.319987 0.0603996 0.00694786 0.5.15 2017.01.22 11:47 4.85593 1.15248 0.762924 0.22744 0.243461 0.254609 0.586741 0.317503 0.0588827 0.00701016 (unicode normalisation NFC) 0.5.15 2017.01.31 12:06 4.88227 1.18008 0.782217 0.232617 0.247672 0.257628 0.596903 0.32169 0.0603505 0.00695196 0.5.15 2017.02.05 10:10 4.90222 1.18444 0.786696 0.233413 0.25071 0.260214 0.602112 0.325235 0.0609932 0.00706897 0.5.16 2017.05.12 07:41 4.92201 1.19269 0.80639 0.239147 0.257518 0.266523 0.62111 0.33359 0.0634668 0.00757178 0.6.1 2018.02.12 09:58 5.25924 1.2649 0.878442 0.257465 0.280558 0.293903 0.686887 0.391275 0.0672474 0.00824723  | |  | 19 20 21 22 23 24 25 26  | 0.5.12 2016.10.14 18:58 4.51895 1.0843 0.772805 0.22387 0.249411 0.261593 0.628802 0.339303 0.0570326 0.00805416 0.5.15 2017.01.22 11:44 4.85204 1.16134 0.770762 0.227874 0.244574 0.253305 0.58831 0.319987 0.0603996 0.00694786 0.5.15 2017.01.22 11:47 4.85593 1.15248 0.762924 0.22744 0.243461 0.254609 0.586741 0.317503 0.0588827 0.00701016 (unicode normalisation NFC) 0.5.15 2017.01.31 12:06 4.88227 1.18008 0.782217 0.232617 0.247672 0.257628 0.596903 0.32169 0.0603505 0.00695196 0.5.15 2017.02.05 10:10 4.90222 1.18444 0.786696 0.233413 0.25071 0.260214 0.602112 0.325235 0.0609932 0.00706897 0.5.16 2017.05.12 07:41 4.92201 1.19269 0.80639 0.239147 0.257518 0.266523 0.62111 0.33359 0.0634668 0.00757178 0.6.1 2018.02.12 09:58 5.25924 1.2649 0.878442 0.257465 0.280558 0.293903 0.686887 0.391275 0.0672474 0.00824723 0.6.2 2018.02.19 19:06 5.51302 1.29359 0.874157 0.260415 0.271596 0.290641 0.684754 0.376905 0.0815201 0.00919633 (spelling normalization)  | 
Modified gc_lang/fr/webext/gce_worker.js from [c20f81d8f3] to [efd11a103b].
| ︙ | ︙ | |||
200 201 202 203 204 205 206  | 
}
function parseAndSpellcheck (sText, sCountry, bDebug, bContext, dInfo={}) {
    let i = 0;
    sText = sText.replace(//g, "").normalize("NFC");
    for (let sParagraph of text.getParagraph(sText)) {
        let aGrammErr = gc_engine.parse(sParagraph, sCountry, bDebug, bContext);
 | | |  | 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224  | 
}
function parseAndSpellcheck (sText, sCountry, bDebug, bContext, dInfo={}) {
    let i = 0;
    sText = sText.replace(//g, "").normalize("NFC");
    for (let sParagraph of text.getParagraph(sText)) {
        let aGrammErr = gc_engine.parse(sParagraph, sCountry, bDebug, bContext);
        let aSpellErr = oSpellChecker.parseParagraph(sParagraph);
        postMessage(createResponse("parseAndSpellcheck", {sParagraph: sParagraph, iParaNum: i, aGrammErr: aGrammErr, aSpellErr: aSpellErr}, dInfo, false));
        i += 1;
    }
    postMessage(createResponse("parseAndSpellcheck", null, dInfo, true));
}
function parseAndSpellcheck1 (sParagraph, sCountry, bDebug, bContext, dInfo={}) {
    sParagraph = sParagraph.replace(//g, "").normalize("NFC");
    let aGrammErr = gc_engine.parse(sParagraph, sCountry, bDebug, bContext);
    let aSpellErr = oSpellChecker.parseParagraph(sParagraph);
    postMessage(createResponse("parseAndSpellcheck1", {sParagraph: sParagraph, aGrammErr: aGrammErr, aSpellErr: aSpellErr}, dInfo, true));
}
function getOptions (dInfo={}) {
    postMessage(createResponse("getOptions", gc_engine.getOptions(), dInfo, true));
}
 | 
| ︙ | ︙ | 
Modified grammalecte-cli.py from [d66b04b565] to [150bfbed66].
1 2 3 4 5 6 7  | #!/usr/bin/env python3 import sys import os.path import argparse import json  | < | < <  | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16  | 
#!/usr/bin/env python3
import sys
import os.path
import argparse
import json
import grammalecte
import grammalecte.text as txt
from grammalecte.graphspell.echo import echo
_EXAMPLE = "Quoi ? Racontes ! Racontes-moi ! Bon sangg, parles ! Oui. Il y a des menteur partout. " \
           "Je suit sidéré par la brutales arrogance de cette homme-là. Quelle salopard ! Un escrocs de la pire espece. " \
           "Quant sera t’il châtiés pour ses mensonge ?             Merde ! J’en aie marre."
 | 
| ︙ | ︙ | |||
40 41 42 43 44 45 46  | 
    if sys.platform == "win32":
        # Apparently, the console transforms «’» in «'».
        # So we reverse it to avoid many useless warnings.
        sText = sText.replace("'", "’")
    return sText
 | < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < | | | | > > > > | > | | | | > | | > | > | | > > > > | | < < < < < > | | > > > > > | | | > | | | < < | | | | < > | | < | < < < < < < < | < < < < < < | | | | | | | | | | | | | | | | |  | 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241  | 
    if sys.platform == "win32":
        # Apparently, the console transforms «’» in «'».
        # So we reverse it to avoid many useless warnings.
        sText = sText.replace("'", "’")
    return sText
def readFile (spf):
    "generator: returns file line by line"
    if os.path.isfile(spf):
        with open(spf, "r", encoding="utf-8") as hSrc:
            for sLine in hSrc:
                yield sLine
    else:
        print("# Error: file <" + spf + ">not found.")
def generateParagraphFromFile (spf, bConcatLines=False):
    "generator: returns text by tuple of (iParagraph, sParagraph, lLineSet)"
    if not bConcatLines:
        for iParagraph, sLine in enumerate(readFile(spf), 1):
            yield iParagraph, sLine, None
    else:
        lLine = []
        iParagraph = 1
        for iLine, sLine in enumerate(readFile(spf), 1):
            if sLine.strip():
                lLine.append((iLine, sLine))
            elif lLine:
                sText, lLineSet = txt.createParagraphWithLines(lLine)
                yield iParagraph, sText, lLineSet
                lLine = []
            iParagraph += 1
        if lLine:
            sText, lLineSet = txt.createParagraphWithLines(lLine)
            yield iParagraph, sText, lLineSet
def output (sText, hDst=None):
    if not hDst:
        echo(sText, end="")
    else:
        hDst.write(sText)
def main ():
    xParser = argparse.ArgumentParser()
    xParser.add_argument("-f", "--file", help="parse file (UTF-8 required!) [on Windows, -f is similar to -ff]", type=str)
    xParser.add_argument("-ff", "--file_to_file", help="parse file (UTF-8 required!) and create a result file (*.res.txt)", type=str)
    xParser.add_argument("-owe", "--only_when_errors", help="display results only when there are errors", action="store_true")
    xParser.add_argument("-j", "--json", help="generate list of errors in JSON (only with option --file or --file_to_file)", action="store_true")
    xParser.add_argument("-cl", "--concat_lines", help="concatenate lines not separated by an empty paragraph (only with option --file or --file_to_file)", action="store_true")
    xParser.add_argument("-tf", "--textformatter", help="auto-format text according to typographical rules (not with option --concat_lines)", action="store_true")
    xParser.add_argument("-tfo", "--textformatteronly", help="auto-format text and disable grammar checking (only with option --file or --file_to_file)", action="store_true")
    xParser.add_argument("-ctx", "--context", help="return errors with context (only with option --json)", action="store_true")
    xParser.add_argument("-wss", "--with_spell_sugg", help="add suggestions for spelling errors (only with option --file or --file_to_file)", action="store_true")
    xParser.add_argument("-w", "--width", help="width in characters (40 < width < 200; default: 100)", type=int, choices=range(40,201,10), default=100)
    xParser.add_argument("-lo", "--list_options", help="list options", action="store_true")
    xParser.add_argument("-lr", "--list_rules", nargs="?", help="list rules [regex pattern as filter]", const="*")
    xParser.add_argument("-sug", "--suggest", help="get suggestions list for given word", type=str)
    xParser.add_argument("-on", "--opt_on", nargs="+", help="activate options")
    xParser.add_argument("-off", "--opt_off", nargs="+", help="deactivate options")
    xParser.add_argument("-roff", "--rule_off", nargs="+", help="deactivate rules")
    xParser.add_argument("-d", "--debug", help="debugging mode (only in interactive mode)", action="store_true")
    xArgs = xParser.parse_args()
    oGrammarChecker = grammalecte.GrammarChecker("fr")
    oSpellChecker = oGrammarChecker.getSpellChecker()
    oLexicographer = oGrammarChecker.getLexicographer()
    oTextFormatter = oGrammarChecker.getTextFormatter()
    if not xArgs.json:
        echo("Grammalecte v{}".format(oGrammarChecker.gce.version))
    # list options or rules
    if xArgs.list_options or xArgs.list_rules:
        if xArgs.list_options:
            oGrammarChecker.gce.displayOptions("fr")
        if xArgs.list_rules:
            oGrammarChecker.gce.displayRules(None  if xArgs.list_rules == "*"  else xArgs.list_rules)
        exit()
    # spell suggestions
    if xArgs.suggest:
        for lSugg in oSpellChecker.suggest(xArgs.suggest):
            if xArgs.json:
                sText = json.dumps({ "aSuggestions": lSugg }, ensure_ascii=False)
            else:
                sText = "Suggestions : " + " | ".join(lSugg)
            echo(sText)
        exit()
    # disable options
    if not xArgs.json:
        xArgs.context = False
    if xArgs.concat_lines:
        xArgs.textformatter = False
    # grammar options
    oGrammarChecker.gce.setOptions({"html": True, "latex": True})
    if xArgs.opt_on:
        oGrammarChecker.gce.setOptions({ opt:True  for opt in xArgs.opt_on  if opt in oGrammarChecker.gce.getOptions() })
    if xArgs.opt_off:
        oGrammarChecker.gce.setOptions({ opt:False  for opt in xArgs.opt_off  if opt in oGrammarChecker.gce.getOptions() })
    # disable grammar rules
    if xArgs.rule_off:
        for sRule in xArgs.rule_off:
            oGrammarChecker.gce.ignoreRule(sRule)
    sFile = xArgs.file or xArgs.file_to_file
    if sFile:
        # file processing
        hDst = open(sFile[:sFile.rfind(".")]+".res.txt", "w", encoding="utf-8", newline="\n")  if xArgs.file_to_file or sys.platform == "win32"  else None
        bComma = False
        if xArgs.json:
            output('{ "grammalecte": "'+oGrammarChecker.gce.version+'", "lang": "'+oGrammarChecker.gce.lang+'", "data" : [\n', hDst)
        for i, sText, lLineSet in generateParagraphFromFile(sFile, xArgs.concat_lines):
            if xArgs.textformatter or xArgs.textformatteronly:
                sText = oTextFormatter.formatText(sText)
            if xArgs.textformatteronly:
                output(sText, hDst)
                continue
            if xArgs.json:
                sText = oGrammarChecker.generateParagraphAsJSON(i, sText, bContext=xArgs.context, bEmptyIfNoErrors=xArgs.only_when_errors, \
                                                                bSpellSugg=xArgs.with_spell_sugg, bReturnText=xArgs.textformatter, lLineSet=lLineSet)
            else:
                sText = oGrammarChecker.generateParagraph(sText, bEmptyIfNoErrors=xArgs.only_when_errors, bSpellSugg=xArgs.with_spell_sugg, nWidth=xArgs.width)
            if sText:
                if xArgs.json and bComma:
                    output(",\n", hDst)
                output(sText, hDst)
                bComma = True
            if hDst:
                echo("§ %d\r" % i, end="", flush=True)
        if xArgs.json:
            output("\n]}\n", hDst)
    else:
        # pseudo-console
        sInputText = "\n~==========~ Enter your text [/h /q] ~==========~\n"
        sText = _getText(sInputText)
        while True:
            if sText.startswith("?"):
                for sWord in sText[1:].strip().split():
                    if sWord:
                        echo("* " + sWord)
                        for sMorph in oSpellChecker.getMorph(sWord):
                            echo("  {:<32} {}".format(sMorph, oLexicographer.formatTags(sMorph)))
            elif sText.startswith("!"):
                for sWord in sText[1:].strip().split():
                    if sWord:
                        for lSugg in oSpellChecker.suggest(sWord):
                            echo(" | ".join(lSugg))
            elif sText.startswith(">"):
                oSpellChecker.drawPath(sText[1:].strip())
            elif sText.startswith("="):
                for sRes in oSpellChecker.select(sText[1:].strip()):
                    echo(sRes)
            elif sText.startswith("/+ "):
                oGrammarChecker.gce.setOptions({ opt:True  for opt in sText[3:].strip().split()  if opt in oGrammarChecker.gce.getOptions() })
                echo("done")
            elif sText.startswith("/- "):
                oGrammarChecker.gce.setOptions({ opt:False  for opt in sText[3:].strip().split()  if opt in oGrammarChecker.gce.getOptions() })
                echo("done")
            elif sText.startswith("/-- "):
                for sRule in sText[3:].strip().split():
                    oGrammarChecker.gce.ignoreRule(sRule)
                echo("done")
            elif sText.startswith("/++ "):
                for sRule in sText[3:].strip().split():
                    oGrammarChecker.gce.reactivateRule(sRule)
                echo("done")
            elif sText == "/debug" or sText == "/d":
                xArgs.debug = not(xArgs.debug)
                echo("debug mode on"  if xArgs.debug  else "debug mode off")
            elif sText == "/textformatter" or sText == "/tf":
                xArgs.textformatter = not(xArgs.textformatter)
                echo("textformatter on"  if xArgs.debug  else "textformatter off")
            elif sText == "/help" or sText == "/h":
                echo(_HELP)
            elif sText == "/lopt" or sText == "/lo":
                oGrammarChecker.gce.displayOptions("fr")
            elif sText.startswith("/lr"):
                sText = sText.strip()
                sFilter = sText[sText.find(" "):].strip()  if sText != "/lr" and sText != "/rules"  else None
                oGrammarChecker.gce.displayRules(sFilter)
            elif sText == "/quit" or sText == "/q":
                break
            elif sText.startswith("/rl"):
                # reload (todo)
                pass
            else:
                for sParagraph in txt.getParagraph(sText):
                    if xArgs.textformatter:
                        sText = oTextFormatter.formatText(sText)
                    sRes = oGrammarChecker.generateParagraph(sText, bEmptyIfNoErrors=xArgs.only_when_errors, nWidth=xArgs.width, bDebug=xArgs.debug)
                    if sRes:
                        echo("\n" + sRes)
                    else:
                        echo("\nNo error found.")
            sText = _getText(sInputText)
if __name__ == '__main__':
    main()
 | 
Modified grammalecte-server.py from [3253a2b2ff] to [a5cc9d7be7].
1 2 3 4 5 6 7 8 9 10 11 12  | #!/usr/bin/env python3 import sys import os.path import argparse import json import traceback import configparser import time from bottle import Bottle, run, request, response, template, static_file  | < | < <  | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21  | 
 #!/usr/bin/env python3
import sys
import os.path
import argparse
import json
import traceback
import configparser
import time
from bottle import Bottle, run, request, response, template, static_file
import grammalecte
import grammalecte.text as txt
from grammalecte.graphspell.echo import echo
HOMEPAGE = """
<!DOCTYPE HTML>
<html>
    <head>
 | 
| ︙ | ︙ | |||
125 126 127 128 129 130 131  | 
def genUserId ():
    i = 0
    while True:
        yield str(i)
        i += 1
 | > | | | | < | < < < < | | < < < < <  | 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151  | 
def genUserId ():
    i = 0
    while True:
        yield str(i)
        i += 1
if __name__ == '__main__':
    # initialisation
    oGrammarChecker = grammalecte.GrammarChecker("fr", "Server")
    oSpellChecker = oGrammarChecker.getSpellChecker()
    oLexicographer = oGrammarChecker.getLexicographer()
    oTextFormatter = oGrammarChecker.getTextFormatter()
    gce = oGrammarChecker.getGCEngine()
    echo("Grammalecte v{}".format(gce.version))
    dServerOptions = getServerOptions()
    dGCOptions = getConfigOptions("fr")
    if dGCOptions:
        gce.setOptions(dGCOptions)
    dServerGCOptions = gce.getOptions()
    echo("Grammar options:\n" + " | ".join([ k + ": " + str(v)  for k, v in sorted(dServerGCOptions.items()) ]))
    dUser = {}
    userGenerator = genUserId()
    app = Bottle()
    # GET
    @app.route("/")
 | 
| ︙ | ︙ | |||
176 177 178 179 180 181 182  | 
    # POST
    @app.route("/gc_text/fr", method="POST")
    def gcText ():
        #if len(lang) != 2 or lang != "fr":
        #    abort(404, "No grammar checker available for lang “" + str(lang) + "”")
        bComma = False
 | < | | |  | 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196  | 
    # POST
    @app.route("/gc_text/fr", method="POST")
    def gcText ():
        #if len(lang) != 2 or lang != "fr":
        #    abort(404, "No grammar checker available for lang “" + str(lang) + "”")
        bComma = False
        dOptions = None
        sError = ""
        if request.cookies.user_id:
            if request.cookies.user_id in dUser:
                dOptions = dUser[request.cookies.user_id].get("gc_options", None)
                response.set_cookie("user_id", request.cookies.user_id, path="/", max_age=86400) # we renew cookie for 24h
            else:
                response.delete_cookie("user_id", path="/")
        if request.forms.options:
            try:
                dOptions = dict(dServerGCOptions)  if not dOptions  else dict(dOptions)
                dOptions.update(json.loads(request.forms.options))
            except:
                sError = "request options not used"
        sJSON = '{ "program": "grammalecte-fr", "version": "'+gce.version+'", "lang": "'+gce.lang+'", "error": "'+sError+'", "data" : [\n'
        for i, sText in enumerate(txt.getParagraph(request.forms.text), 1):
            if bool(request.forms.tf):
                sText = oTextFormatter.formatText(sText)
            sText = oGrammarChecker.generateParagraphAsJSON(i, sText, dOptions=dOptions, bEmptyIfNoErrors=True, bReturnText=bool(request.forms.tf))
            if sText:
                if bComma:
                    sJSON += ",\n"
                sJSON += sText
                bComma = True
        sJSON += "\n]}\n"
        return sJSON
 | 
| ︙ | ︙ | |||
227 228 229 230 231 232 233  | 
    def resetOptions ():
        if request.cookies.user_id and request.cookies.user_id in dUser:
            del dUser[request.cookies.user_id]
        return "done"
    @app.route("/format_text/fr", method="POST")
    def formatText ():
 | |  | 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228  | 
    def resetOptions ():
        if request.cookies.user_id and request.cookies.user_id in dUser:
            del dUser[request.cookies.user_id]
        return "done"
    @app.route("/format_text/fr", method="POST")
    def formatText ():
        return oTextFormatter.formatText(request.forms.text)
    #@app.route('/static/<filepath:path>')
    #def server_static (filepath):
    #    return static_file(filepath, root='./views/static')
    @app.route("/purge_users", method="POST")
    def purgeUsers ():
 | 
| ︙ | ︙ | 
Modified graphspell-js/spellchecker.js from [e878cd2181] to [7b8a526c88].
| ︙ | ︙ | |||
9 10 11 12 13 14 15 16 17 18 19 20 21 22  | 
"use strict";
if (typeof(require) !== 'undefined') {
    var ibdawg = require("resource://grammalecte/graphspell/ibdawg.js");
}
${map}
const dDefaultDictionaries = new Map([
 | >  | 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23  | 
"use strict";
if (typeof(require) !== 'undefined') {
    var ibdawg = require("resource://grammalecte/graphspell/ibdawg.js");
    var tokenizer = require("resource://grammalecte/graphspell/tokenizer.js");
}
${map}
const dDefaultDictionaries = new Map([
 | 
| ︙ | ︙ | |||
32 33 34 35 36 37 38 39 40 41 42 43 44 45 46  | 
        this.sLangCode = sLangCode;
        if (!mainDic) {
            mainDic = dDefaultDictionaries.gl_get(sLangCode, "");
        }
        this.oMainDic = this._loadDictionary(mainDic, sPath, true);
        this.oExtendedDic = this._loadDictionary(extentedDic, sPath);
        this.oPersonalDic = this._loadDictionary(personalDic, sPath);
    }
    _loadDictionary (dictionary, sPath, bNecessary=false) {
        // returns an IBDAWG object
        if (!dictionary) {
            return null;
        }
        try {
 | > | > > > > > > > > > > > > > > > > > > > > > > >  | 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112  | 
        this.sLangCode = sLangCode;
        if (!mainDic) {
            mainDic = dDefaultDictionaries.gl_get(sLangCode, "");
        }
        this.oMainDic = this._loadDictionary(mainDic, sPath, true);
        this.oExtendedDic = this._loadDictionary(extentedDic, sPath);
        this.oPersonalDic = this._loadDictionary(personalDic, sPath);
        this.oTokenizer = null;
    }
    _loadDictionary (dictionary, sPath, bNecessary=false) {
        // returns an IBDAWG object
        if (!dictionary) {
            return null;
        }
        try {
            if (typeof(ibdawg) !== 'undefined') {
                return new ibdawg.IBDAWG(dictionary);  // dictionary can be a filename or a JSON object
            } else {
                return new IBDAWG(dictionary, sPath);  // dictionary can be a filename or a JSON object
            }
        }
        catch (e) {
            let sfDictionary = (typeof(dictionary) == "string") ? dictionary : dictionary.sLangName + "/" + dictionary.sFileName;
            if (bNecessary) {
                throw "Error: <" + sfDictionary + "> not loaded. " + e.message;
            }
            console.log("Error: <" + sfDictionary + "> not loaded.")
            console.log(e.message);
            return null;
        }
    }
    loadTokenizer () {
        if (typeof(tokenizer) !== 'undefined') {
            this.oTokenizer = new tokenizer.Tokenizer(this.sLangCode);
        } else {
            this.oTokenizer = new Tokenizer(this.sLangCode);
        }
    }
    setMainDictionary (dictionary) {
        // returns true if the dictionary is loaded
        this.oMainDic = this._loadDictionary(dictionary);
        return Boolean(this.oMainDic);
    }
    setExtendedDictionary (dictionary) {
        // returns true if the dictionary is loaded
        this.oExtendedDic = this._loadDictionary(dictionary);
        return Boolean(this.oExtendedDic);
    }
    setPersonalDictionary (dictionary) {
        // returns true if the dictionary is loaded
        this.oPersonalDic = this._loadDictionary(dictionary);
        return Boolean(this.oPersonalDic);
    }
    // parse text functions
    parseParagraph (sText) {
        if (!this.oTokenizer) {
            this.loadTokenizer();
        }
        let aSpellErr = [];
        for (let oToken of this.oTokenizer.genTokens(sText)) {
            if (oToken.sType === 'WORD' && !this.isValidToken(oToken.sValue)) {
                aSpellErr.push(oToken);
            }
        }
        return aSpellErr;
    }
    // IBDAWG functions
    isValidToken (sToken) {
        // checks if sToken is valid (if there is hyphens in sToken, sToken is split, each part is checked)
        if (this.oMainDic.isValidToken(sToken)) {
            return true;
 | 
| ︙ | ︙ | 
Modified graphspell-js/tokenizer.js from [c3f0ee8c90] to [bdd895b918].
| ︙ | ︙ | |||
83 84 85 86 87 88 89  | 
                    helpers.logerror(e);
                }
            }
            i += nCut;
            sText = sText.slice(nCut);
        }
    }
 | < < < < < < < < < <  | 83 84 85 86 87 88 89 90 91 92 93 94 95  | 
                    helpers.logerror(e);
                }
            }
            i += nCut;
            sText = sText.slice(nCut);
        }
    }
}
if (typeof(exports) !== 'undefined') {
    exports.Tokenizer = Tokenizer;
}
 | 
Modified graphspell/__init__.py from [a7ffc6f8bf] to [a53bdfb757].
> >  | 1 2  | from .spellchecker import *  | 
Modified graphspell/spellchecker.py from [638f8d8cdf] to [dbd02131cc].
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67  | 
# Spellchecker
# Wrapper for the IBDAWG class.
# Useful to check several dictionaries at once.
# To avoid iterating over a pile of dictionaries, it is assumed that 3 are enough:
# - the main dictionary, bundled with the package
# - the extended dictionary, added by an organization
# - the personal dictionary, created by the user for its own convenience
import traceback
from . import ibdawg
dDefaultDictionaries = {
    "fr": "fr.bdic",
    "en": "en.bdic"
}
class SpellChecker ():
    def __init__ (self, sLangCode, sfMainDic="", sfExtendedDic="", sfPersonalDic=""):
        "returns True if the main dictionary is loaded"
        self.sLangCode = sLangCode
        if not sfMainDic:
            sfMainDic = dDefaultDictionaries.get(sLangCode, "")
        self.oMainDic = self._loadDictionary(sfMainDic, True)
        self.oExtendedDic = self._loadDictionary(sfExtendedDic)
        self.oPersonalDic = self._loadDictionary(sfPersonalDic)
    def _loadDictionary (self, sfDictionary, bNecessary=False):
        "returns an IBDAWG object"
        if not sfDictionary:
            return None
        try:
            return ibdawg.IBDAWG(sfDictionary)
        except Exception as e:
            if bNecessary:
                raise Exception(str(e), "Error: <" + sfDictionary + "> not loaded.")
            print("Error: <" + sfDictionary + "> not loaded.")
            traceback.print_exc()
            return None
    def setMainDictionary (self, sfDictionary):
        "returns True if the dictionary is loaded"
        self.oMainDic = self._loadDictionary(sfDictionary)
        return bool(self.oMainDic)
            
    def setExtendedDictionary (self, sfDictionary):
        "returns True if the dictionary is loaded"
        self.oExtendedDic = self._loadDictionary(sfDictionary)
        return bool(self.oExtendedDic)
    def setPersonalDictionary (self, sfDictionary):
        "returns True if the dictionary is loaded"
        self.oPersonalDic = self._loadDictionary(sfDictionary)
        return bool(self.oPersonalDic)
    # IBDAWG functions
    def isValidToken (self, sToken):
        "checks if sToken is valid (if there is hyphens in sToken, sToken is split, each part is checked)"
        if self.oMainDic.isValidToken(sToken):
            return True
 | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > >  | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102  | 
# Spellchecker
# Wrapper for the IBDAWG class.
# Useful to check several dictionaries at once.
# To avoid iterating over a pile of dictionaries, it is assumed that 3 are enough:
# - the main dictionary, bundled with the package
# - the extended dictionary, added by an organization
# - the personal dictionary, created by the user for its own convenience
import traceback
from . import ibdawg
from . import tokenizer
dDefaultDictionaries = {
    "fr": "fr.bdic",
    "en": "en.bdic"
}
class SpellChecker ():
    def __init__ (self, sLangCode, sfMainDic="", sfExtendedDic="", sfPersonalDic=""):
        "returns True if the main dictionary is loaded"
        self.sLangCode = sLangCode
        if not sfMainDic:
            sfMainDic = dDefaultDictionaries.get(sLangCode, "")
        self.oMainDic = self._loadDictionary(sfMainDic, True)
        self.oExtendedDic = self._loadDictionary(sfExtendedDic)
        self.oPersonalDic = self._loadDictionary(sfPersonalDic)
        self.oTokenizer = None
    def _loadDictionary (self, sfDictionary, bNecessary=False):
        "returns an IBDAWG object"
        if not sfDictionary:
            return None
        try:
            return ibdawg.IBDAWG(sfDictionary)
        except Exception as e:
            if bNecessary:
                raise Exception(str(e), "Error: <" + sfDictionary + "> not loaded.")
            print("Error: <" + sfDictionary + "> not loaded.")
            traceback.print_exc()
            return None
    def loadTokenizer (self):
        self.oTokenizer = tokenizer.Tokenizer(self.sLangCode)
    def setMainDictionary (self, sfDictionary):
        "returns True if the dictionary is loaded"
        self.oMainDic = self._loadDictionary(sfDictionary)
        return bool(self.oMainDic)
            
    def setExtendedDictionary (self, sfDictionary):
        "returns True if the dictionary is loaded"
        self.oExtendedDic = self._loadDictionary(sfDictionary)
        return bool(self.oExtendedDic)
    def setPersonalDictionary (self, sfDictionary):
        "returns True if the dictionary is loaded"
        self.oPersonalDic = self._loadDictionary(sfDictionary)
        return bool(self.oPersonalDic)
    # parse text functions
    def parseParagraph (self, sText, bSpellSugg=False):
        if not self.oTokenizer:
            self.loadTokenizer()
        aSpellErrs = []
        for dToken in self.oTokenizer.genTokens(sText):
            if dToken['sType'] == "WORD" and not self.isValidToken(dToken['sValue']):
                if bSpellSugg:
                    dToken['aSuggestions'] = []
                    for lSugg in self.suggest(dToken['sValue']):
                        dToken['aSuggestions'].extend(lSugg)
                aSpellErrs.append(dToken)
        return aSpellErrs
    def countWordsOccurrences (self, sText, bByLemma=False, bOnlyUnknownWords=False, dWord={}):
        if not self.oTokenizer:
            self.loadTokenizer()
        for dToken in self.oTokenizer.genTokens(sText):
            if dToken['sType'] == "WORD":
                if bOnlyUnknownWords:
                    if not self.isValidToken(dToken['sValue']):
                        dWord[dToken['sValue']] = dWord.get(dToken['sValue'], 0) + 1
                else:
                    if not bByLemma:
                        dWord[dToken['sValue']] = dWord.get(dToken['sValue'], 0) + 1
                    else:
                        for sLemma in self.getLemma(dToken['sValue']):
                            dWord[sLemma] = dWord.get(sLemma, 0) + 1
        return dWord
    # IBDAWG functions
    def isValidToken (self, sToken):
        "checks if sToken is valid (if there is hyphens in sToken, sToken is split, each part is checked)"
        if self.oMainDic.isValidToken(sToken):
            return True
 | 
| ︙ | ︙ | |||
96 97 98 99 100 101 102 103 104 105 106 107 108 109  | 
        lResult = self.oMainDic.getMorph(sWord)
        if self.oExtendedDic:
            lResult.extend(self.oExtendedDic.getMorph(sWord))
        if self.oPersonalDic:
            lResult.extend(self.oPersonalDic.getMorph(sWord))
        return lResult
    def suggest (self, sWord, nSuggLimit=10):
        "generator: returns 1, 2 or 3 lists of suggestions"
        yield self.oMainDic.suggest(sWord, nSuggLimit)
        if self.oExtendedDic:
            yield self.oExtendedDic.suggest(sWord, nSuggLimit)
        if self.oPersonalDic:
            yield self.oPersonalDic.suggest(sWord, nSuggLimit)
 | > > >  | 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147  | 
        lResult = self.oMainDic.getMorph(sWord)
        if self.oExtendedDic:
            lResult.extend(self.oExtendedDic.getMorph(sWord))
        if self.oPersonalDic:
            lResult.extend(self.oPersonalDic.getMorph(sWord))
        return lResult
    def getLemma (self, sWord):
        return set([ s[1:s.find(" ")]  for s in self.getMorph(sWord) ])
    def suggest (self, sWord, nSuggLimit=10):
        "generator: returns 1, 2 or 3 lists of suggestions"
        yield self.oMainDic.suggest(sWord, nSuggLimit)
        if self.oExtendedDic:
            yield self.oExtendedDic.suggest(sWord, nSuggLimit)
        if self.oPersonalDic:
            yield self.oPersonalDic.suggest(sWord, nSuggLimit)
 | 
| ︙ | ︙ | 
Modified tests/fr/horla.res.txt from [036f6beeea] to [0417945727].
1 2 3 4 5 6 7 8 9 10  | 
Guy de Maupassant
Le Horla (1887)
   °°°°°
Le Horla, P. Ollendorff, 1895 [trente-cinquième édition] (pp. 3-68).
   °°°°°     °°°°°°°°°°
8 mai. – Quelle journée admirable ! J’ai passé toute la matinée étendu sur 
                                                                ^^^^^^
 | > | <  | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19  | 
Guy de Maupassant
Le Horla (1887)
   °°°°°
Le Horla, P. Ollendorff, 1895 [trente-cinquième édition] (pp. 3-68).
   °°°°°     °°°°°°°°°°
8 mai. – Quelle journée admirable ! J’ai passé toute la matinée étendu sur 
                                                                ^^^^^^
* 64:70  # 6628s / gn_2m_la:
  Accord de genre erroné : « matinée » est féminin, « étendu » est masculin.
  > Suggestions : étendue
l’herbe, devant ma maison, sous l’énorme platane qui la couvre, l’abrite et 
l’ombrage tout entière. J’aime ce pays, et j’aime y vivre parce que j’y ai mes 
racines, ces profondes et délicates racines, qui attachent un homme à la terre 
où sont nés et morts ses aïeux, qui l’attachent à ce qu’on pense et à ce qu’on 
mange, aux usages comme aux nourritures, aux locutions locales, aux intonations 
 | 
| ︙ | ︙ | |||
107 108 109 110 111 112 113  | 
tomberait pour s’y noyer, dans un gouffre d’eau stagnante. Je ne le sens pas 
venir, comme autrefois, ce sommeil perfide, caché près de moi, qui me guette, 
qui va me saisir par la tête, me fermer les yeux, m’anéantir.
Je dors – longtemps – deux ou trois heures – puis un rêve – non – un cauchemar 
m’étreint. Je sens bien que je suis couché et que je dors,… je le sens et je le 
                                                         ^^
 | > | <  | 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122  | 
tomberait pour s’y noyer, dans un gouffre d’eau stagnante. Je ne le sens pas 
venir, comme autrefois, ce sommeil perfide, caché près de moi, qui me guette, 
qui va me saisir par la tête, me fermer les yeux, m’anéantir.
Je dors – longtemps – deux ou trois heures – puis un rêve – non – un cauchemar 
m’étreint. Je sens bien que je suis couché et que je dors,… je le sens et je le 
                                                         ^^
* 136:138  # 704p / virg_virgule_avant_points_suspension:
  Typographie : pas de virgule avant les points de suspension.
  > Suggestions : …
sais… et je sens aussi que quelqu’un s’approche de moi, me regarde, me palpe, 
monte sur mon lit, s’agenouille sur ma poitrine, me prend le cou entre ses 
mains et serre… serre… de toute sa force pour m’étrangler.
Moi, je me débats, lié par cette impuissance atroce, qui nous paralyse dans les 
 | 
| ︙ | ︙ | |||
475 476 477 478 479 480 481  | 
— Oui, moi, ou plutôt mon mari, qui me charge de les trouver.
J’étais tellement stupéfait, que je balbutiais mes réponses. Je me demandais si 
vraiment elle ne s’était pas moquée de moi avec le docteur Parent, si ce 
n’était pas là une simple farce préparée d’avance et fort bien jouée.
                                ^^^^^^^^^^^^^^^^^
 | > |  | 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490  | 
— Oui, moi, ou plutôt mon mari, qui me charge de les trouver.
J’étais tellement stupéfait, que je balbutiais mes réponses. Je me demandais si 
vraiment elle ne s’était pas moquée de moi avec le docteur Parent, si ce 
n’était pas là une simple farce préparée d’avance et fort bien jouée.
                                ^^^^^^^^^^^^^^^^^
* 185:202  # 3020s / pleo_verbe_à_l_avance:
  Pléonasme.
  > Suggestions : préparée
Mais, en la regardant avec attention, tous mes doutes se dissipèrent. Elle 
tremblait d’angoisse, tant cette démarche lui était douloureuse, et je compris 
qu’elle avait la gorge pleine de sanglots.
 | 
| ︙ | ︙ | |||
584 585 586 587 588 589 590  | Puis il la réveilla. Je tirai de ma poche un portefeuille : — Voici, ma chère cousine, ce que vous m’avez demandé ce matin. Elle fut tellement surprise que je n’osai pas insister. J’essayai cependant de ranimer sa mémoire, mais elle nia avec force, crut que je me moquais d’elle, et faillit, à la fin, se fâcher.  | | | > > >  | 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611  | 
Puis il la réveilla. Je tirai de ma poche un portefeuille :
— Voici, ma chère cousine, ce que vous m’avez demandé ce matin.
Elle fut tellement surprise que je n’osai pas insister. J’essayai cependant de 
ranimer sa mémoire, mais elle nia avec force, crut que je me moquais d’elle, et 
faillit, à la fin, se fâcher.
· · · · · · · · · · · · · · · · · · · · · · · ·
Voilà ! je viens de rentrer ; et je n’ai pu déjeuner, tant cette expérience m’a 
bouleversé.
19 juillet. – Beaucoup de personnes à qui j’ai raconté cette aventure se sont 
moquées de moi. Je ne sais plus que penser. Le sage dit : Peut-être ?
21 juillet. – J’ai été dîner à Bougival, puis j’ai passé la soirée au bal des 
                ^^^^^^         °°°°°°°°
* 16:22  # 9891s / ppas_avoir_été:
  Tournure familière. Utilisez « être allé » plutôt que « avoir été ».
canotiers. Décidément, tout dépend des lieux et des milieux. Croire au 
surnaturel dans l’île de la Grenouillère, serait le comble de la folie… mais au 
sommet du mont Saint-Michel ?… mais dans les Indes ? Nous subissons 
effroyablement l’influence de ce qui nous entoure. Je rentrerai chez moi la 
semaine prochaine.
30 juillet. – Je suis revenu dans ma maison depuis hier. Tout va bien.
 | 
| ︙ | ︙ | |||
810 811 812 813 814 815 816  | 
s’éteignit, et ma fenêtre se ferma comme si un malfaiteur surpris se fût élancé 
dans la nuit, en prenant à pleines mains les battants.
Donc, il s’était sauvé ; il avait eu peur, peur de moi, lui !
Alors,… alors… demain… ou après,… ou un jour quelconque,… je pourrai donc le 
     ^^                        ^^                      ^^
 | > | > | > | > | | > | > > |  | 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871  | 
s’éteignit, et ma fenêtre se ferma comme si un malfaiteur surpris se fût élancé 
dans la nuit, en prenant à pleines mains les battants.
Donc, il s’était sauvé ; il avait eu peur, peur de moi, lui !
Alors,… alors… demain… ou après,… ou un jour quelconque,… je pourrai donc le 
     ^^                        ^^                      ^^
* 5:7  # 704p / virg_virgule_avant_points_suspension:
  Typographie : pas de virgule avant les points de suspension.
  > Suggestions : …
* 31:33  # 704p / virg_virgule_avant_points_suspension:
  Typographie : pas de virgule avant les points de suspension.
  > Suggestions : …
* 55:57  # 704p / virg_virgule_avant_points_suspension:
  Typographie : pas de virgule avant les points de suspension.
  > Suggestions : …
tenir sous mes poings, et l’écraser contre le sol ! Est-ce que les chiens, 
quelquefois, ne mordent point et n’étranglent pas leurs maîtres ?
18 août. – J’ai songé toute la journée. Oh ! oui, je vais lui obéir, suivre ses 
impulsions, accomplir toutes ses volontés, me faire humble, soumis, lâche. Il 
est le plus fort. Mais une heure viendra…
19 août. – Je sais… je sais… je sais tout ! Je viens de lire ceci dans la Revue 
du Monde scientifique : « Une nouvelle assez curieuse nous arrive de Rio de 
Janeiro. Une folie, une épidémie de folie, comparable aux démences contagieuses 
qui atteignirent les peuples d’Europe au moyen âge, sévit en ce moment dans la 
                                         ^^^^^^^^^
* 277:286  # 8968s / maj_Moyen_Âge:
  Le « Moyen Âge ».
  > Suggestions : Moyen Âge
province de San-Paulo. Les habitants éperdus quittent leurs maisons, désertent 
leurs villages, abandonnent leurs cultures, se disant poursuivis, possédés, 
gouvernés comme un bétail humain par des êtres invisibles bien que tangibles, 
des sortes de vampires qui se nourrissent de leur vie, pendant leur sommeil, et 
qui boivent en outre de l’eau et du lait sans paraître toucher à aucun autre 
aliment.
« M. le professeur Don Pedro Henriquez, accompagné de plusieurs savants 
                             °°°°°°°°°
médecins, est parti pour la province de San-Paulo, afin d’étudier sur place les 
origines et les manifestations de cette surprenante folie, et de proposer à 
l’Empereur les mesures qui lui paraîtront le plus propres à rappeler à la 
                                          ^^      ^^^^^^^
* 270:272  # 5835s / gn_le_accord2:
  Accord de nombre erroné : « propres » est au pluriel.
  > Suggestions : les
* 278:285  # 5835s / gn_le_accord2:
  Accord de nombre erroné : « propres » devrait être au singulier.
  > Suggestions : propre
raison ces populations en délire. »
Ah ! Ah ! je me rappelle, je me rappelle le beau trois-mâts brésilien qui passa 
sous mes fenêtres en remontant la Seine, le 8 mai dernier ! Je le trouvai si 
joli, si blanc, si gai ! L’Être était dessus, venant de là-bas, où sa race est 
 | 
| ︙ | ︙ | |||
935 936 937 938 939 940 941  | Mais direz-vous, le papillon ! une fleur qui vole ! J’en rêve un qui serait grand comme cent univers, avec des ailes dont je ne puis même exprimer la forme, la beauté, la couleur et le mouvement. Mais je le vois… il va d’étoile en étoile, les rafraîchissant et les embaumant au souffle harmonieux et léger de sa course !… Et les peuples de là-haut le regardent passer, extasiés et ravis !…  | |  | 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960  | 
Mais direz-vous, le papillon ! une fleur qui vole ! J’en rêve un qui serait 
grand comme cent univers, avec des ailes dont je ne puis même exprimer la 
forme, la beauté, la couleur et le mouvement. Mais je le vois… il va d’étoile 
en étoile, les rafraîchissant et les embaumant au souffle harmonieux et léger 
de sa course !… Et les peuples de là-haut le regardent passer, extasiés et 
ravis !…
· · · · · · · · · · · · · · · · · · · · · · · ·
Qu’ai-je donc ? C’est lui, lui, le Horla, qui me hante, qui me fait penser ces 
                                   °°°°°
folies ! Il est en moi, il devient mon âme ; je le tuerai !
19 août. – Je le tuerai. Je l’ai vu ! je me suis assis hier soir, à ma table ; 
et je fis semblant d’écrire avec une grande attention. Je savais bien qu’il 
 | 
| ︙ | ︙ | |||
994 995 996 997 998 999 1000  | alors ?… 21 août. – J’ai fait venir un serrurier de Rouen, et lui ai commandé pour ma chambre des persiennes de fer, comme en ont, à Paris, certains hôtels particuliers, au rez-de-chaussée, par crainte des voleurs. Il me fera, en outre, une porte pareille. Je me suis donné pour un poltron, mais je m’en moque !…  | |  | 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019  | alors ?… 21 août. – J’ai fait venir un serrurier de Rouen, et lui ai commandé pour ma chambre des persiennes de fer, comme en ont, à Paris, certains hôtels particuliers, au rez-de-chaussée, par crainte des voleurs. Il me fera, en outre, une porte pareille. Je me suis donné pour un poltron, mais je m’en moque !… · · · · · · · · · · · · · · · · · · · · · · · · 10 septembre. – Rouen, hôtel continental. C’est fait… c’est fait… mais est-il mort ? J’ai l’âme bouleversée de ce que j’ai vu. Hier donc, le serrurier ayant posé ma persienne et ma porte de fer, j’ai laissé tout ouvert jusqu’à minuit, bien qu’il commençât à faire froid.  | 
| ︙ | ︙ | |||
1024 1025 1026 1027 1028 1029 1030  | 
bien refermé, à double tour, la grande porte d’entrée.
Et j’allai me cacher au fond de mon jardin, dans un massif de lauriers. Comme 
ce fut long ! comme ce fut long ! Tout était noir, muet, immobile ; pas un 
souffle d’air, pas une étoile, des montagnes de nuages qu’on ne voyait point, 
mais qui pesaient sur mon âme si lourds, si lourds.
                                 ^^^^^^
 | > | <  | 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050  | 
bien refermé, à double tour, la grande porte d’entrée.
Et j’allai me cacher au fond de mon jardin, dans un massif de lauriers. Comme 
ce fut long ! comme ce fut long ! Tout était noir, muet, immobile ; pas un 
souffle d’air, pas une étoile, des montagnes de nuages qu’on ne voyait point, 
mais qui pesaient sur mon âme si lourds, si lourds.
                                 ^^^^^^
* 264:270  # 6602s / gn_2m_mon_ton_son:
  Accord de nombre erroné avec « âme » : « lourds » devrait être au singulier.
  > Suggestions : lourd
Je regardais ma maison, et j’attendais. Comme ce fut long ! Je croyais déjà que 
le feu s’était éteint tout seul, ou qu’il l’avait éteint, Lui, quand une des 
fenêtres d’en bas creva sous la poussée de l’incendie, et une flamme, une 
grande flamme rouge et jaune, longue, molle, caressante, monta le long du mur 
 | 
| ︙ | ︙ | 
Modified tests/fr/horla.txt from [4cbaa54e8b] to [44498933b9].
| ︙ | ︙ | |||
291 292 293 294 295 296 297  | — Votre mari n’a plus besoin de cinq mille francs ! Vous allez donc oublier que vous avez prié votre cousin de vous les prêter, et, s’il vous parle de cela, vous ne comprendrez pas. Puis il la réveilla. Je tirai de ma poche un portefeuille : — Voici, ma chère cousine, ce que vous m’avez demandé ce matin. Elle fut tellement surprise que je n’osai pas insister. J’essayai cependant de ranimer sa mémoire, mais elle nia avec force, crut que je me moquais d’elle, et faillit, à la fin, se fâcher.  | |  | 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305  | — Votre mari n’a plus besoin de cinq mille francs ! Vous allez donc oublier que vous avez prié votre cousin de vous les prêter, et, s’il vous parle de cela, vous ne comprendrez pas. Puis il la réveilla. Je tirai de ma poche un portefeuille : — Voici, ma chère cousine, ce que vous m’avez demandé ce matin. Elle fut tellement surprise que je n’osai pas insister. J’essayai cependant de ranimer sa mémoire, mais elle nia avec force, crut que je me moquais d’elle, et faillit, à la fin, se fâcher. · · · · · · · · · · · · · · · · · · · · · · · · Voilà ! je viens de rentrer ; et je n’ai pu déjeuner, tant cette expérience m’a bouleversé. 19 juillet. – Beaucoup de personnes à qui j’ai raconté cette aventure se sont moquées de moi. Je ne sais plus que penser. Le sage dit : Peut-être ? 21 juillet. – J’ai été dîner à Bougival, puis j’ai passé la soirée au bal des canotiers. Décidément, tout dépend des lieux et des milieux. Croire au surnaturel dans l’île de la Grenouillère, serait le comble de la folie… mais au sommet du mont Saint-Michel ?… mais dans les Indes ? Nous subissons effroyablement l’influence de ce qui nous entoure. Je rentrerai chez moi la semaine prochaine.  | 
| ︙ | ︙ | |||
404 405 406 407 408 409 410  | Un être nouveau ! pourquoi pas ? Il devait venir assurément ! pourquoi serions-nous les derniers ! Nous ne le distinguons point, ainsi que tous les autres créés avant nous ? C’est que sa nature est plus parfaite, son corps plus fin et plus fini que le nôtre, que le nôtre si faible, si maladroitement conçu, encombré d’organes toujours fatigués, toujours forcés comme des ressorts trop complexes, que le nôtre, qui vit comme une plante et comme une bête, en se nourrissant péniblement d’air, d’herbe et de viande, machine animale en proie aux maladies, aux déformations, aux putréfactions, poussive, mal réglée, naïve et bizarre, ingénieusement mal faite, œuvre grossière et délicate, ébauche d’être qui pourrait devenir intelligent et superbe. Nous sommes quelques-uns, si peu sur ce monde, depuis l’huître jusqu’à l’homme. Pourquoi pas un de plus, une fois accomplie la période qui sépare les apparitions successives de toutes les espèces diverses ? Pourquoi pas un de plus ? Pourquoi pas aussi d’autres arbres aux fleurs immenses, éclatantes et parfumant des régions entières ? Pourquoi pas d’autres éléments que le feu, l’air, la terre et l’eau ? – Ils sont quatre, rien que quatre, ces pères nourriciers des êtres ! Quelle pitié ! Pourquoi ne sont-ils pas quarante, quatre cents, quatre mille ! Comme tout est pauvre, mesquin, misérable ! avarement donné, sèchement inventé, lourdement fait ! Ah ! l’éléphant, l’hippopotame, que de grâce ! Le chameau que d’élégance ! Mais direz-vous, le papillon ! une fleur qui vole ! J’en rêve un qui serait grand comme cent univers, avec des ailes dont je ne puis même exprimer la forme, la beauté, la couleur et le mouvement. Mais je le vois… il va d’étoile en étoile, les rafraîchissant et les embaumant au souffle harmonieux et léger de sa course !… Et les peuples de là-haut le regardent passer, extasiés et ravis !…  | |  | 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418  | Un être nouveau ! pourquoi pas ? Il devait venir assurément ! pourquoi serions-nous les derniers ! Nous ne le distinguons point, ainsi que tous les autres créés avant nous ? C’est que sa nature est plus parfaite, son corps plus fin et plus fini que le nôtre, que le nôtre si faible, si maladroitement conçu, encombré d’organes toujours fatigués, toujours forcés comme des ressorts trop complexes, que le nôtre, qui vit comme une plante et comme une bête, en se nourrissant péniblement d’air, d’herbe et de viande, machine animale en proie aux maladies, aux déformations, aux putréfactions, poussive, mal réglée, naïve et bizarre, ingénieusement mal faite, œuvre grossière et délicate, ébauche d’être qui pourrait devenir intelligent et superbe. Nous sommes quelques-uns, si peu sur ce monde, depuis l’huître jusqu’à l’homme. Pourquoi pas un de plus, une fois accomplie la période qui sépare les apparitions successives de toutes les espèces diverses ? Pourquoi pas un de plus ? Pourquoi pas aussi d’autres arbres aux fleurs immenses, éclatantes et parfumant des régions entières ? Pourquoi pas d’autres éléments que le feu, l’air, la terre et l’eau ? – Ils sont quatre, rien que quatre, ces pères nourriciers des êtres ! Quelle pitié ! Pourquoi ne sont-ils pas quarante, quatre cents, quatre mille ! Comme tout est pauvre, mesquin, misérable ! avarement donné, sèchement inventé, lourdement fait ! Ah ! l’éléphant, l’hippopotame, que de grâce ! Le chameau que d’élégance ! Mais direz-vous, le papillon ! une fleur qui vole ! J’en rêve un qui serait grand comme cent univers, avec des ailes dont je ne puis même exprimer la forme, la beauté, la couleur et le mouvement. Mais je le vois… il va d’étoile en étoile, les rafraîchissant et les embaumant au souffle harmonieux et léger de sa course !… Et les peuples de là-haut le regardent passer, extasiés et ravis !… · · · · · · · · · · · · · · · · · · · · · · · · Qu’ai-je donc ? C’est lui, lui, le Horla, qui me hante, qui me fait penser ces folies ! Il est en moi, il devient mon âme ; je le tuerai ! 19 août. – Je le tuerai. Je l’ai vu ! je me suis assis hier soir, à ma table ; et je fis semblant d’écrire avec une grande attention. Je savais bien qu’il viendrait rôder autour de moi, tout près, si près que je pourrais peut-être le toucher, le saisir ? Et alors !… alors, j’aurais la force des désespérés ; j’aurais mes mains, mes genoux, ma poitrine, mon front, mes dents pour l’étrangler, l’écraser, le mordre, le déchirer. Et je le guettais avec tous mes organes surexcités.  | 
| ︙ | ︙ | |||
429 430 431 432 433 434 435  | Je pus enfin me distinguer complètement, ainsi que je le fais chaque jour en me regardant. Je l’avais vu ! L’épouvante m’en est restée, qui me fait encore frissonner. 20 août. – Le tuer, comment ? puisque je ne peux l’atteindre ? Le poison ? mais il me verrait le mêler à l’eau ; et nos poisons, d’ailleurs, auraient-ils un effet sur son corps imperceptible ? Non… non… sans aucun doute… Alors ?… alors ?… 21 août. – J’ai fait venir un serrurier de Rouen, et lui ai commandé pour ma chambre des persiennes de fer, comme en ont, à Paris, certains hôtels particuliers, au rez-de-chaussée, par crainte des voleurs. Il me fera, en outre, une porte pareille. Je me suis donné pour un poltron, mais je m’en moque !…  | |  | 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443  | Je pus enfin me distinguer complètement, ainsi que je le fais chaque jour en me regardant. Je l’avais vu ! L’épouvante m’en est restée, qui me fait encore frissonner. 20 août. – Le tuer, comment ? puisque je ne peux l’atteindre ? Le poison ? mais il me verrait le mêler à l’eau ; et nos poisons, d’ailleurs, auraient-ils un effet sur son corps imperceptible ? Non… non… sans aucun doute… Alors ?… alors ?… 21 août. – J’ai fait venir un serrurier de Rouen, et lui ai commandé pour ma chambre des persiennes de fer, comme en ont, à Paris, certains hôtels particuliers, au rez-de-chaussée, par crainte des voleurs. Il me fera, en outre, une porte pareille. Je me suis donné pour un poltron, mais je m’en moque !… · · · · · · · · · · · · · · · · · · · · · · · · 10 septembre. – Rouen, hôtel continental. C’est fait… c’est fait… mais est-il mort ? J’ai l’âme bouleversée de ce que j’ai vu. Hier donc, le serrurier ayant posé ma persienne et ma porte de fer, j’ai laissé tout ouvert jusqu’à minuit, bien qu’il commençât à faire froid. Tout à coup, j’ai senti qu’il était là, et une joie, une joie folle m’a saisi. Je me suis levé lentement, et j’ai marché à droite, à gauche, longtemps pour qu’il ne devinât rien ; puis j’ai ôté mes bottines et mis mes savates avec négligence ; puis j’ai fermé ma persienne de fer, et revenant à pas tranquilles vers la porte, j’ai fermé la porte aussi à double tour. Retournant alors vers la fenêtre, je la fixai par un cadenas, dont je mis la clef dans ma poche.  | 
| ︙ | ︙ | 
Modified tests/fr/text2.res.txt from [4c753a53e0] to [56d437d0e0].
  | 
  | | | | | | | | | | | | | |  | 1 2 3 4 5 6 7 8 9 10 11 12 13 14  | 
{ "grammalecte": "0.6.2", "lang": "fr", "data" : [
{"lGrammarErrors": [{"sLineId": "885p", "sRuleId": "apostrophe_typographique", "sType": "apos", "aSuggestions": ["Lorsqu’"], "sMessage": "Apostrophe typographique.", "URL": "", "nEndY": 1, "nEndX": 7, "nStartY": 1, "nStartX": 0}, {"sLineId": "11404s", "sRuleId": "conj_il", "sType": "conj", "aSuggestions": ["arriva"], "sMessage": "Conjugaison erronée. Accord avec « il ». Le verbe devrait être à la 3ᵉ personne du singulier.", "URL": "", "nEndY": 1, "nEndX": 17, "nStartY": 1, "nStartX": 10}], "lSpellingErrors": []},
{"lGrammarErrors": [], "lSpellingErrors": []},
{"lGrammarErrors": [], "lSpellingErrors": [{"sType": "WORD", "sValue": "Horla", "nEndY": 5, "nEndX": 8, "nStartY": 5, "nStartX": 3}, {"sType": "WORD", "sValue": "Horla", "nEndY": 6, "nEndX": 8, "nStartY": 6, "nStartX": 3}, {"sType": "WORD", "sValue": "Ollendorff", "nEndY": 6, "nEndX": 23, "nStartY": 6, "nStartX": 13}]},
{"lGrammarErrors": [{"sLineId": "885p", "sRuleId": "apostrophe_typographique", "sType": "apos", "aSuggestions": ["J’"], "sMessage": "Apostrophe typographique.", "URL": "", "nEndY": 9, "nEndX": 39, "nStartY": 9, "nStartX": 37}, {"sLineId": "6265s", "sRuleId": "gn_quelle_accord", "sType": "gn", "aSuggestions": ["Quelles"], "sMessage": "Accord de nombre erroné : « journées » est au pluriel.", "URL": "", "nEndY": 9, "nEndX": 15, "nStartY": 9, "nStartX": 9}, {"sLineId": "6265s", "sRuleId": "gn_quelle_accord", "sType": "gn", "aSuggestions": ["journée"], "sMessage": "Accord de nombre erroné : « Quelle » est au singulier.", "URL": "", "nEndY": 9, "nEndX": 24, "nStartY": 9, "nStartX": 16}, {"sLineId": "6628s", "sRuleId": "gn_2m_la", "sType": "gn", "aSuggestions": ["étendue"], "sMessage": "Accord de genre erroné : « matinée » est féminin, « étendu » est masculin.", "URL": "", "nEndY": 9, "nEndX": 72, "nStartY": 9, "nStartX": 66}, {"sLineId": "10365s", "sRuleId": "ppas_pronom_avoir", "sType": "ppas", "aSuggestions": ["passé"], "sMessage": "Ce verbe devrait être un participe passé au masculin singulier.", "URL": "http://fr.wikipedia.org/wiki/Accord_du_participe_pass%C3%A9_en_fran%C3%A7ais", "nEndY": 9, "nEndX": 48, "nStartY": 9, "nStartX": 42}], "lSpellingErrors": []},
{"lGrammarErrors": [{"sLineId": "11348s", "sRuleId": "conj_j", "sType": "conj", "aSuggestions": ["aime"], "sMessage": "Conjugaison erronée. Accord avec « je ». Le verbe devrait être à la 1ʳᵉ personne du singulier.", "URL": "", "nEndY": 17, "nEndX": 7, "nStartY": 17, "nStartX": 2}], "lSpellingErrors": []},
{"lGrammarErrors": [], "lSpellingErrors": []},
{"lGrammarErrors": [], "lSpellingErrors": []},
{"lGrammarErrors": [{"sLineId": "6199s", "sRuleId": "gn_nombre_lettres_accord", "sType": "gn", "aSuggestions": ["heures"], "sMessage": "Accord de nombre erroné avec « onze » : « heure » devrait être au pluriel.", "URL": "", "nEndY": 30, "nEndX": 15, "nStartY": 30, "nStartX": 10}], "lSpellingErrors": []},
{"lGrammarErrors": [{"sLineId": "11353s", "sRuleId": "conj_je", "sType": "conj", "aSuggestions": ["saluais"], "sMessage": "Conjugaison erronée. Accord avec « Je ». Le verbe devrait être à la 1ʳᵉ personne du singulier.", "URL": "", "nEndY": 36, "nEndX": 22, "nStartY": 36, "nStartX": 15}], "lSpellingErrors": [{"sType": "WORD", "sValue": "goëlettes", "nEndY": 34, "nEndX": 20, "nStartY": 34, "nStartX": 11}]},
{"lGrammarErrors": [{"sLineId": "6108s", "sRuleId": "gn_det_pluriel_accord", "sType": "gn", "aSuggestions": ["jours"], "sMessage": "Accord de nombre erroné : « jour » devrait être au pluriel.", "URL": "", "nEndY": 39, "nEndX": 80, "nStartY": 39, "nStartX": 76}], "lSpellingErrors": []},
{"lGrammarErrors": [{"sLineId": "6053s", "sRuleId": "gn_notre_votre_chaque_accord", "sType": "gn", "aSuggestions": ["confiance"], "sMessage": "Accord de nombre erroné : « confiances » devrait être au singulier.", "URL": "", "nEndY": 43, "nEndX": 27, "nStartY": 43, "nStartX": 17}, {"sLineId": "11353s", "sRuleId": "conj_je", "sType": "conj", "aSuggestions": ["descends"], "sMessage": "Conjugaison erronée. Accord avec « Je ». Le verbe devrait être à la 1ʳᵉ personne du singulier.", "URL": "", "nEndY": 46, "nEndX": 32, "nStartY": 46, "nStartX": 25}, {"sLineId": "12117s", "sRuleId": "inte_ce", "sType": "inte", "aSuggestions": ["est"], "sMessage": "Forme interrogative : « Es » n’est pas un verbe à la 3ᵉ personne du singulier.", "URL": "http://bdl.oqlf.gouv.qc.ca/bdl/gabarit_bdl.asp?id=4132", "nEndY": 48, "nEndX": 22, "nStartY": 48, "nStartX": 20}], "lSpellingErrors": []},
{"lGrammarErrors": [{"sLineId": "10836s", "sRuleId": "imp_va", "sType": "imp", "aSuggestions": ["Va"], "sMessage": "S’il s’agit d’un impératif, pas de “s”.", "URL": "", "nEndY": 57, "nEndX": 3, "nStartY": 57, "nStartX": 0}, {"sLineId": "10836s", "sRuleId": "imp_va", "sType": "imp", "aSuggestions": ["Va"], "sMessage": "S’il s’agit d’un impératif, pas de “s”.", "URL": "", "nEndY": 58, "nEndX": 3, "nStartY": 58, "nStartX": 0}], "lSpellingErrors": []}
]}
 |