3 # This file is part of the LibreOffice project.
5 # This Source Code Form is subject to the terms of the Mozilla Public
6 # License, v. 2.0. If a copy of the MPL was not distributed with this
7 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
10 Script to generate https://wiki.documentfoundation.org/Development/DispatchCommands
11 3 types of source files are scanned to identify and describe a list of relevant UNO commands:
12 - .hxx files: containing the symbolic and numeric id's, and the respective modes and groups
13 - .xcu files; containing several english labels as they appear in menus or tooltips
14 - .sdi files: containing a list of potential arguments for the commands, and their types
19 REPO
= 'https://opengrok.libreoffice.org/xref/core/'
21 BLACKLIST
= ('_SwitchViewShell0', '_SwitchViewShell1', '_SwitchViewShell2', '_SwitchViewShell3', '_SwitchViewShell4')
23 XCU_DIR
= 'officecfg/registry/data/org/openoffice/Office/UI/'
24 XCU_FILES
= ( XCU_DIR
+ 'BasicIDECommands.xcu',
25 XCU_DIR
+ 'CalcCommands.xcu',
26 XCU_DIR
+ 'ChartCommands.xcu',
27 XCU_DIR
+ 'DbuCommands.xcu',
28 XCU_DIR
+ 'DrawImpressCommands.xcu',
29 XCU_DIR
+ 'GenericCommands.xcu',
30 XCU_DIR
+ 'MathCommands.xcu',
31 XCU_DIR
+ 'ReportCommands.xcu',
32 XCU_DIR
+ 'WriterCommands.xcu')
34 HXX_DIR
= './workdir/SdiTarget/'
35 HXX_FILES
= ( HXX_DIR
+ 'basctl/sdi/basslots.hxx',
36 HXX_DIR
+ 'sc/sdi/scslots.hxx',
37 HXX_DIR
+ 'sd/sdi/sdgslots.hxx',
38 HXX_DIR
+ 'sd/sdi/sdslots.hxx',
39 HXX_DIR
+ 'sfx2/sdi/sfxslots.hxx',
40 HXX_DIR
+ 'starmath/sdi/smslots.hxx',
41 HXX_DIR
+ 'svx/sdi/svxslots.hxx',
42 HXX_DIR
+ 'sw/sdi/swslots.hxx')
44 SDI_FILES
= ( 'sc/sdi/scalc.sdi',
47 'starmath/sdi/smath.sdi',
51 # Category is defined by the 1st file where the command has been found. Precedence: 1. xcu, 2. hxx, 3. sdi.
52 MODULES
= {'BasicIDE': 'Basic IDE, Forms, Dialogs',
56 'DrawImpress': 'Draw / Impress',
61 'basslots': 'Basic IDE, Forms, Dialogs',
63 'sdgslots': 'Draw / Impress',
64 'sdslots': 'Draw / Impress',
70 'sdraw': 'Draw / Impress',
76 def newcommand(unocommand
):
77 cmd
= {'unocommand': unocommand
,
99 def analyze_xcu(all_commands
):
100 for filename
in XCU_FILES
:
102 with
open(filename
) as fh
:
106 if '<node oor:name="Popups">' in line
:
109 elif popups
is True and line
== ' </node>':
112 if '<node oor:name=".uno:' not in line
:
116 tmp
= line
.split('"')
117 command_name
= tmp
[1]
120 while '</node>' not in line
:
124 except StopIteration:
125 print("Warning: couldn't find '</node>' line in %s" % filename
,
128 if '<prop oor:name="Label"' in line
:
130 elif '<prop oor:name="ContextLabel"' in line
:
131 label
= 'contextlabel'
132 elif '<prop oor:name="TooltipLabel"' in line
:
133 label
= 'tooltiplabel'
134 elif '<value xml:lang="en-US">' in line
:
135 labeltext
= line
.replace('<value xml:lang="en-US">', '').replace('</value>', '').strip()
136 elif '<prop oor:name="TargetURL"' in line
:
139 if command_ok
is True and popups
is False:
140 if command_name
not in all_commands
:
141 all_commands
[command_name
] = newcommand(command_name
)
143 all_commands
[command_name
]['xcufile'] = XCU_FILES
.index(filename
)
144 all_commands
[command_name
]['xculinenumber'] = cmdln
145 all_commands
[command_name
][label
] = labeltext
.replace('~', '')
146 all_commands
[command_name
]['xcuoccurs'] += 1
149 def analyze_hxx(all_commands
):
150 for filename
in HXX_FILES
:
151 with
open(filename
) as fh
:
156 if not line
.startswith('// Slot Nr. '):
160 # // Slot Nr. 0 : 5502
161 # SFX_NEW_SLOT_ARG( basctl_Shell,SID_SAVEASDOC,SfxGroupId::Document,
163 tmp
= line
.split(':')
164 command_id
= tmp
[1].strip()
168 tmp
= line
.split(',')
170 command_group
= tmp
[2].split('::')[1]
178 mode
+= 'U' if 'AUTOUPDATE' in line
else ''
179 mode
+= 'M' if 'MENUCONFIG' in line
else ''
180 mode
+= 'T' if 'TOOLBOXCONFIG' in line
else ''
181 mode
+= 'A' if 'ACCELCONFIG' in line
else ''
191 tmp
= line
.split('"')
193 command_name
= '.uno:' + tmp
[1]
195 print("Warning: expected \" in line '%s' from file %s" % (line
.strip(), filename
),
197 command_name
= '.uno:'
199 if command_name
not in all_commands
:
200 all_commands
[command_name
] = newcommand(command_name
)
202 all_commands
[command_name
]['hxxfile'] = HXX_FILES
.index(filename
)
203 all_commands
[command_name
]['hxxlinenumber'] = cmdln
204 all_commands
[command_name
]['numericid'] = command_id
205 all_commands
[command_name
]['resourceid'] = command_rid
206 all_commands
[command_name
]['group'] = command_group
207 all_commands
[command_name
]['mode'] = mode
208 all_commands
[command_name
]['hxxoccurs'] += 1
212 def analyze_sdi(all_commands
):
213 def SplitArguments(params
):
214 # Split a string like : SfxStringItem Name SID_CHART_NAME,SfxStringItem Range SID_CHART_SOURCE,SfxBoolItem ColHeaders FN_PARAM_1,SfxBoolItem RowHeaders FN_PARAM_2
215 # in : Name (string)\nRange (string)\nRowHeaders (bool)
218 params
= params
.strip(' ,').replace(', ', ',') # At least 1 case of ', ' in svx/sdi/svx.sdi line 3592
220 for p
in params
.split(','):
226 if 'String' in elems
[0]:
228 elif 'Bool' in elems
[0]:
230 elif 'Int16' in elems
[0]:
231 split
+= ' (integer)'
232 elif 'Int32' in elems
[0]:
235 split
+= ' (' + elems
[0].replace('Sfx', '').replace('Svx', '').replace('Item', '').lower() + ')'
238 for filename
in SDI_FILES
:
240 comment
, square
, command
, param
= False, False, False, False
241 with
open(filename
) as fh
:
244 line
= line
.replace(' ', ' ').strip() # Anomaly met in svx/sdi/svx.sdi
245 if line
.startswith('//'):
247 elif comment
is False and line
.startswith('/*') and not line
.endswith('*/'):
249 elif comment
is True and line
.endswith('*/'):
251 elif comment
is False and line
.startswith('/*') and line
.endswith('*/'):
253 elif comment
is True:
255 elif square
is False and line
.startswith('['):
259 elif square
is True and line
.endswith(']'):
260 all_commands
[command_name
]['mode'] = mode
263 squaremode
= line
.strip(',;').split()
264 if len(squaremode
) == 3:
265 mode
+= 'U' if squaremode
[0] == 'AutoUpdate' and squaremode
[2] == 'TRUE' else ''
266 mode
+= 'M' if squaremode
[0] == 'MenuConfig' and squaremode
[2] == 'TRUE' else ''
267 mode
+= 'T' if squaremode
[0] == 'ToolBoxConfig' and squaremode
[2] == 'TRUE' else ''
268 mode
+= 'A' if squaremode
[0] == 'AccelConfig' and squaremode
[2] == 'TRUE' else ''
269 elif comment
is False and square
is False and command
is False and len(line
) == 0:
271 elif command
is False:
272 command_name
= '.uno:' + line
.split(' ')[1]
273 if command_name
not in all_commands
:
274 all_commands
[command_name
] = newcommand(command_name
)
275 all_commands
[command_name
]['sdifile'] = SDI_FILES
.index(filename
)
276 all_commands
[command_name
]['sdilinenumber'] = ln
277 all_commands
[command_name
]['sdioccurs'] += 1
278 if len(all_commands
[command_name
]['resourceid']) == 0:
279 all_commands
[command_name
]['resourceid'] = line
.split(' ')[2]
281 elif command
is True and (line
== '' or line
== '()'):
283 elif command
is True and (param
is True or line
.startswith('(')) and line
.endswith(')'):
285 params
+= line
.strip(' (),').replace(', ', ',') # At least 1 case of ", " in svx/sdi/svx.sdi line 8767
286 # At least 1 case of "( " in sw/sdi/swriter.sdi line 5477
288 params
= line
.strip(' (),').replace(', ', ',') # At least 1 case in sw/sdi/swriter.sdi line 7083
289 all_commands
[command_name
]['arguments'] = SplitArguments(params
)
292 elif command
is True and line
.startswith('('): # Arguments always on 1 line, except in some cases (cfr.BasicIDEAppear)
293 params
= line
.strip(' ()').replace(', ', ',')
299 def categorize(all_commands
):
300 # Clean black listed commands
301 for command
in BLACKLIST
:
302 cmd
= '.uno:' + command
303 if cmd
in all_commands
:
304 del all_commands
[cmd
]
305 # Set category based on the file name where the command was found first
306 for cmd
in all_commands
:
307 command
= all_commands
[cmd
]
308 cxcu
, chxx
, csdi
= '', '', ''
309 fxcu
= command
['xcufile']
311 cxcu
= os
.path
.basename(XCU_FILES
[fxcu
]).split('.')[0].replace('Commands', '')
312 fhxx
= command
['hxxfile']
314 chxx
= os
.path
.basename(HXX_FILES
[fhxx
]).split('.')[0]
315 fsdi
= command
['sdifile']
317 csdi
= os
.path
.basename(SDI_FILES
[fsdi
]).split('.')[0]
325 # Exceptions on general rule
326 if cat
== 'Generic' and chxx
== 'basslots':
328 command
['module'] = MODULES
[cat
]
331 def print_output(all_commands
):
333 # Return the longest string among the arguments
334 return max(args
, key
= len)
337 # Build string identifying the sources
338 xcufile
, xculinenumber
, hxxfile
, hxxlinenumber
, sdifile
, sdilinenumber
= 2, 3, 8, 10, 14, 16
340 if cmd
[xcufile
] >= 0:
341 src
+= '[' + REPO
+ XCU_FILES
[cmd
[xcufile
]] + '#' + str(cmd
[xculinenumber
]) + ' XCU]'
342 if cmd
[sdifile
] >= 0:
343 src
+= ' [' + REPO
+ SDI_FILES
[cmd
[sdifile
]] + '#' + str(cmd
[sdilinenumber
]) + ' SDI]'
344 if cmd
[hxxfile
] >= 0:
345 file = str(cmd
[hxxfile
] + 1 + len(XCU_FILES
) + len(SDI_FILES
))
346 src
+= ' <span title="File (' + file + ') line ' + str(cmd
[hxxlinenumber
]) + '">[[#hxx' + file + '|HXX]]</span>'
349 # Sort by category and command name
351 for cmd
in all_commands
:
352 cmdlist
= tuple(all_commands
[cmd
].values())
353 commands_list
.append(cmdlist
)
354 sorted_by_command
= sorted(commands_list
, key
= lambda cmd
: cmd
[0])
355 sorted_by_module
= sorted(sorted_by_command
, key
= lambda cmd
: cmd
[1])
357 # Produce tabular output
358 unocommand
, module
, label
, contextlabel
, tooltiplabel
, arguments
, resourceid
, numericid
, group
, mode
= 0, 1, 5, 6, 7, 18, 11, 12, 13, 17
360 for cmd
in sorted_by_module
:
361 # Format bottom and header
362 if lastmodule
!= cmd
[module
]:
363 if len(lastmodule
) > 0:
366 lastmodule
= cmd
[module
]
367 print('=== %s ===\n' % lastmodule
)
369 print('{| class="wikitable sortable" width="100%"')
371 print('! scope="col" | Dispatch command')
372 print('! scope="col" | Description')
373 print('! scope="col" | Group')
374 print('! scope="col" | Arguments')
375 print('! scope="col" | Internal<br>name (value)')
376 print('! scope="col" | Mode')
377 print('! scope="col" | Source<br>files')
379 print('| ' + cmd
[unocommand
].replace('&', '\n&'))
380 print('| ' + longest(cmd
[label
], cmd
[contextlabel
], cmd
[tooltiplabel
]))
381 print('| ' + cmd
[group
])
382 print('| ' + cmd
[arguments
].replace('\\n', '\n'))
383 if len(cmd
[numericid
]) == 0:
384 print('| ' + cmd
[resourceid
])
386 print('| ' + cmd
[resourceid
] + ' (' + cmd
[numericid
] + ')')
387 print('| ' + cmd
[mode
])
388 print('| ' + sources(cmd
))
390 # List the source files
391 print('== Source files ==\n')
393 for i
in range(len(XCU_FILES
)):
395 print(f
'({fn}) {REPO}{XCU_FILES[i]}\n')
397 for i
in range(len(SDI_FILES
)):
399 print(f
'({fn}) {REPO}{SDI_FILES[i]}\n')
401 for i
in range(len(HXX_FILES
)):
403 print(f
'<span id="hxx{fn}">({fn}) {HXX_FILES[i][2:]}</span>\n')
410 analyze_xcu(all_commands
)
412 analyze_hxx(all_commands
)
414 analyze_sdi(all_commands
)
416 categorize(all_commands
)
418 print_output(all_commands
)
420 if __name__
== '__main__':