Skip to content

Instantly share code, notes, and snippets.

@varshneydevansh
Created March 12, 2024 10:50
Show Gist options
  • Save varshneydevansh/3f1df11f6b98820d768e8324012dcdb9 to your computer and use it in GitHub Desktop.
Save varshneydevansh/3f1df11f6b98820d768e8324012dcdb9 to your computer and use it in GitHub Desktop.
[~/libreoffice]
devansh   97448_pyHelper_addons_generator  cd /home/devansh/libreoffice ; /usr/bin/env /bin/python3 /home/devansh/.vscode/extensions/ms-python.debugpy-2024.2.0-linux-x64/bundled/libs/debugpy/adapter/../../debugpy/launcher 43529 -- /home/devansh/libreoffice/odk/source/helper/addon_console.py
Welcome to the LibreOffice Addons Configuration Tool!
Enter addon name (extID name, e.g., com.<companyname>.<company-namespace>.<extension-namespace>): org.libreoffice.TuesdayPrinter
Do you want to create the 'AddonMenu' item (Tools > Add-ons)? (y/n): n
Do you want to create the 'OfficeHelp' item (Help menu)? (y/n): n
Images attribute defines the icons that appear next to the text in the menu and toolbar items.
Do you want to provide images? (y/n): n
Enter merge type (menu or toolbar): menu
Enter merge name (m<No> name for menu, label[] for toolbar): menu1
Enter merge point: .uno:PickList\.uno:Print
Enter merge command (possible merge commands are: AddAfter, AddBefore, Replace, Remove): Replace
Enter merge fallback (possible merge fallbacks are: AddAfter, AddBefore, Replace, Remove): AddAfter
Select the application module context for the merge instruction:
1. Writer
2. Spreadsheet
3. Presentation
4. Draw
5. Formula
6. Chart
7. Bibliography
Enter your choice (1-7): 1
Enter menu item name (or 'done' to finish): submenu1
Enter menu item item title (use '~' before the accelerator key, e.g., '~File'): Print on Tuesdays
Enter menu item URL (can be a macro path or dispatch command): service:org.libreoffice.TuesdayPrinter?printontuesday
Enter menu item target (_self, _default, _top, _parent, or _blank): _self
Enter path to menu item image (optional, or 'skip' to skip): skip
Add submenu for this menu item? (y/n): n
Enter menu item name (or 'done' to finish): done
Continue to modify Menu or Toolbar? ( y or n ): y
Enter merge type (menu or toolbar): toolbar
Enter merge name (m<No> name for menu, label[] for toolbar): toolbar1
Enter merge point: .uno:PrintDefault
Enter merge command (possible merge commands are: AddAfter, AddBefore, Replace, Remove): Replace
Enter merge fallback (possible merge fallbacks are: AddAfter, AddBefore, Replace, Remove):
Select the application module context for the merge instruction:
1. Writer
2. Spreadsheet
3. Presentation
4. Draw
5. Formula
6. Chart
7. Bibliography
Enter your choice (1-7): 1
Enter toolbar item name (or 'done' to finish): button1
Enter toolbar item item title (use '~' before the accelerator key, e.g., '~File'): Print on Tuesdays
Enter toolbar item URL (can be a macro path or dispatch command): service:org.libreoffice.TuesdayPrinter?printontuesday
Enter toolbar item target (_self, _default, _top, _parent, or _blank): _self
Enter path to toolbar item image (optional, or 'skip' to skip): skip
Add submenu for this toolbar item? (y/n): n
Enter the Merge ToolBar name
(specifies the appearance of the floating toolbar reached via View > Toolbars > Add-on) : org.libreoffice.TuesdayPrinter.standardbar
Do you want to add a button separator?
1. Before the first button
2. Between the buttons
3. No separator
Enter your choice (1-3): 3
Enter toolbar item name (or 'done' to finish): previewobjectbar
Enter toolbar item item title (use '~' before the accelerator key, e.g., '~File'): Print on Tuesdays
Enter toolbar item URL (can be a macro path or dispatch command): service:org.libreoffice.TuesdayPrinter?printontuesday
Enter toolbar item target (_self, _default, _top, _parent, or _blank): _self
Enter path to toolbar item image (optional, or 'skip' to skip): skip
Add submenu for this toolbar item? (y/n): n
Enter the Merge ToolBar name
(specifies the appearance of the floating toolbar reached via View > Toolbars > Add-on) : org.libreoffice.TuesdayPrinter.previewobjectbar
Do you want to add a button separator?
1. Before the first button
2. Between the buttons
3. No separator
Enter your choice (1-3): 3
Enter toolbar item name (or 'done' to finish): done
Continue to modify Menu or Toolbar? ( y or n ): n
Save Addons.xcu file to desktop or specify a path to custom directory? (d/c): d
Thank you for using the Addons.xcu file generation. Have a Nice Day!
---------------------------------
<?xml version='1.0' encoding='UTF-8'?>
<oor:component-data oor:package="org.openoffice.Office" oor:name="Addons"
xmlns:oor="http://openoffice.org/2001/registry" xmlns:xs="http://www.w3.org/2001/XMLSchema">
<node oor:name="AddonUI">
<node oor:name="OfficeMenuBar">
<node oor:name="org.libreoffice.TuesdayPrinter" oor:op="replace" xmlns:oor="http://openoffice.org/2001/registry">
<node oor:name="menu1" oor:op="replace">
<prop oor:name="MergePoint">
<value>.uno:PickList\.uno:Print</value>
</prop>
<prop oor:name="MergeCommand">
<value>Replace</value>
</prop>
<prop oor:name="MergeFallback">
<value>AddAfter</value>
</prop>
<prop oor:name="Context" oor:type="xs:string">
<value>com.sun.star.text.TextDocument</value>
</prop>
<node oor:name="MenuItems">
<node oor:name="submenu1" oor:op="replace">
<prop oor:name="Title">
<value xml:lang="en-US">Print on Tuesdays</value>
</prop>
<prop oor:name="URL">
<value>service:org.libreoffice.TuesdayPrinter?printontuesday</value>
</prop>
<prop oor:name="ImageIdentifier">
<value>None</value>
</prop>
<prop oor:name="Target" oor:type="xs:string">
<value>_self</value>
</prop>
</node>
</node>
</node>
</node>
</node>
<node oor:name="OfficeToolBar">
<node oor:name="org.libreoffice.TuesdayPrinter" oor:op="replace" xmlns:oor="http://openoffice.org/2001/registry">
<node oor:name="toolbar1" oor:op="replace">
<prop oor:name="MergeToolBar">
<value>org.libreoffice.TuesdayPrinter.previewobjectbar</value>
</prop>
<prop oor:name="MergePoint">
<value>.uno:PrintDefault</value>
</prop>
<prop oor:name="MergeCommand">
<value>Replace</value>
</prop>
<prop oor:name="MergeFallback">
<value></value>
</prop>
<prop oor:name="Context" oor:type="xs:string">
<value>com.sun.star.text.TextDocument</value>
</prop>
<node oor:name="ToolBarItems">
<node oor:name="button1" oor:op="replace" xmlns:oor="http://openoffice.org/2001/registry">
<prop oor:name="Title" oor:type="xs:string">
<value xml:lang="en-US">Print on Tuesdays</value>
</prop>
<prop oor:name="URL" oor:type="xs:string">
<value>service:org.libreoffice.TuesdayPrinter?printontuesday</value>
</prop>
<prop oor:name="ImageIdentifier" oor:type="xs:string">
<value>None</value>
</prop>
<prop oor:name="Target" oor:type="xs:string">
<value>_self</value>
</prop>
</node>
<prop oor:name="SeparatorPosition"><value>none</value></prop>
<node oor:name="previewobjectbar" oor:op="replace" xmlns:oor="http://openoffice.org/2001/registry">
<prop oor:name="Title" oor:type="xs:string">
<value xml:lang="en-US">Print on Tuesdays</value>
</prop>
<prop oor:name="URL" oor:type="xs:string">
<value>service:org.libreoffice.TuesdayPrinter?printontuesday</value>
</prop>
<prop oor:name="ImageIdentifier" oor:type="xs:string">
<value>None</value>
</prop>
<prop oor:name="Target" oor:type="xs:string">
<value>_self</value>
</prop>
</node>
<prop oor:name="SeparatorPosition"><value>none</value></prop>
</node>
</node>
</node>
</node>
</node>
</oor:component-data>
@varshneydevansh
Copy link
Author

