Grammalecte  Check-in [af32e20bdb]

Overview
Comment:merge trunk
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | multid
Files: files | file ages | folders
SHA3-256: af32e20bdb0bcb20ac7d61d1aa9e467db9a6f13792aa1f4de86968e0cb9d2e96
User & Date: olr on 2018-02-23 21:00:52
Other Links: branch diff | manifest | tags
Context
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
Changes

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
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
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
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
92

93
94
95
96
97
98
99
100

101
102
103
104
105
106
107
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&amp;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="m4" oor:op="replace">
                <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="m5" oor:op="replace">
                <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
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
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="m6" oor:op="replace">
                <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="m7" oor:op="replace">
                <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="m8" oor:op="replace">
                <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="m9" oor:op="replace">
                <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
26

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 09:06    6.20116     1.44334     1.02936     0.272956    0.311561    0.362367    0.812705    0.419061    0.0773003   0.00845671  (spelling normalization)
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
207

208
209
210
211
212
213
214
215
216
217

218
219
220
221
222
223
224
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 = oTokenizer.getSpellingErrors(sParagraph, oSpellChecker);
        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 = oTokenizer.getSpellingErrors(sParagraph, oSpellChecker);
    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
8
9

10
11
12
13
14
15
16
17
18
19
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.fr as gce
import grammalecte.fr.lexicographe as lxg
import grammalecte
import grammalecte.fr.textformatter as tf
import grammalecte.text as txt
import grammalecte.graphspell.tokenizer as tkz
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
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
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 _getErrors (sText, oTokenizer, oSpellChecker, bContext=False, bSpellSugg=False, bDebug=False):
    "returns a tuple: (grammar errors, spelling errors)"
    aGrammErrs = gce.parse(sText, "FR", bDebug=bDebug, bContext=bContext)
    aSpellErrs = []
    for dToken in oTokenizer.genTokens(sText):
        if dToken['sType'] == "WORD" and not oSpellChecker.isValidToken(dToken['sValue']):
            if bSpellSugg:
                dToken['aSuggestions'] = []
                for lSugg in oSpellChecker.suggest(dToken['sValue']):
                    dToken['aSuggestions'].extend(lSugg)
            aSpellErrs.append(dToken)
    return aGrammErrs, aSpellErrs


def generateText (sText, oTokenizer, oSpellChecker, bDebug=False, bEmptyIfNoErrors=False, bSpellSugg=False, nWidth=100):
    aGrammErrs, aSpellErrs = _getErrors(sText, oTokenizer, oSpellChecker, False, bSpellSugg, bDebug)
    if bEmptyIfNoErrors and not aGrammErrs and not aSpellErrs:
        return ""
    return txt.generateParagraph(sText, aGrammErrs, aSpellErrs, nWidth)


def generateJSON (iIndex, sText, oTokenizer, oSpellChecker, bContext=False, bDebug=False, bEmptyIfNoErrors=False, bSpellSugg=False, lLineSet=None, bReturnText=False):
    aGrammErrs, aSpellErrs = _getErrors(sText, oTokenizer, oSpellChecker, bContext, bSpellSugg, bDebug)
    aGrammErrs = list(aGrammErrs)
    if bEmptyIfNoErrors and not aGrammErrs and not aSpellErrs:
        return ""
    if lLineSet:
        aGrammErrs, aSpellErrs = txt.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)


def readfile (spf):
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 not found.")
        print("# Error: file <" + spf + ">not found.")