# -*- tab-width: 4; indent-tabs-mode: nil; py-indent-offset: 4 -*-
#
# This file is part of the LibreOffice project.
#
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
#


import argparse
import xml.etree.ElementTree as ET
import os
from prompt_toolkit import prompt

# Define namespace prefixes and URIs
XML_NAMESPACE = "http://openoffice.org/2001/registry"
XML_SCHEMA_NAMESPACE = "http://www.w3.org/2001/XMLSchema"

# Template fragments for merging menu items
MERGE_MENU_TEMPLATE = """
<node oor:name="OfficeMenuBar">
    <node oor:name="{addon_name}" oor:op="replace" xmlns:oor={XML_NAMESPACE}>
        <node oor:name="{merge_name}" oor:op="replace">
            <prop oor:name="MergePoint">
                <value>{merge_point}</value>
            </prop>
            <prop oor:name="MergeCommand">
                <value>{merge_command}</value>
            </prop>
            <prop oor:name="MergeFallback">
                <value>{merge_fallback}</value>
            </prop>
            <prop oor:name="Context" oor:type="xs:string">
                <value>{merge_context}</value>
            </prop>
            <node oor:name="MenuItems">
                {menu_items}
            </node>
        </node>
    </node>
</node>
"""

# Template fragments for menu items
MENU_ITEM_TEMPLATE = """
<node oor:name="{item_name}" oor:op="replace">
    <prop oor:name="Title">
        <value xml:lang="en-US">{title}</value>
    </prop>
    <prop oor:name="URL">
        <value>{url}</value>
    </prop>
    <prop oor:name="ImageIdentifier">
        <value>{image_path}</value>
    </prop>
    <prop oor:name="Target" oor:type="xs:string">
        <value>{target}</value>
    </prop>
    {submenus}
</node>
"""

SUBMENU_TEMPLATE = """
<node oor:name="Submenus">
    {submenu_items}
</node>
"""

SUBMENU_ITEM_TEMPLATE = """
<node oor:name="{submenu_name}" oor:op="replace">
    <prop oor:name="Title">
        <value xml:lang="en-US">{title}</value>
    </prop>
    <prop oor:name="URL">
        <value>{url}</value>
    </prop>
    <prop oor:name="ImageIdentifier">
        <value>{image_path}</value>
    </prop>
    <prop oor:name="Target" oor:type="xs:string">
        <value>{target}</value>
    </prop>
</node>
"""

# Template fragments for merging toolbar items
MERGE_TOOLBAR_TEMPLATE = """
<node oor:name="OfficeToolBar">
    <node oor:name="{addon_name}" oor:op="replace" xmlns:oor={XML_NAMESPACE}>
        <node oor:name="{merge_name}" oor:op="replace">
            <prop oor:name="MergeToolBar">
                <value>{merge_toolbar}</value>
            </prop>
            <prop oor:name="MergePoint">
                <value>{merge_point}</value>
            </prop>
            <prop oor:name="MergeCommand">
                <value>{merge_command}</value>
            </prop>
            <prop oor:name="MergeFallback">
                <value>{merge_fallback}</value>
            </prop>
            <prop oor:name="Context" oor:type="xs:string">
                <value>{merge_context}</value>
            </prop>
            <node oor:name="ToolBarItems">
                {toolbar_items}
            </node>
        </node>
    </node>
</node>
"""

# Template fragments for toolbar items
TOOLBAR_ITEM_TEMPLATE = """
<node oor:name="{item_name}" oor:op="replace" xmlns:oor={XML_NAMESPACE}>
    <prop oor:name="Title" oor:type="xs:string">
        <value xml:lang="en-US">{title}</value>
    </prop>
    <prop oor:name="URL" oor:type="xs:string">
        <value>{url}</value>
    </prop>
    <prop oor:name="ImageIdentifier" oor:type="xs:string">
        <value>{image_path}</value>
    </prop>
    <prop oor:name="Target" oor:type="xs:string">
        <value>{target}</value>
    </prop>
</node>
"""

# Template fragment for images
IMAGES_TEMPLATE = """
<node oor:name="Images" oor:op="replace">
    <prop oor:name="ImageSmall">
        <value>{image_small}</value>
    </prop>
    <prop oor:name="ImageBig">
        <value>{image_big}</value>
    </prop>
    <prop oor:name="ImageSmallHC">
        <value>{image_small_hc}</value>
    </prop>
    <prop oor:name="ImageBigHC">
        <value>{image_big_hc}</value>
    </prop>
</node>
"""

# Template fragments for addon menu and help menu
ADDON_MENU_TEMPLATE = """
<node oor:name="{addon_menu_name}" oor:op="replace">
    <prop oor:name="Title">
        <value xml:lang="en-US">{title}</value>
    </prop>
    <prop oor:name="URL">
        <value>{url}</value>
    </prop>
    <prop oor:name="ImageIdentifier">
        <value>{image_path}</value>
    </prop>
    <prop oor:name="Target" oor:type="xs:string">
        <value>{target}</value>
    </prop>
</node>
"""

HELP_MENU_TEMPLATE = """
<node oor:name="{help_menu_name}" oor:op="replace">
    <prop oor:name="Title">
        <value xml:lang="en-US">{title}</value>
    </prop>
    <prop oor:name="URL">
        <value>{url}</value>
    </prop>
    <prop oor:name="ImageIdentifier">
        <value>{image_path}</value>
    </prop>
    <prop oor:name="Target" oor:type="xs:string">
        <value>{target}</value>
    </prop>
</node>
"""

#-------------------------- generating the xml functions --------------------------

def generate_menu_fragment(addon_name, merge_name, merge_point, merge_command, merge_fallback, merge_context, menu_items):
    merge_node = ET.Element('node', {'oor:name': 'OfficeMenuBar'})
    addon_node = ET.SubElement(merge_node, 'node', {'oor:name': addon_name, 'oor:op': 'replace', 'xmlns:oor': '{XML_NAMESPACE}'})
    merge_item_node = ET.SubElement(addon_node, 'node', {'oor:name': merge_name, 'oor:op': 'replace'})

    if merge_point:
        merge_point_node = ET.SubElement(merge_item_node, 'prop', {'oor:name': 'MergePoint'})
        ET.SubElement(merge_point_node, 'value').text = merge_point

    merge_command_node = ET.SubElement(merge_item_node, 'prop', {'oor:name': 'MergeCommand'})
    ET.SubElement(merge_command_node, 'value').text = merge_command

    if merge_fallback:
        merge_fallback_node = ET.SubElement(merge_item_node, 'prop', {'oor:name': 'MergeFallback'})
        ET.SubElement(merge_fallback_node, 'value').text = merge_fallback

    merge_context_node = ET.SubElement(merge_item_node, 'prop', {'oor:name': 'Context', 'oor:type': 'xs:string'})
    ET.SubElement(merge_context_node, 'value').text = merge_context

    menu_items_node = ET.SubElement(merge_item_node, 'node', {'oor:name': 'MenuItems'})
    for menu_item in menu_items:
        menu_items_node.append(ET.fromstring(menu_item))

    return merge_node

def generate_menu_item_fragment(item_name, title, url, image_path, target, submenus=None):
    menu_item_node = ET.Element('node', {'oor:name': item_name, 'oor:op': 'replace'})

    if title:
        title_node = ET.SubElement(menu_item_node, 'prop', {'oor:name': 'Title'})
        ET.SubElement(title_node, 'value', {'xml:lang': 'en-US'}).text = title

    if url:
        url_node = ET.SubElement(menu_item_node, 'prop', {'oor:name': 'URL'})
        ET.SubElement(url_node, 'value').text = url

    if image_path:
        image_node = ET.SubElement(menu_item_node, 'prop', {'oor:name': 'ImageIdentifier'})
        ET.SubElement(image_node, 'value').text = image_path

    target_node = ET.SubElement(menu_item_node, 'prop', {'oor:name': 'Target', 'oor:type': 'xs:string'})
    ET.SubElement(target_node, 'value').text = target

    if submenus:
        submenu_node = ET.SubElement(menu_item_node, 'node', {'oor:name': 'Submenus'})
        for submenu in submenus:
            submenu_node.append(ET.fromstring(submenu))

    return ET.tostring(menu_item_node, encoding='unicode')