def readfileAndConcatLines (spf):
    "generator: returns text by list of lines not separated by an empty line"
    lLine = []
    for i, sLine in enumerate(readfile(spf), 1):
        if sLine.strip():
            lLine.append((i, sLine))
        elif lLine:
            yield lLine
            lLine = []
    if lLine:
        yield lLine
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 (unavailable with option --concat_lines)", 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()
    gce.load()

    if not xArgs.json:
        echo("Grammalecte v{}".format(gce.version))
        echo("Grammalecte v{}".format(oGrammarChecker.gce.version))
    oSpellChecker = gce.getSpellChecker()
    oTokenizer = tkz.Tokenizer("fr")
    oLexGraphe = lxg.Lexicographe(oSpellChecker)
    if xArgs.textformatter or xArgs.textformatteronly:
        oTF = tf.TextFormatter()

    # list options or rules
    if xArgs.list_options or xArgs.list_rules:
        if xArgs.list_options:
            gce.displayOptions("fr")
            oGrammarChecker.gce.displayOptions("fr")
        if xArgs.list_rules:
            gce.displayRules(None  if xArgs.list_rules == "*"  else 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
    gce.setOptions({"html": True, "latex": True})
    oGrammarChecker.gce.setOptions({"html": True, "latex": True})
    if xArgs.opt_on:
        gce.setOptions({ opt:True  for opt in xArgs.opt_on  if opt in gce.getOptions() })
        oGrammarChecker.gce.setOptions({ opt:True  for opt in xArgs.opt_on  if opt in oGrammarChecker.gce.getOptions() })
    if xArgs.opt_off:
        gce.setOptions({ opt:False  for opt in xArgs.opt_off  if opt in gce.getOptions() })
        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:
            gce.ignoreRule(sRule)
            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": "'+gce.version+'", "lang": "'+gce.lang+'", "data" : [\n', hDst)
        if not xArgs.concat_lines:
            output('{ "grammalecte": "'+oGrammarChecker.gce.version+'", "lang": "'+oGrammarChecker.gce.lang+'", "data" : [\n', hDst)
        for i, sText, lLineSet in generateParagraphFromFile(sFile, xArgs.concat_lines):
            # pas de concaténation des lignes
            for i, sText in enumerate(readfile(sFile), 1):
                if xArgs.textformatter or xArgs.textformatteronly:
                    sText = oTF.formatText(sText)
                if xArgs.textformatteronly:
                    output(sText, hDst)
            if xArgs.textformatter or xArgs.textformatteronly:
                sText = oTextFormatter.formatText(sText)
            if xArgs.textformatteronly:
                output(sText, hDst)
                else:
                    if xArgs.json:
                        sText = generateJSON(i, sText, oTokenizer, oSpellChecker, bContext=xArgs.context, bDebug=False, bEmptyIfNoErrors=xArgs.only_when_errors, bSpellSugg=xArgs.with_spell_sugg, bReturnText=xArgs.textformatter)
                continue
            if xArgs.json:
                sText = oGrammarChecker.generateParagraphAsJSON(i, sText, bContext=xArgs.context, bEmptyIfNoErrors=xArgs.only_when_errors, \
                    else:
                        sText = generateText(sText, oTokenizer, oSpellChecker, bDebug=False, bEmptyIfNoErrors=xArgs.only_when_errors, bSpellSugg=xArgs.with_spell_sugg, nWidth=xArgs.width)
                                                                bSpellSugg=xArgs.with_spell_sugg, bReturnText=xArgs.textformatter, lLineSet=lLineSet)
                    if sText:
                        if xArgs.json and bComma:
                            output(",\n", hDst)
                        output(sText, hDst)
                        bComma = True
                if hDst:
                    echo("§ %d\r" % i, end="", flush=True)
        else:
            else:
            # concaténation des lignes non séparées par une ligne vide
            for i, lLine in enumerate(readfileAndConcatLines(sFile), 1):
                sText, lLineSet = txt.createParagraphWithLines(lLine)
                if xArgs.json:
                    sText = generateJSON(i, sText, oTokenizer, oSpellChecker, bContext=xArgs.context, bDebug=False, bEmptyIfNoErrors=xArgs.only_when_errors, bSpellSugg=xArgs.with_spell_sugg, lLineSet=lLineSet)
                else:
                    sText = generateText(sText, oTokenizer, oSpellChecker, bDebug=False, 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)
                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, oLexGraphe.formatTags(sMorph)))
                            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("/+ "):
                gce.setOptions({ opt:True  for opt in sText[3:].strip().split()  if opt in gce.getOptions() })
                oGrammarChecker.gce.setOptions({ opt:True  for opt in sText[3:].strip().split()  if opt in oGrammarChecker.gce.getOptions() })
                echo("done")
            elif sText.startswith("/- "):
                gce.setOptions({ opt:False  for opt in sText[3:].strip().split()  if opt in gce.getOptions() })
                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():
                    gce.ignoreRule(sRule)
                    oGrammarChecker.gce.ignoreRule(sRule)
                echo("done")
            elif sText.startswith("/++ "):
                for sRule in sText[3:].strip().split():
                    gce.reactivateRule(sRule)
                    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":
                gce.displayOptions("fr")
                oGrammarChecker.gce.displayOptions("fr")
            elif sText.startswith("/lr"):
                sText = sText.strip()
                sFilter = sText[sText.find(" "):].strip()  if sText != "/lr" and sText != "/rules"  else None
                gce.displayRules(sFilter)
                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 = oTF.formatText(sText)
                    sRes = generateText(sText, oTokenizer, oSpellChecker, bDebug=xArgs.debug, bEmptyIfNoErrors=xArgs.only_when_errors, nWidth=xArgs.width)
                        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