def generate_submenu_item_fragment(submenu_name, title, url, image_path, target):
    submenu_node = ET.Element('node', {'oor:name': submenu_name, 'oor:op': 'replace'})

    if title:
        title_node = ET.SubElement(submenu_node, 'prop', {'oor:name': 'Title'})
        ET.SubElement(title_node, 'value', {'xml:lang': 'en-US'}).text = title

    if url:
        url_node = ET.SubElement(submenu_node, 'prop', {'oor:name': 'URL'})
        ET.SubElement(url_node, 'value').text = url

    if image_path:
        image_node = ET.SubElement(submenu_node, 'prop', {'oor:name': 'ImageIdentifier'})
        ET.SubElement(image_node, 'value').text = image_path

    target_node = ET.SubElement(submenu_node, 'prop', {'oor:name': 'Target', 'oor:type': 'xs:string'})
    ET.SubElement(target_node, 'value').text = target

    return ET.tostring(submenu_node, encoding='unicode')

def generate_toolbar_fragment(addon_name, merge_name, merge_toolbar, merge_point, merge_command, merge_fallback, merge_context, toolbar_items):
    merge_node = ET.Element('node', {'oor:name': 'OfficeToolBar'})
    addon_node = ET.SubElement(merge_node, 'node', {'oor:name': addon_name, 'oor:op': 'replace', 'xmlns:oor': '{XML_NAMESPACE}'})
    merge_item_node = ET.SubElement(addon_node, 'node', {'oor:name': merge_name, 'oor:op': 'replace'})

    if merge_toolbar:
        merge_toolbar_node = ET.SubElement(merge_item_node, 'prop', {'oor:name': 'MergeToolBar'})
        ET.SubElement(merge_toolbar_node, 'value').text = merge_toolbar

    if merge_point:
        merge_point_node = ET.SubElement(merge_item_node, 'prop', {'oor:name': 'MergePoint'})
        ET.SubElement(merge_point_node, 'value').text = merge_point

    merge_command_node = ET.SubElement(merge_item_node, 'prop', {'oor:name': 'MergeCommand'})
    ET.SubElement(merge_command_node, 'value').text = merge_command

    if merge_fallback:
        merge_fallback_node = ET.SubElement(merge_item_node, 'prop', {'oor:name': 'MergeFallback'})
        ET.SubElement(merge_fallback_node, 'value').text = merge_fallback

    merge_context_node = ET.SubElement(merge_item_node, 'prop', {'oor:name': 'Context', 'oor:type': 'xs:string'})
    ET.SubElement(merge_context_node, 'value').text = merge_context

    toolbar_items_node = ET.SubElement(merge_item_node, 'node', {'oor:name': 'ToolBarItems'})
    for toolbar_item in toolbar_items:
        toolbar_items_node.append(ET.fromstring(toolbar_item))

    return merge_node

def generate_toolbar_item_fragment(item_name, title, url, image_path, target, separator_position=None):
    toolbar_item_node = ET.Element('node', {'oor:name': item_name, 'oor:op': 'replace', 'xmlns:oor': '{XML_NAMESPACE}'})

    if title:
        title_node = ET.SubElement(toolbar_item_node, 'prop', {'oor:name': 'Title', 'oor:type': 'xs:string'})
        ET.SubElement(title_node, 'value', {'xml:lang': 'en-US'}).text = title

    if url:
        url_node = ET.SubElement(toolbar_item_node, 'prop', {'oor:name': 'URL', 'oor:type': 'xs:string'})
        ET.SubElement(url_node, 'value').text = url

    if image_path:
        image_node = ET.SubElement(toolbar_item_node, 'prop', {'oor:name': 'ImageIdentifier', 'oor:type': 'xs:string'})
        ET.SubElement(image_node, 'value').text = image_path

    target_node = ET.SubElement(toolbar_item_node, 'prop', {'oor:name': 'Target', 'oor:type': 'xs:string'})
    ET.SubElement(target_node, 'value').text = target

    if separator_position:
        separator_node = ET.SubElement(toolbar_item_node, 'prop', {'oor:name': 'SeparatorPosition'})
        ET.SubElement(separator_node, 'value').text = separator_position

    return ET.tostring(toolbar_item_node, encoding='unicode')


def generate_images_fragment(image_small, image_big, image_small_hc, image_big_hc):
    images_node = ET.Element('node', {'oor:name': 'Images', 'oor:op': 'replace'})

    if image_small:
        small_node = ET.SubElement(images_node, 'prop', {'oor:name': 'ImageSmall'})
        ET.SubElement(small_node, 'value').text = image_small

    if image_big:
        big_node = ET.SubElement(images_node, 'prop', {'oor:name': 'ImageBig'})
        ET.SubElement(big_node, 'value').text = image_big

    if image_small_hc:
        small_hc_node = ET.SubElement(images_node, 'prop', {'oor:name': 'ImageSmallHC'})
        ET.SubElement(small_hc_node, 'value').text = image_small_hc

    if image_big_hc:
        big_hc_node = ET.SubElement(images_node, 'prop', {'oor:name': 'ImageBigHC'})
        ET.SubElement(big_hc_node, 'value').text = image_big_hc

    return ET.tostring(images_node, encoding='unicode')

def generate_addon_menu_fragment(addon_menu_name, title, url, image_path, target):
    addon_menu_node = ET.Element('node', {'oor:name': addon_menu_name, 'oor:op': 'replace'})

    if title:
        title_node = ET.SubElement(addon_menu_node, 'prop', {'oor:name': 'Title'})
        ET.SubElement(title_node, 'value', {'xml:lang': 'en-US'}).text = title

    if url:
        url_node = ET.SubElement(addon_menu_node, 'prop', {'oor:name': 'URL'})
        ET.SubElement(url_node, 'value').text = url

    if image_path:
        image_node = ET.SubElement(addon_menu_node, 'prop', {'oor:name': 'ImageIdentifier'})
        ET.SubElement(image_node, 'value').text = image_path

    target_node = ET.SubElement(addon_menu_node, 'prop', {'oor:name': 'Target', 'oor:type': 'xs:string'})
    ET.SubElement(target_node, 'value').text = target

    return ET.tostring(addon_menu_node, encoding='unicode')

def generate_help_menu_fragment(help_menu_name, title, url, image_path, target):
    help_menu_node = ET.Element('node', {'oor:name': help_menu_name, 'oor:op': 'replace'})

    if title:
        title_node = ET.SubElement(help_menu_node, 'prop', {'oor:name': 'Title'})
        ET.SubElement(title_node, 'value', {'xml:lang': 'en-US'}).text = title

    if url:
        url_node = ET.SubElement(help_menu_node, 'prop', {'oor:name': 'URL'})
        ET.SubElement(url_node, 'value').text = url

    if image_path:
        image_node = ET.SubElement(help_menu_node, 'prop', {'oor:name': 'ImageIdentifier'})
        ET.SubElement(image_node, 'value').text = image_path

    target_node = ET.SubElement(help_menu_node, 'prop', {'oor:name': 'Target', 'oor:type': 'xs:string'})
    ET.SubElement(target_node, 'value').text = target

    return ET.tostring(help_menu_node, encoding='unicode')

def stitching_xcu(addon_name, merge_type, merge_name, merge_point, merge_command, merge_fallback, items, merge_toolbar, merge_context):
    if merge_type == 'menu':
        merge_fragment = generate_menu_fragment(addon_name, merge_name, merge_point, merge_command, merge_fallback, merge_context, menu_items=items)
    elif merge_type == 'toolbar':
        merge_fragment = generate_toolbar_fragment(addon_name, merge_name, merge_toolbar, merge_point, merge_command, merge_fallback, merge_context, toolbar_items=items)
    else:
        merge_fragment = ''

    return merge_fragment

def generate_xcu(merge_fragment, output_dir, addon_menu_fragment, help_menu_fragment, images_fragment):
    # Determine the output directory
    if not output_dir:
        while True:
            choice = input("\n\nSave Addons.xcu file to desktop or specify a path to custom directory? (d/c): ").lower()
            if choice in ("d", "desktop"):
                output_dir = os.path.join(os.path.expanduser('~'), 'Desktop')
                break
            elif choice == "c":
                custom_dir = input("\nEnter custom directory path: ")
                output_dir = validate_directory(custom_dir)
                if output_dir:
                    break
            else:
                print("\nInvalid choice. Please choose 'd' or 'c'.")
    
    root = ET.Element('{{{}}}component-data'.format(XML_NAMESPACE), {
        '{{{}}}package'.format(XML_NAMESPACE): 'org.openoffice.Office',
        '{{{}}}name'.format(XML_NAMESPACE): 'Addons',
        'xmlns:xs': XML_SCHEMA_NAMESPACE
    })

    addon_ui = ET.SubElement(root, 'node', {'oor:name': 'AddonUI'})

    if addon_menu_fragment:
        addon_ui.append(ET.fromstring(addon_menu_fragment))

    if merge_fragment:
        addon_ui.append(ET.fromstring(merge_fragment))

    if help_menu_fragment:
        addon_ui.append(ET.fromstring(help_menu_fragment))

    if images_fragment:
        addon_ui.append(ET.fromstring(images_fragment))

    xml_tree = ET.ElementTree(root)

    file_path = os.path.join(output_dir, 'Addons.xcu')
    with open(file_path, 'wb') as file:
        xml_tree.write(file, encoding='utf-8', xml_declaration=True)