13
14

15
16
17
18
19
20
21
22
23
24
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.fr as gce
import grammalecte.fr.lexicographe as lxg
import grammalecte
import grammalecte.fr.textformatter as tf
import grammalecte.text as txt
import grammalecte.graphspell.tokenizer as tkz
from grammalecte.graphspell.echo import echo


HOMEPAGE = """
<!DOCTYPE HTML>
<html>
    <head>
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
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__':
def parseParagraph (iParagraph, sText, oTokenizer, oSpellChecker, dOptions, bDebug=False, bEmptyIfNoErrors=False):
    aGrammErrs = gce.parse(sText, "FR", bDebug, dOptions)
    aGrammErrs = list(aGrammErrs)
    aSpellErrs = []

    # initialisation
    oGrammarChecker = grammalecte.GrammarChecker("fr", "Server")
    oSpellChecker = oGrammarChecker.getSpellChecker()
    for dToken in oTokenizer.genTokens(sText):
        if dToken['sType'] == "WORD" and not oSpellChecker.isValidToken(dToken['sValue']):
    oLexicographer = oGrammarChecker.getLexicographer()
            aSpellErrs.append(dToken)
    if bEmptyIfNoErrors and not aGrammErrs and not aSpellErrs:
        return ""
    return "  " + json.dumps({ "iParagraph": iParagraph, "lGrammarErrors": aGrammErrs, "lSpellingErrors": aSpellErrs }, ensure_ascii=False)
    

    oTextFormatter = oGrammarChecker.getTextFormatter()
    gce = oGrammarChecker.getGCEngine()
if __name__ == '__main__':

    gce.load("Server")
    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()) ]))
    oSpellChecker = gce.getSpellChecker()
    oTokenizer = tkz.Tokenizer("fr")
    oTF = tf.TextFormatter()
    dUser = {}
    userGenerator = genUserId()

    app = Bottle()

    # GET
    @app.route("/")
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
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
        bTF = bool(request.forms.tf)
        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 bTF:
                sText = oTF.formatText(sText)
            sText = parseParagraph(i, sText, oTokenizer, oSpellChecker, dOptions, bEmptyIfNoErrors=True)
            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
234

235
236
237
238
239
240
241
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 oTF.formatText(request.forms.text)
        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
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
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
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(require) !== 'undefined') {
            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
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
83
84
85
86
87
88
89










90
91
92
93
94
95







-
-
-
-
-
-
-
-
-
-






                    helpers.logerror(e);
                }
            }
            i += nCut;
            sText = sText.slice(nCut);
        }
    }

    getSpellingErrors (sText, oSpellChecker) {
        let aSpellErr = [];
        for (let oToken of this.genTokens(sText)) {
            if (oToken.sType === 'WORD' && !oSpellChecker.isValidToken(oToken.sValue)) {
                aSpellErr.push(oToken);
            }
        }
        return aSpellErr;
    }
}


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
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
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

11

12
13
14
15
16
17
18
19
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:
* 64:70  # 3514s  : Accord de genre erroné : « matinée » est féminin, « étendu »
  Accord de genre erroné : « matinée » est féminin, « étendu » est masculin.
  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

114

115
116
117
118
119
120
121
122
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:
* 136:138  # 377p  : Typographie : pas de virgule avant les points de
  Typographie : pas de virgule avant les points de suspension.
  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

482

483
484
485
486
487
488
489
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:
* 185:202  # 1518s  : Pléonasme.
  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
591

592
593
594
595
596
597
598
599
600




601
602
603
604
605
606
607
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

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
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:
* 5:7  # 377p  : Typographie : pas de virgule avant les points de suspension.
  Typographie : pas de virgule avant les points de suspension.
  > Suggestions : …
* 31:33  # 704p / virg_virgule_avant_points_suspension:
* 31:33  # 377p  : Typographie : pas de virgule avant les points de suspension.
  Typographie : pas de virgule avant les points de suspension.
  > Suggestions : …
* 55:57  # 704p / virg_virgule_avant_points_suspension:
* 55:57  # 377p  : Typographie : pas de virgule avant les points de 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:
* 277:286  # 5069s  : Le « 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 
                                                  ^^^^^^^
* 278:285  # 3142s  : Accord de nombre erroné : « propres » devrait être au
  singulier.
                                          ^^      ^^^^^^^
* 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
942

943
944
945
946
947
948
949
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
1001

1002
1003
1004
1005
1006
1007
1008
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

1031

1032
1033
1034
1035
1036
1037
1038
1039
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:
* 264:270  # 3554s  : Accord de nombre erroné avec « âme » : « lourds » devrait
  Accord de nombre erroné avec « âme » : « lourds » devrait être au singulier.
  ê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
298

299
300
301
302
303
304
305
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
411

412
413
414
415
416
417
418
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
436

437
438
439
440
441
442
443
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













1
2
3
4
5
6
7
8
9
10
11
12
13
14
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+

{ "grammalecte": "0.5.12", "lang": "fr", "data" : [
{"lSpellingErrors": [], "lGrammarErrors": [{"aSuggestions": ["Lorsqu’"], "sType": "apos", "nStartX": 0, "sRuleId": "apostrophe_typographique", "sBefore": "", "nEndX": 7, "nStartY": 1, "sLineId": "523p", "nEndY": 1, "sMessage": "Apostrophe typographique.", "sUnderlined": "Lorsqu'", "sAfter": "il arrivas", "URL": ""}, {"aSuggestions": ["arriva"], "sType": "gv", "nStartX": 10, "sRuleId": "conj_il", "sBefore": "Lorsqu'il ", "nEndX": 17, "nStartY": 1, "sLineId": "7710s", "nEndY": 1, "sMessage": "Conjugaison erronée. Accord avec « il ». Le verbe devrait être à la 3ᵉ personne du singulier.", "sUnderlined": "arrivas", "sAfter": "", "URL": ""}, {"aSuggestions": ["arriva"], "sType": "gv", "nStartX": 10, "sRuleId": "conj_xxxas_sans_sujet", "sBefore": "Lorsqu'il ", "nEndX": 17, "nStartY": 1, "sLineId": "7959s", "nEndY": 1, "sMessage": "Incohérence. Ceci est un verbe à la 2ᵉ personne du singulier. Sujet (“tu” ou “toi qui”) introuvable.", "sUnderlined": "arrivas", "sAfter": "", "URL": ""}]},
{"lSpellingErrors": [], "lGrammarErrors": []},
{"lSpellingErrors": [{"sType": "WORD", "nStartX": 3, "nEndX": 8, "nEndY": 5, "nStartY": 5, "sValue": "Horla"}, {"sType": "WORD", "nStartX": 3, "nEndX": 8, "nEndY": 6, "nStartY": 6, "sValue": "Horla"}, {"sType": "WORD", "nStartX": 13, "nEndX": 23, "nEndY": 6, "nStartY": 6, "sValue": "Ollendorff"}], "lGrammarErrors": []},
{"lSpellingErrors": [], "lGrammarErrors": [{"aSuggestions": ["J’"], "sType": "apos", "nStartX": 37, "sRuleId": "apostrophe_typographique", "sBefore": "8 mai. – Quelle journées admirable ! ", "nEndX": 39, "nStartY": 9, "sLineId": "523p", "nEndY": 9, "sMessage": "Apostrophe typographique.", "sUnderlined": "J'", "sAfter": "ai passés toute la matinée étendu sur l’herbe, devant ma maison, sous l’énorme p", "URL": ""}, {"aSuggestions": ["Quelles"], "sType": "gn", "nStartX": 9, "sRuleId": "gn_quelle_accord", "sBefore": "– ", "nEndX": 15, "nStartY": 9, "sLineId": "3673s", "nEndY": 9, "sMessage": "Accord de nombre erroné : « journées » est au pluriel.", "sUnderlined": "Quelle", "sAfter": " journées admirable ! ", "URL": ""}, {"aSuggestions": ["étendue"], "sType": "gn", "nStartX": 66, "sRuleId": "gn_2m_det_fem_sing", "sBefore": "J'ai passés toute la matinée ", "nEndX": 72, "nStartY": 9, "sLineId": "3990s", "nEndY": 9, "sMessage": "Accord de genre erroné : « matinée » est féminin, « étendu » est masculin.", "sUnderlined": "étendu", "sAfter": " sur l’herbe, devant ma maison, sous l’énorme platane qui la couvre, l’abrite et", "URL": ""}, {"aSuggestions": ["passé"], "sType": "ppas", "nStartX": 42, "sRuleId": "ppas_pronom_avoir", "sBefore": "J'ai ", "nEndX": 48, "nStartY": 9, "sLineId": "7013s", "nEndY": 9, "sMessage": "Ce verbe devrait être un participe passé au masculin singulier.", "sUnderlined": "passés", "sAfter": " toute la matinée étendu sur l’herbe, devant ma maison, sous l’énorme platane qu", "URL": "http://fr.wikipedia.org/wiki/Accord_du_participe_pass%C3%A9_en_fran%C3%A7ais"}]},
{"lSpellingErrors": [], "lGrammarErrors": [{"aSuggestions": ["aime"], "sType": "gv", "nStartX": 2, "sRuleId": "conj_j", "sBefore": "J’", "nEndX": 7, "nStartY": 17, "sLineId": "7653s", "nEndY": 17, "sMessage": "Conjugaison erronée. Accord avec « je ». Le verbe devrait être à la 1ʳᵉ personne du singulier.", "sUnderlined": "aimes", "sAfter": " ma maison où j’ai grandi. ", "URL": ""}, {"aSuggestions": ["aime"], "sType": "gv", "nStartX": 2, "sRuleId": "conj_xxxes_sans_sujet", "sBefore": "J’", "nEndX": 7, "nStartY": 17, "sLineId": "7953s", "nEndY": 17, "sMessage": "Incohérence. Ceci est un verbe à la 2ᵉ personne du singulier. Sujet (“tu” ou “toi qui”) introuvable.", "sUnderlined": "aimes", "sAfter": " ma maison où j’ai grandi. ", "URL": ""}]},
{"lSpellingErrors": [], "lGrammarErrors": []},
{"lSpellingErrors": [], "lGrammarErrors": []},
{"lSpellingErrors": [], "lGrammarErrors": [{"aSuggestions": ["heures"], "sType": "gn", "nStartX": 10, "sRuleId": "gn_nombre_lettres_accord", "sBefore": "Vers onze ", "nEndX": 15, "nStartY": 30, "sLineId": "3626s", "nEndY": 30, "sMessage": "Accord de nombre erroné avec « onze » : « heure » devrait être au pluriel.", "sUnderlined": "heure", "sAfter": ", un long convoi de navires, traînés par un remorqueur, gros comme une mouche, e", "URL": ""}]},
{"lSpellingErrors": [{"sType": "WORD", "nStartX": 11, "nEndX": 20, "nEndY": 34, "nStartY": 34, "sValue": "goëlettes"}], "lGrammarErrors": [{"aSuggestions": ["saluais"], "sType": "gv", "nStartX": 15, "sRuleId": "conj_je_pronom", "sBefore": "Je le ", "nEndX": 22, "nStartY": 36, "sLineId": "7661s", "nEndY": 36, "sMessage": "Conjugaison erronée. Accord avec « Je ». Le verbe devrait être à la 1ʳᵉ personne du singulier.", "sUnderlined": "saluait", "sAfter": ", je ne sais pourquoi, tant ce navire me fit plaisir à voir.", "URL": ""}]},
{"lSpellingErrors": [], "lGrammarErrors": [{"aSuggestions": ["jours"], "sType": "gn", "nStartX": 76, "sRuleId": "gn_det_pluriel_accord", "sBefore": "– J’ai un peu de fièvre depuis quelques ", "nEndX": 80, "nStartY": 39, "sLineId": "3540s", "nEndY": 39, "sMessage": "Accord de nombre erroné : « jour » devrait être au pluriel.", "sUnderlined": "jour", "sAfter": ", et toi ; ", "URL": ""}]},
{"lSpellingErrors": [], "lGrammarErrors": [{"aSuggestions": ["confiance"], "sType": "gn", "nStartX": 17, "sRuleId": "gn_notre_votre_chaque_accord", "sBefore": "es influences mystérieuses qui changent en découragement notre bonheur et notre ", "nEndX": 27, "nStartY": 43, "sLineId": "3492s", "nEndY": 43, "sMessage": "Accord de nombre erroné : « confiances » devrait être au singulier.", "sUnderlined": "confiances", "sAfter": " en détresse. ", "URL": ""}, {"aSuggestions": ["descends"], "sType": "gv", "nStartX": 25, "sRuleId": "conj_je", "sBefore": "– Je ", "nEndX": 32, "nStartY": 46, "sLineId": "7657s", "nEndY": 46, "sMessage": "Conjugaison erronée. Accord avec « Je ». Le verbe devrait être à la 1ʳᵉ personne du singulier.", "sUnderlined": "descend", "sAfter": " le long de l’eau ; ", "URL": ""}]},
{"lSpellingErrors": [], "lGrammarErrors": [{"aSuggestions": ["Va"], "sType": "imp", "nStartX": 0, "sRuleId": "imp_va", "sBefore": "", "nEndX": 3, "nStartY": 57, "sLineId": "7357s", "nEndY": 57, "sMessage": "S’il s’agit d’un impératif, pas de “s”.", "sUnderlined": "Vas", "sAfter": " en enfer. ", "URL": ""}, {"aSuggestions": ["Va"], "sType": "gv", "nStartX": 0, "sRuleId": "conj_xxxas_sans_sujet", "sBefore": "", "nEndX": 3, "nStartY": 57, "sLineId": "7959s", "nEndY": 57, "sMessage": "Incohérence. Ceci est un verbe à la 2ᵉ personne du singulier. Sujet (“tu” ou “toi qui”) introuvable.", "sUnderlined": "Vas", "sAfter": " en enfer. ", "URL": ""}, {"aSuggestions": ["Va"], "sType": "imp", "nStartX": 0, "sRuleId": "imp_va", "sBefore": "", "nEndX": 3, "nStartY": 58, "sLineId": "7357s", "nEndY": 58, "sMessage": "S’il s’agit d’un impératif, pas de “s”.", "sUnderlined": "Vas", "sAfter": " en enfer.", "URL": ""}, {"aSuggestions": ["Va"], "sType": "gv", "nStartX": 0, "sRuleId": "conj_xxxas_sans_sujet", "sBefore": "", "nEndX": 3, "nStartY": 58, "sLineId": "7959s", "nEndY": 58, "sMessage": "Incohérence. Ceci est un verbe à la 2ᵉ personne du singulier. Sujet (“tu” ou “toi qui”) introuvable.", "sUnderlined": "Vas", "sAfter": " en enfer.", "URL": ""}]}
{ "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": []}
]}