#-------------------------- prompt message functions --------------------------

def prompt_merge_context():
    print("\nSelect the application module context for the merge instruction:")
    print("1. Writer")
    print("2. Spreadsheet")
    print("3. Presentation")
    print("4. Draw")
    print("5. Formula")
    print("6. Chart")
    print("7. Bibliography")
    choice = input("Enter your choice (1-7): ").strip()
    if choice == '1':
        return "com.sun.star.text.TextDocument"
    elif choice == '2':
        return "com.sun.star.sheet.SpreadsheetDocument"
    elif choice == '3':
        return "com.sun.star.presentation.PresentationDocument"
    elif choice == '4':
        return "com.sun.star.drawing.DrawingDocument"
    elif choice == '5':
        return "com.sun.star.formula.FormulaProperties"
    elif choice == '6':
        return "com.sun.star.chart.ChartDocument"
    elif choice == '7':
        return "com.sun.star.frame.Bibliography"
    else:
        print("Invalid choice. Please select again.")
        return prompt_merge_context()

def prompt_images():
    print("\nPlease provide paths to the [ .bmp ] images (type 'skip' to skip providing an image):")
    image_small = input("Path to small image: ").strip()
    if image_small.lower() == 'skip':
        image_small = None
    else:
        image_small = validate_image_path(image_small)

    image_big = input("Path to big image: ").strip()
    if image_big.lower() == 'skip':
        image_big = None
    else:
        image_big = validate_image_path(image_big)

    image_small_hc = input("Path to small high-contrast image: ").strip()
    if image_small_hc.lower() == 'skip':
        image_small_hc = None
    else:
        image_small_hc = validate_image_path(image_small_hc)

    image_big_hc = input("Path to big high-contrast image: ").strip()
    if image_big_hc.lower() == 'skip':
        image_big_hc = None
    else:
        image_big_hc = validate_image_path(image_big_hc)

    return image_small, image_big, image_small_hc, image_big_hc

def prompt_addon_name():
    addon_name = prompt("\nEnter addon name (extID name, e.g., com.<companyname>.<company-namespace>.<extension-namespace>): ").strip()
    return addon_name

def prompt_menu_title(menu_type):
    title = prompt(f"\nEnter {menu_type} item title (use '~' before the accelerator key, e.g., '~File'): ").strip()
    return title

def prompt_button_separator():
    print("\nDo you want to add a button separator?")
    print("1. Before the first button")
    print("2. Between the buttons")
    print("3. No separator")
    choice = input("Enter your choice (1-3): ").strip()
    if choice == '1':
        return "before_first"
    elif choice == '2':
        return "between_buttons"
    elif choice == '3':
        return "none"
    else:
        print("Invalid choice. Please select again.")
        return prompt_button_separator()

def prompt_image_path(merge_type):
    while True:
        image_path = prompt(f"\nEnter path to {merge_type} item image (optional, or 'skip' to skip): ").strip().lower()
        if image_path == 'skip':
            return None
        image_path = validate_image_path(image_path)
        if image_path:
            return image_path
        print("Invalid file type. Please try again or enter 'skip' to skip.")

#-------------------------- validating provided path --------------------------

def validate_directory(directory_path):
    if not directory_path:
        return None  # No output directory provided
    try:
        if not os.path.isdir(directory_path):
            raise ValueError("\nInvalid directory: Path is not a directory")
        # Additional validations can be added here if needed
    except ValueError as e:
        print(f"Error: {e}")
        return None
    return directory_path

def validate_image_path(image_path):
    if not image_path:
        return None  # No image path provided
    try:
        if not os.path.exists(image_path):
            raise ValueError("\nInvalid path: Path does not exist")
        _, ext = os.path.splitext(image_path)
        if ext.lower() != '.bmp':
            raise ValueError("\nInvalid file type: Only .bmp files are allowed")
        # Additional validations can be added here if needed
    except ValueError as e:
        print(f"Error: {e}")
        return None
    return image_path

#-------------------------- MAIN --------------------------

def main():
    print("\nWelcome to the LibreOffice Addons Configuration Tool!\n")
    merge_fragment = ''
    addon_name = prompt_addon_name()
    addon_menu_fragment = ''
    help_menu_fragment = ''
    images_fragment = ''

    # Prompt for addon menu item
    choice = prompt("\nDo you want to create the 'AddonMenu' item (Tools > Add-ons)? (y/n): ").strip().lower()
    if choice == 'y':
        addon_menu_name = prompt("\nEnter addon menu item name (Tools > Add-ons): ").strip()
        addon_menu_title = prompt_menu_title("addon menu")
        addon_menu_url = prompt("\nEnter addon menu item URL: ").strip()
        addon_menu_target = prompt("\nEnter addon menu item target (_self, _default, _top, _parent, or _blank): ").strip()
        addon_menu_image_path = prompt("\nEnter path to addon menu item image (optional): ").strip()
        addon_menu_image_path = validate_image_path(addon_menu_image_path)
        addon_menu_fragment = generate_addon_menu_fragment(addon_menu_name, addon_menu_title, addon_menu_url, addon_menu_image_path, addon_menu_target)

    # Prompt for help menu item
    choice = prompt("\nDo you want to create the 'OfficeHelp' item (Help menu)? (y/n): ").strip().lower()
    if choice == 'y':
        help_menu_name = prompt("\nEnter help menu item name (Help menu): ").strip()
        help_menu_title = prompt_menu_title("help menu")
        help_menu_url = prompt("\nEnter help menu item URL: ").strip()
        help_menu_target = prompt("\nEnter help menu item target (_self, _default, _top, _parent, or _blank): ").strip()
        help_menu_image_path = prompt("\nEnter path to help menu item image (optional): ").strip()
        help_menu_image_path = validate_image_path(help_menu_image_path)
        help_menu_fragment = generate_help_menu_fragment(help_menu_name, help_menu_title, help_menu_url, help_menu_image_path, help_menu_target)

    # Prompt for images
    choice = prompt("\nImages attribute defines the icons that appear next to the text in the menu and toolbar items.\nDo you want to provide images? (y/n): ").strip().lower()
    if choice == 'y':
        image_small, image_big, image_small_hc, image_big_hc = prompt_images()
        images_fragment = generate_images_fragment(image_small, image_big, image_small_hc, image_big_hc)

    while True:  # Outer loop for gathering input for each merge
        items = []  # Initialize items list for each merge type
        merge_type = prompt("\n\nEnter merge type (menu or toolbar): ").strip()

        while merge_type not in ['menu', 'toolbar']:
            merge_type = prompt("\nInvalid input. \nEnter merge type (menu or toolbar): ").strip()

        # Gather input for merge
        merge_name = prompt("\nEnter merge name (m<No> name for menu, label[] for toolbar): \nFor example:\nFor a menu merge operation, you could enter something like 'm1' or 'm_print_options'.\nFor a toolbar merge operation, you could enter something like [PrintButton] or [FormatToolbar]: ").strip()
        merge_point = prompt("\nEnter merge point: ").strip()
        merge_command = prompt("\nEnter merge command (possible merge commands are: AddAfter, AddBefore, Replace, Remove):  ").strip()
        merge_fallback = prompt("\nEnter merge fallback (possible merge fallbacks are: AddAfter, AddBefore, Replace, Remove):  ").strip()
        merge_toolbar = None
        output_dir = None
        merge_context = prompt_merge_context()

        while True:  # Inner loop for gathering input for items
            item_name = prompt(f"\nEnter {merge_type} item name (or 'done' to finish): ").strip().lower()
            if item_name.lower() == 'done':
                break

            title = prompt_menu_title(f"{merge_type} item")
            url = prompt(f"\nEnter {merge_type} item URL (can be a macro path or dispatch command): ").strip()
            target = prompt(f"\nEnter {merge_type} item target (_self, _default, _top, _parent, or _blank): ").strip()
            image_path = prompt_image_path(merge_type)

            # Prompt for submenus
            submenus = []
            add_submenu = prompt(f"\nAdd submenu for this {merge_type} item? (y/n): ").strip().lower()
            if add_submenu == 'y':
                while True:
                    submenu_name = prompt("\nEnter submenu name (or 'done' to finish): ").strip().lower()
                    if submenu_name.lower() == 'done':
                        break
                    submenu_title = prompt_menu_title("submenu")
                    submenu_url = prompt("\nEnter submenu URL: ").strip()
                    submenu_target = prompt("\nEnter submenu target (_self, _default, _top, _parent, or _blank): ").strip()
                    submenu_image_path = prompt("\nEnter path to submenu image (optional): ").strip()
                    submenu_image_path = validate_image_path(submenu_image_path)
                    submenus.append(generate_submenu_item_fragment(submenu_name, submenu_title, submenu_url, submenu_image_path, submenu_target))

            # Generate item fragment based on merge type
            if merge_type == 'menu':
                items.append(generate_menu_item_fragment(item_name, title, url, image_path, target, submenus))
            elif merge_type == 'toolbar':
                merge_toolbar = prompt("\nEnter the Merge ToolBar name\n(specifies the appearance of the floating toolbar reached via View > Toolbars > Add-on) : ").strip()
                separator_position = prompt_button_separator()
                items.append(generate_toolbar_item_fragment(item_name, title, url, image_path, target, separator_position))

        merge_fragment += stitching_xcu(addon_name, merge_type, merge_name, merge_point, merge_command, merge_fallback, items, merge_toolbar, merge_context)

        keep_merging = prompt("\n\n\nContinue to modify Menu or Toolbar? ( y or n ): ").strip().lower()
        if keep_merging == 'n':
            # Generate .xcu file
            generate_xcu(merge_fragment, output_dir, addon_menu_fragment, help_menu_fragment, images_fragment)
            break
    print("Thank you for using the Addons.xcu file generation. Have a Nice Day!")

if __name__ == "__main__":
    main()

@varshneydevansh
Copy link
Author

This is working only problem is improper indentation and some repeating merging options as seen in the o/p file attaching at the last -

# -*- tab-width: 4; indent-tabs-mode: nil; py-indent-offset: 4 -*-
#
# This file is part of the LibreOffice project.
#
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
#


import argparse
import xml.etree.ElementTree as ET
import os
from prompt_toolkit import prompt
import xml.dom.minidom

# Template fragments for merging menu items
MERGE_MENU_TEMPLATE = """
<node oor:name="OfficeMenuBar">
    <node oor:name="{addon_name}" oor:op="replace" xmlns:oor="http://openoffice.org/2001/registry">
        <node oor:name="{merge_name}" oor:op="replace">
            <prop oor:name="MergePoint">
                <value>{merge_point}</value>
            </prop>
            <prop oor:name="MergeCommand">
                <value>{merge_command}</value>
            </prop>
            <prop oor:name="MergeFallback">
                <value>{merge_fallback}</value>
            </prop>
            <prop oor:name="Context" oor:type="xs:string">
                <value>{merge_context}</value>
            </prop>
            <node oor:name="MenuItems">
                {menu_items}
            </node>
        </node>
    </node>
</node>
"""

# Template fragments for menu items
MENU_ITEM_TEMPLATE = """
<node oor:name="{item_name}" oor:op="replace">
    <prop oor:name="Title">
        <value xml:lang="en-US">{title}</value>
    </prop>
    <prop oor:name="URL">
        <value>{url}</value>
    </prop>
    <prop oor:name="ImageIdentifier">
        <value>{image_path}</value>
    </prop>
    <prop oor:name="Target" oor:type="xs:string">
        <value>{target}</value>
    </prop>
    {submenus}
</node>
"""

SUBMENU_TEMPLATE = """
<node oor:name="Submenus">
    {submenu_items}
</node>
"""

SUBMENU_ITEM_TEMPLATE = """
<node oor:name="{submenu_name}" oor:op="replace">
    <prop oor:name="Title">
        <value xml:lang="en-US">{title}</value>
    </prop>
    <prop oor:name="URL">
        <value>{url}</value>
    </prop>
    <prop oor:name="ImageIdentifier">
        <value>{image_path}</value>
    </prop>
    <prop oor:name="Target" oor:type="xs:string">
        <value>{target}</value>
    </prop>
</node>
"""

# Template fragments for merging toolbar items
MERGE_TOOLBAR_TEMPLATE = """
<node oor:name="OfficeToolBar">
    <node oor:name="{addon_name}" oor:op="replace" xmlns:oor="http://openoffice.org/2001/registry">
        <node oor:name="{merge_name}" oor:op="replace">
            <prop oor:name="MergeToolBar">
                <value>{merge_toolbar}</value>
            </prop>
            <prop oor:name="MergePoint">
                <value>{merge_point}</value>
            </prop>
            <prop oor:name="MergeCommand">
                <value>{merge_command}</value>
            </prop>
            <prop oor:name="MergeFallback">
                <value>{merge_fallback}</value>
            </prop>
            <prop oor:name="Context" oor:type="xs:string">
                <value>{merge_context}</value>
            </prop>
            <node oor:name="ToolBarItems">
                {toolbar_items}
            </node>
        </node>
    </node>
</node>
"""

# Template fragments for toolbar items
TOOLBAR_ITEM_TEMPLATE = """
<node oor:name="{item_name}" oor:op="replace" xmlns:oor="http://openoffice.org/2001/registry">
    <prop oor:name="Title" oor:type="xs:string">
        <value xml:lang="en-US">{title}</value>
    </prop>
    <prop oor:name="URL" oor:type="xs:string">
        <value>{url}</value>
    </prop>
    <prop oor:name="ImageIdentifier" oor:type="xs:string">
        <value>{image_path}</value>
    </prop>
    <prop oor:name="Target" oor:type="xs:string">
        <value>{target}</value>
    </prop>
</node>
"""

# Template fragment for images
IMAGES_TEMPLATE = """
<node oor:name="Images" oor:op="replace">
    <prop oor:name="ImageSmall">
        <value>{image_small}</value>
    </prop>
    <prop oor:name="ImageBig">
        <value>{image_big}</value>
    </prop>
    <prop oor:name="ImageSmallHC">
        <value>{image_small_hc}</value>
    </prop>
    <prop oor:name="ImageBigHC">
        <value>{image_big_hc}</value>
    </prop>
</node>
"""

# Template fragments for addon menu and help menu
ADDON_MENU_TEMPLATE = """
<node oor:name="{addon_menu_name}" oor:op="replace">
    <prop oor:name="Title">
        <value xml:lang="en-US">{title}</value>
    </prop>
    <prop oor:name="URL">
        <value>{url}</value>
    </prop>
    <prop oor:name="ImageIdentifier">
        <value>{image_path}</value>
    </prop>
    <prop oor:name="Target" oor:type="xs:string">
        <value>{target}</value>
    </prop>
</node>
"""

HELP_MENU_TEMPLATE = """
<node oor:name="{help_menu_name}" oor:op="replace">
    <prop oor:name="Title">
        <value xml:lang="en-US">{title}</value>
    </prop>
    <prop oor:name="URL">
        <value>{url}</value>
    </prop>
    <prop oor:name="ImageIdentifier">
        <value>{image_path}</value>
    </prop>
    <prop oor:name="Target" oor:type="xs:string">
        <value>{target}</value>
    </prop>
</node>
"""

#-------------------------- generating the xml functions --------------------------

def generate_menu_fragment(addon_name, merge_name, merge_point, merge_command, merge_fallback, merge_context, menu_items):
    ns = {'oor': 'http://openoffice.org/2001/registry'}
    ET.register_namespace('oor', 'http://openoffice.org/2001/registry')

    merge_node = ET.Element('{http://openoffice.org/2001/registry}node', {'oor:name': 'OfficeMenuBar'})
    addon_node = ET.SubElement(merge_node, '{http://openoffice.org/2001/registry}node', {'oor:name': addon_name, 'oor:op': 'replace'})
    merge_item_node = ET.SubElement(addon_node, '{http://openoffice.org/2001/registry}node', {'oor:name': merge_name, 'oor:op': 'replace'})

    if merge_point:
        merge_point_node = ET.SubElement(merge_item_node, 'prop', {'oor:name': 'MergePoint'})
        ET.SubElement(merge_point_node, 'value').text = merge_point

    merge_command_node = ET.SubElement(merge_item_node, 'prop', {'oor:name': 'MergeCommand'})
    ET.SubElement(merge_command_node, 'value').text = merge_command

    if merge_fallback:
        merge_fallback_node = ET.SubElement(merge_item_node, 'prop', {'oor:name': 'MergeFallback'})
        ET.SubElement(merge_fallback_node, 'value').text = merge_fallback

    merge_context_node = ET.SubElement(merge_item_node, 'prop', {'oor:name': 'Context', 'oor:type': 'xs:string'})
    ET.SubElement(merge_context_node, 'value').text = merge_context

    menu_items_node = ET.SubElement(merge_item_node, '{http://openoffice.org/2001/registry}node', {'oor:name': 'MenuItems'})
    for menu_item in menu_items:
        root = ET.fromstring(f"<root xmlns:oor='http://openoffice.org/2001/registry'>{menu_item}</root>")
        menu_item_node = root.find('oor:node', ns)
        if menu_item_node is not None:
            menu_items_node.extend(menu_item_node)

    return merge_node

def generate_menu_item_fragment(item_name, title, url, image_path, target, submenus=None):
    menu_item_node = ET.Element('node', {'oor:name': item_name, 'oor:op': 'replace'})

    if title:
        title_node = ET.SubElement(menu_item_node, 'prop', {'oor:name': 'Title'})
        ET.SubElement(title_node, 'value', {'xml:lang': 'en-US'}).text = title

    if url:
        url_node = ET.SubElement(menu_item_node, 'prop', {'oor:name': 'URL'})
        ET.SubElement(url_node, 'value').text = url

    if image_path:
        image_node = ET.SubElement(menu_item_node, 'prop', {'oor:name': 'ImageIdentifier'})
        ET.SubElement(image_node, 'value').text = image_path

    target_node = ET.SubElement(menu_item_node, 'prop', {'oor:name': 'Target', 'oor:type': 'xs:string'})
    ET.SubElement(target_node, 'value').text = target

    if submenus:
        submenu_node = ET.SubElement(menu_item_node, 'node', {'oor:name': 'Submenus'})
        for submenu in submenus:
            submenu_node.append(ET.fromstring(submenu))

    return ET.tostring(menu_item_node, encoding='unicode')

def generate_submenu_item_fragment(submenu_name, title, url, image_path, target):
    submenu_node = ET.Element('node', {'oor:name': submenu_name, 'oor:op': 'replace'})

    if title:
        title_node = ET.SubElement(submenu_node, 'prop', {'oor:name': 'Title'})
        ET.SubElement(title_node, 'value', {'xml:lang': 'en-US'}).text = title

    if url:
        url_node = ET.SubElement(submenu_node, 'prop', {'oor:name': 'URL'})
        ET.SubElement(url_node, 'value').text = url

    if image_path:
        image_node = ET.SubElement(submenu_node, 'prop', {'oor:name': 'ImageIdentifier'})
        ET.SubElement(image_node, 'value').text = image_path

    target_node = ET.SubElement(submenu_node, 'prop', {'oor:name': 'Target', 'oor:type': 'xs:string'})
    ET.SubElement(target_node, 'value').text = target

    return ET.tostring(submenu_node, encoding='unicode')


def generate_toolbar_fragment(addon_name, merge_name, merge_toolbar, merge_point, merge_command, merge_fallback, merge_context, toolbar_items):
    ns = {'oor': 'http://openoffice.org/2001/registry'}
    ET.register_namespace('oor', 'http://openoffice.org/2001/registry')

    merge_node = ET.Element('{http://openoffice.org/2001/registry}node', {'oor:name': 'OfficeToolBar'})
    addon_node = ET.SubElement(merge_node, '{http://openoffice.org/2001/registry}node', {'oor:name': addon_name, 'oor:op': 'replace'})
    merge_item_node = ET.SubElement(addon_node, '{http://openoffice.org/2001/registry}node', {'oor:name': merge_name, 'oor:op': 'replace'})

    if merge_toolbar:
        merge_toolbar_node = ET.SubElement(merge_item_node, 'prop', {'oor:name': 'MergeToolBar'})
        ET.SubElement(merge_toolbar_node, 'value').text = merge_toolbar

    if merge_point:
        merge_point_node = ET.SubElement(merge_item_node, 'prop', {'oor:name': 'MergePoint'})
        ET.SubElement(merge_point_node, 'value').text = merge_point

    merge_command_node = ET.SubElement(merge_item_node, 'prop', {'oor:name': 'MergeCommand'})
    ET.SubElement(merge_command_node, 'value').text = merge_command

    if merge_fallback:
        merge_fallback_node = ET.SubElement(merge_item_node, 'prop', {'oor:name': 'MergeFallback'})
        ET.SubElement(merge_fallback_node, 'value').text = merge_fallback

    merge_context_node = ET.SubElement(merge_item_node, 'prop', {'oor:name': 'Context', 'oor:type': 'xs:string'})
    ET.SubElement(merge_context_node, 'value').text = merge_context

    toolbar_items_node = ET.SubElement(merge_item_node, '{http://openoffice.org/2001/registry}node', {'oor:name': 'ToolBarItems'})
    for toolbar_item in toolbar_items:
        root = ET.fromstring(f"<root xmlns:oor='http://openoffice.org/2001/registry'>{toolbar_item}</root>")
        toolbar_item_node = root.find('oor:node', ns)
        if toolbar_item_node is not None:
            toolbar_items_node.extend(toolbar_item_node)

    return merge_node

def generate_toolbar_item_fragment(item_name, title, url, image_path, target, separator_position=None):
    toolbar_item_node = ET.Element('node', {'oor:name': item_name, 'oor:op': 'replace', 'xmlns:oor': 'http://openoffice.org/2001/registry'})

    if title:
        title_node = ET.SubElement(toolbar_item_node, 'prop', {'oor:name': 'Title', 'oor:type': 'xs:string'})
        ET.SubElement(title_node, 'value', {'xml:lang': 'en-US'}).text = title

    if url:
        url_node = ET.SubElement(toolbar_item_node, 'prop', {'oor:name': 'URL', 'oor:type': 'xs:string'})
        ET.SubElement(url_node, 'value').text = url

    if image_path:
        image_node = ET.SubElement(toolbar_item_node, 'prop', {'oor:name': 'ImageIdentifier', 'oor:type': 'xs:string'})
        ET.SubElement(image_node, 'value').text = image_path

    target_node = ET.SubElement(toolbar_item_node, 'prop', {'oor:name': 'Target', 'oor:type': 'xs:string'})
    ET.SubElement(target_node, 'value').text = target

    if separator_position:
        separator_node = ET.SubElement(toolbar_item_node, 'prop', {'oor:name': 'SeparatorPosition'})
        ET.SubElement(separator_node, 'value').text = separator_position

    return ET.tostring(toolbar_item_node, encoding='unicode')


def generate_images_fragment(image_small, image_big, image_small_hc, image_big_hc):
    images_node = ET.Element('node', {'oor:name': 'Images', 'oor:op': 'replace'})

    if image_small:
        small_node = ET.SubElement(images_node, 'prop', {'oor:name': 'ImageSmall'})
        ET.SubElement(small_node, 'value').text = image_small

    if image_big:
        big_node = ET.SubElement(images_node, 'prop', {'oor:name': 'ImageBig'})
        ET.SubElement(big_node, 'value').text = image_big

    if image_small_hc:
        small_hc_node = ET.SubElement(images_node, 'prop', {'oor:name': 'ImageSmallHC'})
        ET.SubElement(small_hc_node, 'value').text = image_small_hc

    if image_big_hc:
        big_hc_node = ET.SubElement(images_node, 'prop', {'oor:name': 'ImageBigHC'})
        ET.SubElement(big_hc_node, 'value').text = image_big_hc

    return ET.tostring(images_node, encoding='unicode')

def generate_addon_menu_fragment(addon_menu_name, title, url, image_path, target):
    addon_menu_node = ET.Element('node', {'oor:name': addon_menu_name, 'oor:op': 'replace'})

    if title:
        title_node = ET.SubElement(addon_menu_node, 'prop', {'oor:name': 'Title'})
        ET.SubElement(title_node, 'value', {'xml:lang': 'en-US'}).text = title

    if url:
        url_node = ET.SubElement(addon_menu_node, 'prop', {'oor:name': 'URL'})
        ET.SubElement(url_node, 'value').text = url

    if image_path:
        image_node = ET.SubElement(addon_menu_node, 'prop', {'oor:name': 'ImageIdentifier'})
        ET.SubElement(image_node, 'value').text = image_path

    target_node = ET.SubElement(addon_menu_node, 'prop', {'oor:name': 'Target', 'oor:type': 'xs:string'})
    ET.SubElement(target_node, 'value').text = target

    return ET.tostring(addon_menu_node, encoding='unicode')

def generate_help_menu_fragment(help_menu_name, title, url, image_path, target):
    help_menu_node = ET.Element('node', {'oor:name': help_menu_name, 'oor:op': 'replace'})

    if title:
        title_node = ET.SubElement(help_menu_node, 'prop', {'oor:name': 'Title'})
        ET.SubElement(title_node, 'value', {'xml:lang': 'en-US'}).text = title

    if url:
        url_node = ET.SubElement(help_menu_node, 'prop', {'oor:name': 'URL'})
        ET.SubElement(url_node, 'value').text = url

    if image_path:
        image_node = ET.SubElement(help_menu_node, 'prop', {'oor:name': 'ImageIdentifier'})
        ET.SubElement(image_node, 'value').text = image_path

    target_node = ET.SubElement(help_menu_node, 'prop', {'oor:name': 'Target', 'oor:type': 'xs:string'})
    ET.SubElement(target_node, 'value').text = target

    return ET.tostring(help_menu_node, encoding='unicode')

def stitching_xcu(addon_name, merge_type, merge_name, merge_point, merge_command, merge_fallback, items, merge_toolbar, merge_context):
    if merge_type == 'menu':
        merge_fragment = generate_menu_fragment(addon_name, merge_name, merge_point, merge_command, merge_fallback, merge_context, menu_items=items)
    elif merge_type == 'toolbar':
        merge_fragment = generate_toolbar_fragment(addon_name, merge_name, merge_toolbar, merge_point, merge_command, merge_fallback, merge_context, toolbar_items=items)
    else:
        merge_fragment = ''

    return ET.tostring(merge_fragment, encoding='unicode')

def generate_xcu(merge_fragment, output_dir, addon_menu_fragment, help_menu_fragment, images_fragment):
    ns = {'oor': 'http://openoffice.org/2001/registry', 'xs': 'http://www.w3.org/2001/XMLSchema'}
    ET.register_namespace('oor', 'http://openoffice.org/2001/registry')
    ET.register_namespace('xs', 'http://www.w3.org/2001/XMLSchema')

    # Determine the output directory
    if not output_dir:
        while True:
            choice = input("\n\nSave Addons.xcu file to desktop or specify a path to custom directory? (d/c): ").lower()
            if choice in ("d", "desktop"):
                output_dir = os.path.join(os.path.expanduser('~'), 'Desktop')
                break
            elif choice == "c":
                custom_dir = input("\nEnter custom directory path: ")
                output_dir = validate_directory(custom_dir)
                if output_dir:
                    break
            else:
                print("\nInvalid choice. Please choose 'd' or 'c'.")

    root = ET.Element('{http://openoffice.org/2001/registry}oor:component-data', {
        'oor:package': 'org.openoffice.Office',
        'oor:name': 'Addons',
        'xmlns:xs': 'http://www.w3.org/2001/XMLSchema'
    })

    addon_ui = ET.SubElement(root, '{http://openoffice.org/2001/registry}node', {'oor:name': 'AddonUI'})

    if addon_menu_fragment:
        root_addon_menu = ET.fromstring(f"<root xmlns:oor='http://openoffice.org/2001/registry'>{addon_menu_fragment}</root>")
        addon_ui.extend(root_addon_menu.findall('oor:node', ns))

    if merge_fragment:
        root_merge = ET.fromstring(merge_fragment)
        for node in root_merge.iter('{http://openoffice.org/2001/registry}node'):
            addon_ui.append(node)

    if help_menu_fragment:
        root_help_menu = ET.fromstring(f"<root xmlns:oor='http://openoffice.org/2001/registry'>{help_menu_fragment}</root>")
        addon_ui.extend(root_help_menu.findall('oor:node', ns))

    if images_fragment:
        root_images = ET.fromstring(f"<root xmlns:oor='http://openoffice.org/2001/registry'>{images_fragment}</root>")
        addon_ui.extend(root_images.findall('oor:node', ns))

    xml_tree = ET.ElementTree(root)

    file_path = os.path.join(output_dir, 'Addons.xcu')
    with open(file_path, 'wb') as file:
        xml_tree.write(file, encoding='utf-8', xml_declaration=True)

#-------------------------- prompt message functions --------------------------

def prompt_merge_context():
    print("\nSelect the application module context for the merge instruction:")
    print("1. Writer")
    print("2. Spreadsheet")
    print("3. Presentation")
    print("4. Draw")
    print("5. Formula")
    print("6. Chart")
    print("7. Bibliography")
    choice = input("Enter your choice (1-7): ").strip()
    if choice == '1':
        return "com.sun.star.text.TextDocument"
    elif choice == '2':
        return "com.sun.star.sheet.SpreadsheetDocument"
    elif choice == '3':
        return "com.sun.star.presentation.PresentationDocument"
    elif choice == '4':
        return "com.sun.star.drawing.DrawingDocument"
    elif choice == '5':
        return "com.sun.star.formula.FormulaProperties"
    elif choice == '6':
        return "com.sun.star.chart.ChartDocument"
    elif choice == '7':
        return "com.sun.star.frame.Bibliography"
    else:
        print("Invalid choice. Please select again.")
        return prompt_merge_context()

def prompt_images():
    print("\nPlease provide paths to the [ .bmp ] images (type 'skip' to skip providing an image):")
    image_small = input("Path to small image: ").strip()
    if image_small.lower() == 'skip':
        image_small = None
    else:
        image_small = validate_image_path(image_small)

    image_big = input("Path to big image: ").strip()
    if image_big.lower() == 'skip':
        image_big = None
    else:
        image_big = validate_image_path(image_big)

    image_small_hc = input("Path to small high-contrast image: ").strip()
    if image_small_hc.lower() == 'skip':
        image_small_hc = None
    else:
        image_small_hc = validate_image_path(image_small_hc)

    image_big_hc = input("Path to big high-contrast image: ").strip()
    if image_big_hc.lower() == 'skip':
        image_big_hc = None
    else:
        image_big_hc = validate_image_path(image_big_hc)

    return image_small, image_big, image_small_hc, image_big_hc

def prompt_addon_name():
    addon_name = prompt("\nEnter addon name (extID name, e.g., com.<companyname>.<company-namespace>.<extension-namespace>): ").strip()
    return addon_name

def prompt_menu_title(menu_type):
    title = prompt(f"\nEnter {menu_type} item title (use '~' before the accelerator key, e.g., '~File'): ").strip()
    return title

def prompt_button_separator():
    print("\nDo you want to add a button separator?")
    print("1. Before the first button")
    print("2. Between the buttons")
    print("3. No separator")
    choice = input("Enter your choice (1-3): ").strip()
    if choice == '1':
        return "before_first"
    elif choice == '2':
        return "between_buttons"
    elif choice == '3':
        return "none"
    else:
        print("Invalid choice. Please select again.")
        return prompt_button_separator()

def prompt_image_path(merge_type):
    while True:
        image_path = prompt(f"\nEnter path to {merge_type} item image (optional, or 'skip' to skip): ").strip().lower()
        if image_path == 'skip':
            return None
        image_path = validate_image_path(image_path)
        if image_path:
            return image_path
        print("Invalid file type. Please try again or enter 'skip' to skip.")

#-------------------------- validating provided path --------------------------

def validate_directory(directory_path):
    if not directory_path:
        return None  # No output directory provided
    try:
        if not os.path.isdir(directory_path):
            raise ValueError("\nInvalid directory: Path is not a directory")
        # Additional validations can be added here if needed
    except ValueError as e:
        print(f"Error: {e}")
        return None
    return directory_path

def validate_image_path(image_path):
    if not image_path:
        return None  # No image path provided
    try:
        if not os.path.exists(image_path):
            raise ValueError("\nInvalid path: Path does not exist")
        _, ext = os.path.splitext(image_path)
        if ext.lower() != '.bmp':
            raise ValueError("\nInvalid file type: Only .bmp files are allowed")
        # Additional validations can be added here if needed
    except ValueError as e:
        print(f"Error: {e}")
        return None
    return image_path

#-------------------------- MAIN --------------------------

def main():
    print("\nWelcome to the LibreOffice Addons Configuration Tool!\n")
    merge_fragment = ''
    addon_name = prompt_addon_name()
    addon_menu_fragment = ''
    help_menu_fragment = ''
    images_fragment = ''

    # Prompt for addon menu item
    choice = prompt("\nDo you want to create the 'AddonMenu' item (Tools > Add-ons)? (y/n): ").strip().lower()
    if choice == 'y':
        addon_menu_name = prompt("\nEnter addon menu item name (Tools > Add-ons): ").strip()
        addon_menu_title = prompt_menu_title("addon menu")
        addon_menu_url = prompt("\nEnter addon menu item URL: ").strip()
        addon_menu_target = prompt("\nEnter addon menu item target (_self, _default, _top, _parent, or _blank): ").strip()
        addon_menu_image_path = prompt("\nEnter path to addon menu item image (optional): ").strip()
        addon_menu_image_path = validate_image_path(addon_menu_image_path)
        addon_menu_fragment = generate_addon_menu_fragment(addon_menu_name, addon_menu_title, addon_menu_url, addon_menu_image_path, addon_menu_target)

    # Prompt for help menu item
    choice = prompt("\nDo you want to create the 'OfficeHelp' item (Help menu)? (y/n): ").strip().lower()
    if choice == 'y':
        help_menu_name = prompt("\nEnter help menu item name (Help menu): ").strip()
        help_menu_title = prompt_menu_title("help menu")
        help_menu_url = prompt("\nEnter help menu item URL: ").strip()
        help_menu_target = prompt("\nEnter help menu item target (_self, _default, _top, _parent, or _blank): ").strip()
        help_menu_image_path = prompt("\nEnter path to help menu item image (optional): ").strip()
        help_menu_image_path = validate_image_path(help_menu_image_path)
        help_menu_fragment = generate_help_menu_fragment(help_menu_name, help_menu_title, help_menu_url, help_menu_image_path, help_menu_target)

    # Prompt for images
    choice = prompt("\nImages attribute defines the icons that appear next to the text in the menu and toolbar items.\nDo you want to provide images? (y/n): ").strip().lower()
    if choice == 'y':
        image_small, image_big, image_small_hc, image_big_hc = prompt_images()
        images_fragment = generate_images_fragment(image_small, image_big, image_small_hc, image_big_hc)

    while True:  # Outer loop for gathering input for each merge
        items = []  # Initialize items list for each merge type
        merge_type = prompt("\n\nEnter merge type (menu or toolbar): ").strip()

        while merge_type not in ['menu', 'toolbar']:
            merge_type = prompt("\nInvalid input. \nEnter merge type (menu or toolbar): ").strip()

        # Gather input for merge
        merge_name = prompt("\nEnter merge name (m<No> name for menu, label[] for toolbar): \nFor example:\nFor a menu merge operation, you could enter something like 'm1' or 'm_print_options'.\nFor a toolbar merge operation, you could enter something like [PrintButton] or [FormatToolbar]: ").strip()
        merge_point = prompt("\nEnter merge point: ").strip()
        merge_command = prompt("\nEnter merge command (possible merge commands are: AddAfter, AddBefore, Replace, Remove):  ").strip()
        merge_fallback = prompt("\nEnter merge fallback (possible merge fallbacks are: AddAfter, AddBefore, Replace, Remove):  ").strip()
        merge_toolbar = None
        output_dir = None
        merge_context = prompt_merge_context()

        while True:  # Inner loop for gathering input for items
            item_name = prompt(f"\nEnter {merge_type} item name (or 'done' to finish): ").strip().lower()
            if item_name.lower() == 'done':
                break

            title = prompt_menu_title(f"{merge_type} item")
            url = prompt(f"\nEnter {merge_type} item URL (can be a macro path or dispatch command): ").strip()
            target = prompt(f"\nEnter {merge_type} item target (_self, _default, _top, _parent, or _blank): ").strip()
            image_path = prompt_image_path(merge_type)

            # Prompt for submenus
            submenus = []
            add_submenu = prompt(f"\nAdd submenu for this {merge_type} item? (y/n): ").strip().lower()
            if add_submenu == 'y':
                while True:
                    submenu_name = prompt("\nEnter submenu name (or 'done' to finish): ").strip().lower()
                    if submenu_name.lower() == 'done':
                        break
                    submenu_title = prompt_menu_title("submenu")
                    submenu_url = prompt("\nEnter submenu URL: ").strip()
                    submenu_target = prompt("\nEnter submenu target (_self, _default, _top, _parent, or _blank): ").strip()
                    submenu_image_path = prompt("\nEnter path to submenu image (optional): ").strip()
                    submenu_image_path = validate_image_path(submenu_image_path)
                    submenus.append(generate_submenu_item_fragment(submenu_name, submenu_title, submenu_url, submenu_image_path, submenu_target))

            # Generate item fragment based on merge type
            if merge_type == 'menu':
                items.append(generate_menu_item_fragment(item_name, title, url, image_path, target, submenus))
            elif merge_type == 'toolbar':
                merge_toolbar = prompt("\nEnter the Merge ToolBar name\n(specifies the appearance of the floating toolbar reached via View > Toolbars > Add-on) : ").strip()
                separator_position = prompt_button_separator()
                items.append(generate_toolbar_item_fragment(item_name, title, url, image_path, target, separator_position))

        merge_fragment += stitching_xcu(addon_name, merge_type, merge_name, merge_point, merge_command, merge_fallback, items, merge_toolbar, merge_context)

        keep_merging = prompt("\n\n\nContinue to modify Menu or Toolbar? ( y or n ): ").strip().lower()
        if keep_merging == 'n':
            # Generate .xcu file
            generate_xcu(merge_fragment, output_dir, addon_menu_fragment, help_menu_fragment, images_fragment)
            break
    print("Thank you for using the Addons.xcu file generation. Have a Nice Day!")

if __name__ == "__main__":
    main()
<?xml version='1.0' encoding='utf-8'?>
<oor:oor:component-data
	xmlns:oor="http://openoffice.org/2001/registry" oor:package="org.openoffice.Office" oor:name="Addons"
	xmlns:xs="http://www.w3.org/2001/XMLSchema">
	<oor:node oor:name="AddonUI">
		<oor:node oor:name="OfficeMenuBar">
			<oor:node oor:name="org.libreoffice.TuesdayPrinter" oor:op="replace">
				<oor:node oor:name="menu1" oor:op="replace">
					<prop oor:name="MergePoint">
						<value>point</value>
					</prop>
					<prop oor:name="MergeCommand">
						<value>Replace</value>
					</prop>
					<prop oor:name="MergeFallback">
						<value>AdAfter</value>
					</prop>
					<prop oor:name="Context" oor:type="xs:string">
						<value>com.sun.star.sheet.SpreadsheetDocument</value>
					</prop>
					<oor:node oor:name="MenuItems" /></oor:node>
			</oor:node>
		</oor:node>
		<oor:node oor:name="org.libreoffice.TuesdayPrinter" oor:op="replace">
			<oor:node oor:name="menu1" oor:op="replace">
				<prop oor:name="MergePoint">
					<value>point</value>
				</prop>
				<prop oor:name="MergeCommand">
					<value>Replace</value>
				</prop>
				<prop oor:name="MergeFallback">
					<value>AdAfter</value>
				</prop>
				<prop oor:name="Context" oor:type="xs:string">
					<value>com.sun.star.sheet.SpreadsheetDocument</value>
				</prop>
				<oor:node oor:name="MenuItems" /></oor:node>
		</oor:node>
		<oor:node oor:name="menu1" oor:op="replace">
			<prop oor:name="MergePoint">
				<value>point</value>
			</prop>
			<prop oor:name="MergeCommand">
				<value>Replace</value>
			</prop>
			<prop oor:name="MergeFallback">
				<value>AdAfter</value>
			</prop>
			<prop oor:name="Context" oor:type="xs:string">
				<value>com.sun.star.sheet.SpreadsheetDocument</value>
			</prop>
			<oor:node oor:name="MenuItems" /></oor:node>
		<oor:node oor:name="MenuItems" /></oor:node>
</oor:oor:component-data>

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment