mindmap.mindmap module

class mindmap.mindmap.MindmapDocument(charttype: str = 'auto', turbo_mode: bool = False, inline_editing_mode: bool = False, mermaid_mode: bool = True, macos_access: str = 'appscript')

Bases: object

__init__(charttype: str = 'auto', turbo_mode: bool = False, inline_editing_mode: bool = False, mermaid_mode: bool = True, macos_access: str = 'appscript')

Initialize a MindmapDocument instance which automates MindManager operations.

Parameters:
  • charttype (str) – The type of chart to be used (orgchart, radial, auto).

  • turbo_mode (bool) – Flag for enabling turbo mode -> use only text.

  • inline_editing_mode (bool) – Flag for enabling inline editing mode.

  • mermaid_mode (bool) – Flag for enabling mermaid mode.

  • macos_access (str) – Method for accessing macOS features (default is ‘appscript’, alternative is ‘applescript’).

check_parent_exists(topic_guid, this_guid, visited=None)

Recursively check if a parent-child relationship exists between topics.

Parameters:
  • topic_guid (str) – The GUID of the topic to check.

  • this_guid (str) – The GUID that might be a parent of the topic.

  • visited (set, optional) – Set of visited GUIDs to avoid infinite recursion.

Returns:

True if the parent-child relationship exists, False otherwise.

Return type:

bool

clone_mindmap_topic(mindmap_topic, subtopics: list[MindmapTopic] = None, parent=None)

Clone a MindmapTopic instance including its subtopics.

Parameters:
  • mindmap_topic (MindmapTopic) – The topic to clone.

  • subtopics (list[MindmapTopic], optional) – A list of subtopics to clone.

  • parent – The parent for the cloned topic.

Returns:

A new instance that is a clone of the given topic.

Return type:

MindmapTopic

count_parent_and_child_occurrences(mindmap_topic, guid_counts, visited=None)

Recursively count the occurrences of parent and child relationships for each topic.

Parameters:
  • mindmap_topic (MindmapTopic) – The current topic in the mindmap.

  • guid_counts (dict) – Dictionary to store counts with topic GUID as keys.

  • visited (set) – Set of visited topic GUIDs to avoid infinite recursion.

create_mindmap(verbose=False)

Create a MindManager mindmap document from the internal MindmapTopic structure. This includes counting occurrences, extracting tags/icons, and setting up relationships and links.

Parameters:

verbose (bool) – (Optional) Enable verbose output.

create_mindmap_and_finalize()

Create the mindmap document and finalize it.

finalize()

Finalize the mindmap document by ensuring the maximum topic level is set, then calling MindManager’s finalize.

get_grounding_information()

Extract grounding information from the mindmap, including the central topic and selected subtopics.

Returns:

(top_most_topic, subtopics) where top_most_topic is the central topic or a concatenated string

of non-selected topics, and subtopics is a comma-separated string of selected subtopics.

Return type:

tuple

get_library_folder()

Get the library folder used by MindManager.

Returns:

The path to the library folder.

get_map_icons_and_fix_refs_from_mindmap(mindmap, map_icons: list[MindmapIcon], visited=None)

Extract icons from mindmap topics and fix their references if needed.

Parameters:
  • mindmap (MindmapTopic) – The current topic in the mindmap.

  • map_icons (list[MindmapIcon]) – List to collect unique icons.

  • visited (set) – Set of visited topic GUIDs to avoid infinite recursion.

get_max_topic_level(mindmap_topic, max_topic_level=0, visited=None)

Recursively compute the maximum topic level within the mind map.

Parameters:
  • mindmap_topic (MindmapTopic) – The current topic to evaluate.

  • max_topic_level (int) – The current maximum level found.

  • visited (set) – Set of visited topic GUIDs to avoid infinite recursion.

Returns:

The highest topic level found in the mindmap.

Return type:

int

get_mindmap(topic=None, mode='full')

Retrieve the mind map structure from the currently open MindManager document.

Parameters:
  • topic – (Optional) A specific topic from which to start building the mindmap. If not provided, the central topic is used.

  • mode (str) – The mode to use to gather attributes (full=all attributes, content=text+rtf+notes, text=text only).

Returns:

True if the mind map was successfully retrieved, otherwise False.

Return type:

bool

get_mindmap_topic_from_topic(topic, parent_topic=None, mode='full')

Recursively convert a MindManager topic into a MindmapTopic object.

Parameters:
  • topic – The current MindManager topic to convert.

  • parent_topic (MindmapTopic) – The parent MindmapTopic, if any.

  • mode (str) – The mode to use to gather attributes (full=all attributes, content=text+rtf+notes, text=text only).

Returns:

The converted topic with its subtopics.

Return type:

MindmapTopic

get_parent_topic(topic, visited=None)

Retrieve the parent topic for a given MindManager topic.

Parameters:
  • topic – The current topic from which to get the parent.

  • visited (set, optional) – Track visited GUIDs to avoid cycles.

Returns:

The parent topic wrapped as a MindmapTopic, or None if at the root.

Return type:

MindmapTopic or None

get_parents_from_mindmap(mindmap, parents, visited=None)

Build a dictionary mapping subtopic GUIDs to their parent’s GUID.

Parameters:
  • mindmap (MindmapTopic) – The current topic in the mindmap.

  • parents (dict) – Dictionary to store parent-child GUID mappings.

  • visited (set) – Set of visited topic GUIDs to avoid infinite recursion.

get_relationships_from_mindmap(mindmap, references, visited=None)

Recursively extract relationships (references) from the mindmap.

Parameters:
  • mindmap (MindmapTopic) – The current topic in the mindmap.

  • references (list[MindmapReference]) – List to collect the relationships.

  • visited (set) – Set of visited topic GUIDs to avoid infinite recursion.

get_selection()

Retrieve the currently selected topics in the MindManager document.

Returns:

A list of MindmapTopic instances representing the selection.

Return type:

list[MindmapTopic]

get_tags_from_mindmap(mindmap, tags, visited=None)

Recursively collect unique tags from the mindmap.

Parameters:
  • mindmap (MindmapTopic) – The current topic in the mindmap.

  • tags (list[str]) – List to collect tag texts.

  • visited (set) – Set of visited topic GUIDs to avoid infinite recursion.

Recursively extract topic links from the mindmap.

Parameters:
  • mindmap (MindmapTopic) – The current topic in the mindmap.

  • links (list[MindmapReference]) – List to collect topic links as MindmapReference objects.

  • visited (set) – Set of visited topic GUIDs to avoid infinite recursion.

get_topic_texts_from_selection(mindmap_topics)

Extract topic texts, levels, and GUIDs from selected topics.

Parameters:

mindmap_topics (list[MindmapTopic]) – List of topics to process.

set_background_image(image_path)

Set the background image for the MindManager document.

Parameters:

image_path (str) – The file path to the background image.

set_topic_from_mindmap_topic(topic, mindmap_topic, map_icons, done=None, done_global=None, level=0)

Create or update a MindManager topic from a MindmapTopic instance recursively.

Parameters:
  • topic – The current MindManager topic to update.

  • mindmap_topic (MindmapTopic) – The source MindmapTopic data.

  • map_icons (list[MindmapIcon]) – List of map icons to use.

  • done (dict, optional) – Dictionary tracking processed topics at current level.

  • done_global (dict, optional) – Global dictionary for tracking duplicate processing.

  • level (int) – Current hierarchical level.

Returns:

The processed MindmapTopic.

Return type:

MindmapTopic

update_done(topic_guid, mindmap_topic, level, done, done_global)

Update tracking dictionaries for processed topics and create duplicate links/tags.

Parameters:
  • topic_guid (str) – The GUID of the current topic in MindManager.

  • mindmap_topic (MindmapTopic) – The MindmapTopic being processed.

  • level (int) – The current level in the topic hierarchy.

  • done (dict) – Dictionary tracking topics processed at a given level.

  • done_global (dict) – Global dictionary tracking processed topics for duplicate detection.

class mindmap.mindmap.MindmapIcon(text: str = '', is_stock_icon=True, index: int = 1, signature: str = '', path: str = '', group: str = '')

Bases: object

__init__(text: str = '', is_stock_icon=True, index: int = 1, signature: str = '', path: str = '', group: str = '')

Initialize a MindmapIcon instance.

Parameters:
  • text (str) – The display text for the icon.

  • is_stock_icon (bool) – Flag indicating if the icon is a stock icon.

  • index (int) – The index of a stock icon.

  • signature (str) – A unique signature for the icon.

  • path (str) – File path to the icon if it is custom.

  • group (str) – The group/category of the icon.

class mindmap.mindmap.MindmapImage(text: str = '')

Bases: object

__init__(text: str = '')

Initialize a MindmapImage instance.

Parameters:

text (str) – Path to the image.

Bases: object

__init__(text: str = '', url: str = '', guid: str = '')

Initialize a MindmapLink instance.

Parameters:
  • text (str) – The display text for the link.

  • url (str) – The URL that the link points to.

  • guid (str) – A unique identifier for the link.

class mindmap.mindmap.MindmapNotes(text: str = '', xhtml: str = '', rtf: str = '')

Bases: object

__init__(text: str = '', xhtml: str = '', rtf: str = '')

Initialize a MindmapNotes instance.

Parameters:
  • text (str) – Plain text version of the notes.

  • xhtml (str) – XHTML formatted notes.

  • rtf (str) – RTF formatted notes.

class mindmap.mindmap.MindmapReference(guid_1: str = '', guid_2: str = '', direction: int = 1, label: str = '')

Bases: object

__init__(guid_1: str = '', guid_2: str = '', direction: int = 1, label: str = '')

Initialize a MindmapReference (relationship) instance.

Parameters:
  • guid_1 (str) – The GUID of the first topic.

  • guid_2 (str) – The GUID of the second topic.

  • direction (int) – The direction of the reference (1 indicates a standard direction).

  • label (str) – A label for the relationship.

class mindmap.mindmap.MindmapTag(text: str = '')

Bases: object

__init__(text: str = '')

Initialize a MindmapTag instance.

Parameters:

text (str) – The text representing the tag.

class mindmap.mindmap.MindmapTopic(guid: str = '', text: str = '', rtf: str = '', level: int = 0, selected: bool = False, parent: MindmapTopic = None, subtopics: list[MindmapTopic] = None, links: list[MindmapLink] = None, image: MindmapImage = None, icons: list[MindmapIcon] = None, notes: MindmapNotes = None, tags: list[MindmapTag] = None, references: list[MindmapReference] = None)

Bases: object

__init__(guid: str = '', text: str = '', rtf: str = '', level: int = 0, selected: bool = False, parent: MindmapTopic = None, subtopics: list[MindmapTopic] = None, links: list[MindmapLink] = None, image: MindmapImage = None, icons: list[MindmapIcon] = None, notes: MindmapNotes = None, tags: list[MindmapTag] = None, references: list[MindmapReference] = None)

Initialize a MindmapTopic instance.

Parameters:
  • guid (str) – Unique identifier for the topic.

  • text (str) – The text content of the topic.

  • rtf (str) – RTF formatted text for the topic.

  • level (int) – The hierarchical level of the topic.

  • selected (bool) – Flag to indicate if the topic is selected.

  • parent (MindmapTopic) – The parent topic if any.

  • subtopics (list[MindmapTopic]) – List of subtopics.

  • links (list[MindmapLink]) – List of associated links.

  • image (MindmapImage) – Associated image object.

  • icons (list[MindmapIcon]) – List of associated icons.

  • notes (MindmapNotes) – Associated notes.

  • tags (list[MindmapTag]) – List of associated tags.

  • references (list[MindmapReference]) – List of associated relationships.

Source code for mindmap.py
  1import uuid
  2
  3import mindm.mindmanager as mm
  4
  5DUPLICATED_TAG = 'Duplicated'
  6DUPLICATE_LABEL = 'DUPLICATE'
  7
  8class MindmapLink:
  9    def __init__(self, text: str = '', url: str = '', guid: str = ''):
 10        """
 11        Initialize a MindmapLink instance.
 12
 13        Args:
 14            text (str): The display text for the link.
 15            url (str): The URL that the link points to.
 16            guid (str): A unique identifier for the link.
 17        """
 18        self.text = text
 19        self.url = url
 20        self.guid = guid
 21
 22class MindmapImage:
 23    def __init__(self, text: str = ''):
 24        """
 25        Initialize a MindmapImage instance.
 26
 27        Args:
 28            text (str): Path to the image.
 29        """
 30        self.text = text
 31
 32class MindmapNotes:
 33    def __init__(self, text: str = '', xhtml: str = '', rtf: str = ''):
 34        """
 35        Initialize a MindmapNotes instance.
 36
 37        Args:
 38            text (str): Plain text version of the notes.
 39            xhtml (str): XHTML formatted notes.
 40            rtf (str): RTF formatted notes.
 41        """
 42        self.text = text
 43        self.xhtml = xhtml
 44        self.rtf = rtf
 45
 46class MindmapIcon:
 47    def __init__(self, 
 48                 text: str = '', 
 49                 is_stock_icon=True, 
 50                 index: int = 1, 
 51                 signature: str = '', 
 52                 path: str = '',
 53                 group: str = ''):
 54        """
 55        Initialize a MindmapIcon instance.
 56
 57        Args:
 58            text (str): The display text for the icon.
 59            is_stock_icon (bool): Flag indicating if the icon is a stock icon.
 60            index (int): The index of a stock icon.
 61            signature (str): A unique signature for the icon.
 62            path (str): File path to the icon if it is custom.
 63            group (str): The group/category of the icon.
 64        """
 65        self.text = text
 66        self.is_stock_icon = is_stock_icon
 67        self.index = index
 68        self.signature = signature
 69        self.path = path
 70        self.group = group
 71
 72class MindmapTag:
 73    def __init__(self, text: str = ''):
 74        """
 75        Initialize a MindmapTag instance.
 76
 77        Args:
 78            text (str): The text representing the tag.
 79        """
 80        self.text = text
 81
 82class MindmapReference:
 83    def __init__(self, 
 84                 guid_1: str = '', 
 85                 guid_2: str = '', 
 86                 direction: int = 1, 
 87                 label: str = ''):
 88        """
 89        Initialize a MindmapReference (relationship) instance.
 90
 91        Args:
 92            guid_1 (str): The GUID of the first topic.
 93            guid_2 (str): The GUID of the second topic.
 94            direction (int): The direction of the reference (1 indicates a standard direction).
 95            label (str): A label for the relationship.
 96        """
 97        self.guid_1 = guid_1
 98        self.guid_2 = guid_2
 99        self.direction = direction
100        self.label = label
101
102class MindmapTopic:
103    def __init__(self,
104                 guid: str = '',
105                 text: str = '',
106                 rtf: str = '',
107                 level: int = 0,
108                 selected: bool = False,
109                 parent: 'MindmapTopic' = None,
110                 subtopics: list['MindmapTopic'] = None,
111                 links: list['MindmapLink'] = None,
112                 image: 'MindmapImage' = None,
113                 icons: list['MindmapIcon'] = None,
114                 notes: 'MindmapNotes' = None,
115                 tags: list['MindmapTag'] = None,
116                 references: list['MindmapReference'] = None):
117        """
118        Initialize a MindmapTopic instance.
119
120        Args:
121            guid (str): Unique identifier for the topic.
122            text (str): The text content of the topic.
123            rtf (str): RTF formatted text for the topic.
124            level (int): The hierarchical level of the topic.
125            selected (bool): Flag to indicate if the topic is selected.
126            parent (MindmapTopic): The parent topic if any.
127            subtopics (list[MindmapTopic]): List of subtopics.
128            links (list[MindmapLink]): List of associated links.
129            image (MindmapImage): Associated image object.
130            icons (list[MindmapIcon]): List of associated icons.
131            notes (MindmapNotes): Associated notes.
132            tags (list[MindmapTag]): List of associated tags.
133            references (list[MindmapReference]): List of associated relationships.
134        """
135        self.guid = guid
136        self.text = text.replace('"', '`').replace("'", "`").replace("\r", "").replace("\n", "")
137        self.rtf = rtf
138        self.level = level
139        self.selected = selected
140        self.parent = parent
141        self.links = links if links is not None else []
142        self.image = image
143        self.icons = icons if icons is not None else []
144        self.notes = notes
145        self.tags = tags if tags is not None else []
146        self.references = references if references is not None else []
147        self.subtopics = subtopics if subtopics is not None else []
148
149
150class MindmapDocument:
151    def __init__(self, charttype: str = 'auto', turbo_mode: bool = False, inline_editing_mode: bool = False, mermaid_mode: bool = True, macos_access: str = 'appscript'):
152        """
153        Initialize a MindmapDocument instance which automates MindManager operations.
154
155        Args:
156            charttype (str): The type of chart to be used (orgchart, radial, auto).
157            turbo_mode (bool): Flag for enabling turbo mode -> use only text.
158            inline_editing_mode (bool): Flag for enabling inline editing mode.
159            mermaid_mode (bool): Flag for enabling mermaid mode.
160            macos_access (str): Method for accessing macOS features (default is 'appscript', alternative is 'applescript').
161        """
162        self.charttype: str = charttype
163        self.turbo_mode: bool = turbo_mode
164        self.inline_editing_mode: bool = inline_editing_mode
165        self.mermaid_mode: bool = mermaid_mode
166        self.macos_access: str = macos_access
167        self.mindmap: 'MindmapTopic' = None
168        self.central_topic_selected: bool = False
169        self.selected_topic_texts: list[str] = []
170        self.selected_topic_levels: list[int] = []
171        self.selected_topic_ids: list[str] = []
172        self.max_topic_level: int = 0
173        self.macos_access = macos_access
174        self.mindm = mm.Mindmanager(charttype, macos_access)
175
176    def get_mindmap(self, topic=None, mode='full'):
177        """
178        Retrieve the mind map structure from the currently open MindManager document.
179
180        Args:
181            topic: (Optional) A specific topic from which to start building the mindmap.
182                   If not provided, the central topic is used.
183            mode (str): The mode to use to gather attributes (full=all attributes, content=text+rtf+notes, text=text only).
184
185        Returns:
186            bool: True if the mind map was successfully retrieved, otherwise False.
187        """
188        if self.macos_access == 'applescript' and self.mindm.platform == 'darwin':
189            # get whole mindmap
190            mindmap = self.mindm.get_central_topic()
191        else:
192            if topic is None:
193                topic = self.mindm.get_central_topic()
194            
195            mindmap = self.get_mindmap_topic_from_topic(self.mindm.get_topic_by_id(topic.guid), mode=mode)
196
197        self.max_topic_level = self.get_max_topic_level(mindmap)
198        self.mindmap = mindmap
199        return True
200    
201    def get_max_topic_level(self, mindmap_topic, max_topic_level=0, visited=None):
202        """
203        Recursively compute the maximum topic level within the mind map.
204
205        Args:
206            mindmap_topic (MindmapTopic): The current topic to evaluate.
207            max_topic_level (int): The current maximum level found.
208            visited (set): Set of visited topic GUIDs to avoid infinite recursion.
209
210        Returns:
211            int: The highest topic level found in the mindmap.
212        """
213        if visited is None:
214            visited = set()
215        if mindmap_topic.guid in visited:
216            return max_topic_level
217        visited.add(mindmap_topic.guid)
218        for subtopic in mindmap_topic.subtopics:
219            if subtopic.level > max_topic_level:
220                max_topic_level = subtopic.level
221            max_topic_level = self.get_max_topic_level(subtopic, max_topic_level, visited)
222        return max_topic_level
223
224    def get_parent_topic(self, topic, visited=None):
225        """
226        Retrieve the parent topic for a given MindManager topic.
227
228        Args:
229            topic: The current topic from which to get the parent.
230            visited (set, optional): Track visited GUIDs to avoid cycles.
231
232        Returns:
233            MindmapTopic or None: The parent topic wrapped as a MindmapTopic, or None if at the root.
234        """
235        if visited is None:
236            visited = set()
237        topic_guid = self.mindm.get_guid_from_topic(topic)
238        if topic_guid in visited:
239            return None
240        visited.add(topic_guid)
241        topic_level = self.mindm.get_level_from_topic(topic)
242        if topic_level == 0:
243            return None
244        parent_topic = self.mindm.get_parent_from_topic(topic)
245        if parent_topic is None:
246            return None
247        parent_guid = self.mindm.get_guid_from_topic(parent_topic)
248        if parent_guid == topic_guid:
249            return None
250        parent_mindmap_topic = MindmapTopic(
251            guid=parent_guid,
252            text=self.mindm.get_text_from_topic(parent_topic), 
253            level=self.mindm.get_level_from_topic(parent_topic),
254            parent=self.get_parent_topic(parent_topic, visited),
255        )
256        return parent_mindmap_topic
257
258    def get_selection(self):
259        """
260        Retrieve the currently selected topics in the MindManager document.
261
262        Returns:
263            list[MindmapTopic]: A list of MindmapTopic instances representing the selection.
264        """
265        selection = self.mindm.get_selection()
266        mindmap_topics = []
267        for topic in selection:
268            level = self.mindm.get_level_from_topic(topic)
269            mindmap_topic = MindmapTopic(
270                guid=self.mindm.get_guid_from_topic(topic),
271                text=self.mindm.get_text_from_topic(topic), 
272                level=level,
273                parent=self.get_parent_topic(topic),
274                selected=True,
275            )
276            mindmap_topics.append(mindmap_topic)
277        self.get_topic_texts_from_selection(mindmap_topics)
278        return mindmap_topics
279
280    def get_mindmap_topic_from_topic(self, topic, parent_topic=None, mode='full'):
281        """
282        Recursively convert a MindManager topic into a MindmapTopic object.
283
284        Args:
285            topic: The current MindManager topic to convert.
286            parent_topic (MindmapTopic): The parent MindmapTopic, if any.
287            mode (str): The mode to use to gather attributes (full=all attributes, content=text+rtf+notes, text=text only).
288
289        Returns:
290            MindmapTopic: The converted topic with its subtopics.
291        """
292        if mode == 'full':
293            mindmap_topic = self.mindm.get_mindmaptopic_from_topic_full(topic)
294            mindmap_topic.parent = parent_topic
295        elif mode == 'content':
296            mindmap_topic = self.mindm.get_mindmaptopic_from_topic_content(topic)
297        else:
298            mindmap_topic = self.mindm.get_mindmaptopic_from_topic(topic)
299
300        subtopics = self.mindm.get_subtopics_from_topic(topic)
301        mindmap_subtopics = []
302        for subtopic in subtopics:
303            child = self.get_mindmap_topic_from_topic(subtopic, parent_topic=mindmap_topic, mode=mode)
304            mindmap_subtopics.append(child)
305        mindmap_topic.subtopics = mindmap_subtopics
306        return mindmap_topic 
307
308    def get_relationships_from_mindmap(self, mindmap, references, visited=None):
309        """
310        Recursively extract relationships (references) from the mindmap.
311
312        Args:
313            mindmap (MindmapTopic): The current topic in the mindmap.
314            references (list[MindmapReference]): List to collect the relationships.
315            visited (set): Set of visited topic GUIDs to avoid infinite recursion.
316        """
317        if visited is None:
318            visited = set()
319        if mindmap.guid in visited:
320            return
321        visited.add(mindmap.guid)
322        for reference in mindmap.references:
323            if reference.direction == 1:
324                references.append(MindmapReference(
325                    guid_1=reference.guid_1,
326                    guid_2=reference.guid_2,
327                    direction=reference.direction,
328                    label=reference.label
329                ))
330        for subtopic in mindmap.subtopics:
331            self.get_relationships_from_mindmap(subtopic, references, visited)
332
333    def get_topic_links_from_mindmap(self, mindmap, links, visited=None):
334        """
335        Recursively extract topic links from the mindmap.
336
337        Args:
338            mindmap (MindmapTopic): The current topic in the mindmap.
339            links (list[MindmapReference]): List to collect topic links as MindmapReference objects.
340            visited (set): Set of visited topic GUIDs to avoid infinite recursion.
341        """
342        if visited is None:
343            visited = set()
344        if mindmap.guid in visited:
345            return
346        visited.add(mindmap.guid)
347        for link in mindmap.links:
348            if link.guid != '':
349                links.append(MindmapReference(
350                    guid_1=mindmap.guid, 
351                    guid_2=link.guid, 
352                    direction=1, 
353                    label=link.text
354                ))
355        for subtopic in mindmap.subtopics:
356            self.get_topic_links_from_mindmap(subtopic, links, visited)
357
358    def get_tags_from_mindmap(self, mindmap, tags, visited=None):
359        """
360        Recursively collect unique tags from the mindmap.
361
362        Args:
363            mindmap (MindmapTopic): The current topic in the mindmap.
364            tags (list[str]): List to collect tag texts.
365            visited (set): Set of visited topic GUIDs to avoid infinite recursion.
366        """
367        if visited is None:
368            visited = set()
369        if mindmap.guid in visited:
370            return
371        visited.add(mindmap.guid)
372        for tag in mindmap.tags:
373            if tag.text != '' and tag.text not in tags:
374                tags.append(tag.text)
375        for subtopic in mindmap.subtopics:
376            self.get_tags_from_mindmap(subtopic, tags, visited)
377
378    def get_parents_from_mindmap(self, mindmap, parents, visited=None):
379        """
380        Build a dictionary mapping subtopic GUIDs to their parent's GUID.
381
382        Args:
383            mindmap (MindmapTopic): The current topic in the mindmap.
384            parents (dict): Dictionary to store parent-child GUID mappings.
385            visited (set): Set of visited topic GUIDs to avoid infinite recursion.
386        """
387        if visited is None:
388            visited = set()
389        if mindmap.guid in visited:
390            return
391        visited.add(mindmap.guid)
392        for subtopic in mindmap.subtopics:
393            if subtopic.guid not in parents:
394                parents[subtopic.guid] = mindmap.guid
395                self.get_parents_from_mindmap(subtopic, parents, visited)
396        return
397
398    def get_map_icons_and_fix_refs_from_mindmap(self, mindmap, map_icons: list['MindmapIcon'], visited=None):
399        """
400        Extract icons from mindmap topics and fix their references if needed.
401
402        Args:
403            mindmap (MindmapTopic): The current topic in the mindmap.
404            map_icons (list[MindmapIcon]): List to collect unique icons.
405            visited (set): Set of visited topic GUIDs to avoid infinite recursion.
406        """
407        if visited is None:
408            visited = set()
409        if mindmap.guid in visited:
410            return
411        visited.add(mindmap.guid)
412        
413        for i, topic_icon_ref in enumerate(mindmap.icons):
414            # Only process non-stock icons belonging to the 'Types' group
415            if not topic_icon_ref.is_stock_icon and topic_icon_ref.group == 'Types':
416                found = False
417                for map_icon in map_icons:
418                    if map_icon.signature == topic_icon_ref.signature:
419                        found = True
420                        new_icon = map_icon
421                        break
422                if not found:
423                    new_icon = MindmapIcon(
424                        text=topic_icon_ref.text, 
425                        index=topic_icon_ref.index,
426                        is_stock_icon=topic_icon_ref.is_stock_icon, 
427                        path=topic_icon_ref.path,
428                        signature=topic_icon_ref.signature,
429                        group=topic_icon_ref.group)                
430                    map_icons.append(new_icon)
431                mindmap.icons[i] = new_icon
432        for subtopic in mindmap.subtopics:
433            self.get_map_icons_and_fix_refs_from_mindmap(subtopic, map_icons, visited)
434
435    def count_parent_and_child_occurrences(self, mindmap_topic, guid_counts, visited=None):
436        """
437        Recursively count the occurrences of parent and child relationships for each topic.
438
439        Args:
440            mindmap_topic (MindmapTopic): The current topic in the mindmap.
441            guid_counts (dict): Dictionary to store counts with topic GUID as keys.
442            visited (set): Set of visited topic GUIDs to avoid infinite recursion.
443        """
444        if visited is None:
445            visited = set()
446        if str(mindmap_topic.guid) == '':
447            mindmap_topic.guid = str(uuid.uuid4())
448        if mindmap_topic.guid not in visited:
449            visited.add(mindmap_topic.guid)
450            if mindmap_topic.guid not in guid_counts:
451                guid_counts[mindmap_topic.guid] = {'parent': 0, 'child': 0}
452            for subtopic in mindmap_topic.subtopics:
453                if mindmap_topic.guid:
454                    guid_counts[mindmap_topic.guid]['parent'] += 1
455                if subtopic.guid:
456                    if subtopic.guid not in guid_counts:
457                        guid_counts[subtopic.guid] = {'parent': 0, 'child': 0}
458                    guid_counts[subtopic.guid]['child'] += 1
459                self.count_parent_and_child_occurrences(subtopic, guid_counts, visited)
460
461    def get_topic_texts_from_selection(self, mindmap_topics):
462        """
463        Extract topic texts, levels, and GUIDs from selected topics.
464
465        Args:
466            mindmap_topics (list[MindmapTopic]): List of topics to process.
467        """
468        topic_texts = []
469        topic_levels = []
470        topic_ids = []
471        central_topic_selected = False
472        for topic in mindmap_topics:
473            if topic.selected:
474                if topic.level > 0:
475                    topic_texts.append(topic.text)
476                    topic_levels.append(topic.level)
477                    topic_ids.append(topic.guid)
478                else:
479                    central_topic_selected = True
480
481        self.central_topic_selected = central_topic_selected
482        self.selected_topic_texts = topic_texts
483        self.selected_topic_levels = topic_levels
484        self.selected_topic_ids = topic_ids
485            
486    def clone_mindmap_topic(self, mindmap_topic, subtopics: list['MindmapTopic'] = None, parent=None):
487        """
488        Clone a MindmapTopic instance including its subtopics.
489
490        Args:
491            mindmap_topic (MindmapTopic): The topic to clone.
492            subtopics (list[MindmapTopic], optional): A list of subtopics to clone.
493            parent: The parent for the cloned topic.
494
495        Returns:
496            MindmapTopic: A new instance that is a clone of the given topic.
497        """
498        cloned_subtopics = []
499        if subtopics is not None:
500            for subtopic in subtopics:
501                cloned_subtopic = self.clone_mindmap_topic(subtopic)
502                cloned_subtopics.append(cloned_subtopic)
503        return MindmapTopic(
504            guid=mindmap_topic.guid,
505            text=mindmap_topic.text, 
506            rtf=mindmap_topic.rtf,
507            level=mindmap_topic.level,
508            parent=parent,
509            links=mindmap_topic.links,
510            image=mindmap_topic.image,
511            icons=mindmap_topic.icons,
512            notes=mindmap_topic.notes,
513            tags=mindmap_topic.tags,
514            subtopics=cloned_subtopics
515        )
516
517    def update_done(self, topic_guid, mindmap_topic, level, done, done_global):
518        """
519        Update tracking dictionaries for processed topics and create duplicate links/tags.
520
521        Args:
522            topic_guid (str): The GUID of the current topic in MindManager.
523            mindmap_topic (MindmapTopic): The MindmapTopic being processed.
524            level (int): The current level in the topic hierarchy.
525            done (dict): Dictionary tracking topics processed at a given level.
526            done_global (dict): Global dictionary tracking processed topics for duplicate detection.
527        """
528        if mindmap_topic.guid == '':
529            return
530        if level <= 1:
531            done = {}
532        elif level >= 2: 
533            done[mindmap_topic.guid] = [topic_guid] if mindmap_topic.guid not in done else done[mindmap_topic.guid] + [topic_guid]
534        if mindmap_topic.guid in done_global:
535            # Check for duplicate relationships and add links/tags accordingly.
536            if self.guid_counts[mindmap_topic.guid]['child'] < 11 and self.guid_counts[mindmap_topic.guid]['parent'] >= 0:
537                for i in range(len(done_global[mindmap_topic.guid])):
538                    link_from = topic_guid
539                    link_to = done_global[mindmap_topic.guid][i]
540                    self.mindm.add_topic_link(link_from, link_to, DUPLICATE_LABEL)
541                    self.mindm.add_topic_link(link_to, link_from, DUPLICATE_LABEL)
542                if len(done_global[mindmap_topic.guid]) == 1:
543                    self.mindm.add_tag_to_topic(topic=None, tag_text=DUPLICATED_TAG, topic_guid=done_global[mindmap_topic.guid][0])
544            self.mindm.add_tag_to_topic(topic=None, tag_text=DUPLICATED_TAG, topic_guid=topic_guid)
545            done_global[mindmap_topic.guid] = done_global[mindmap_topic.guid] + [topic_guid]
546        else:
547            done_global[mindmap_topic.guid] = [topic_guid]
548
549    def set_topic_from_mindmap_topic(self, topic, mindmap_topic, map_icons, done=None, done_global=None, level=0):
550        """
551        Create or update a MindManager topic from a MindmapTopic instance recursively.
552
553        Args:
554            topic: The current MindManager topic to update.
555            mindmap_topic (MindmapTopic): The source MindmapTopic data.
556            map_icons (list[MindmapIcon]): List of map icons to use.
557            done (dict, optional): Dictionary tracking processed topics at current level.
558            done_global (dict, optional): Global dictionary for tracking duplicate processing.
559            level (int): Current hierarchical level.
560            
561        Returns:
562            MindmapTopic: The processed MindmapTopic.
563        """
564        if done is None:
565            done = {}
566        if done_global is None:
567            done_global = {}
568        try:
569            if self.turbo_mode:
570                topic_guid = self.mindm.get_guid_from_topic(topic)
571                self.update_done(topic_guid, mindmap_topic, level, done, done_global)
572                for subtopic in mindmap_topic.subtopics:
573                    try:
574                        sub = self.mindm.add_subtopic_to_topic(topic, subtopic.text)
575                        self.set_topic_from_mindmap_topic(sub, subtopic, map_icons, done, done_global, level + 1)
576                    except Exception as e:
577                        print(f"Error(1) processing topic/subtopic {mindmap_topic.guid}/{subtopic.guid}: {e}")
578            else:
579                topic, topic_guid = self.mindm.set_topic_from_mindmap_topic(topic, mindmap_topic, map_icons)
580                self.update_done(topic_guid, mindmap_topic, level, done, done_global)
581
582                if mindmap_topic.subtopics and len(mindmap_topic.subtopics) > 0:
583                    # Sort subtopics alphabetically by text
584                    mindmap_topic.subtopics.sort(key=lambda sub: sub.text)
585
586                for subtopic in mindmap_topic.subtopics:
587                    try:
588                        if subtopic.guid in done:
589                            this_guid_as_parent_exists = self.check_parent_exists(topic_guid, subtopic.guid)
590                            if not this_guid_as_parent_exists:
591                                cloned_subtopic = self.clone_mindmap_topic(subtopic)
592                                sub = self.mindm.add_subtopic_to_topic(topic, cloned_subtopic.text)
593                                self.set_topic_from_mindmap_topic(sub, cloned_subtopic, map_icons, done, done_global, level + 1)
594                        else:
595                            sub = self.mindm.add_subtopic_to_topic(topic, subtopic.text)
596                            self.set_topic_from_mindmap_topic(sub, subtopic, map_icons, done, done_global, level + 1)
597                    except Exception as e:
598                        print(f"Error(2) processing topic/subtopic {mindmap_topic.guid}/{subtopic.guid}: {e}")
599            return mindmap_topic
600        except Exception as e:
601            print(f"Error in set_topic_from_mindmap_topic at level {level} with topic {mindmap_topic.guid}: {e}")
602
603    def check_parent_exists(self, topic_guid, this_guid, visited=None):
604        """
605        Recursively check if a parent-child relationship exists between topics.
606
607        Args:
608            topic_guid (str): The GUID of the topic to check.
609            this_guid (str): The GUID that might be a parent of the topic.
610            visited (set, optional): Set of visited GUIDs to avoid infinite recursion.
611
612        Returns:
613            bool: True if the parent-child relationship exists, False otherwise.
614        """
615        if visited is None:
616            visited = set()
617        if topic_guid in visited:
618            return False
619        visited.add(topic_guid)
620        
621        check = False
622        if topic_guid in self.parents:
623            parent_guid = self.parents[topic_guid]
624            if parent_guid == this_guid:
625                check = True
626            else:
627                check = self.check_parent_exists(parent_guid, this_guid, visited)
628        return check
629
630    def create_mindmap(self, verbose=False):
631        """
632        Create a MindManager mindmap document from the internal MindmapTopic structure.
633        This includes counting occurrences, extracting tags/icons, and setting up relationships and links.
634
635        Args:
636            verbose (bool): (Optional) Enable verbose output.
637        """
638        tags = []
639        map_icons = []
640        relationships = []
641        links = []
642
643        self.parents = {}
644        self.guid_counts = {}
645        self.count_parent_and_child_occurrences(self.mindmap, self.guid_counts)
646        self.get_parents_from_mindmap(self.mindmap, self.parents)
647
648        self.get_tags_from_mindmap(self.mindmap, tags)
649        self.get_map_icons_and_fix_refs_from_mindmap(self.mindmap, map_icons)
650        self.get_relationships_from_mindmap(self.mindmap, relationships)
651        self.get_topic_links_from_mindmap(self.mindmap, links)
652
653        self.mindm.add_document(0)
654        self.mindm.create_map_icons(map_icons)
655        self.mindm.create_tags(tags, DUPLICATED_TAG)
656
657        if self.mindm.platform == 'darwin' and self.macos_access == 'applescript':
658            self.mindm.set_topic_from_mindmap_topic(None, self.mindmap, map_icons)
659            self.get_mindmap()
660        else:
661            central_topic = self.mindm.get_central_topic()
662            self.mindm.set_text_to_topic(self.mindm.get_topic_by_id(central_topic.guid), self.mindmap.text)
663
664            done_global = {}
665            self.set_topic_from_mindmap_topic(
666                topic=self.mindm.get_topic_by_id(central_topic.guid),
667                mindmap_topic=self.mindmap,
668                map_icons=map_icons,
669                done={},
670                done_global=done_global)
671
672            # Create relationships between topics
673            for reference in relationships:
674                object1_guids = done_global[reference.guid_1]
675                object2_guids = done_global[reference.guid_2]
676                for object1_guid in object1_guids:
677                    for object2_guid in object2_guids:
678                        self.mindm.add_relationship(object1_guid, object2_guid, reference.label)
679
680            # Create topic links
681            for link in links:
682                object1_guids = done_global[link.guid_1]
683                object2_guids = done_global[link.guid_2]
684                for object1_guid in object1_guids:
685                    for object2_guid in object2_guids:
686                        self.mindm.add_topic_link(object1_guid, object2_guid, link.label)
687
688    def create_mindmap_and_finalize(self):
689        """
690        Create the mindmap document and finalize it.
691        """
692        self.create_mindmap()
693        self.finalize()
694
695    def finalize(self):
696        """
697        Finalize the mindmap document by ensuring the maximum topic level is set, then calling MindManager's finalize.
698        """
699        if self.max_topic_level == 0:
700            self.max_topic_level = self.get_max_topic_level(self.mindmap)
701        self.mindm.finalize(self.max_topic_level)
702    
703    def set_background_image(self, image_path):
704        """
705        Set the background image for the MindManager document.
706
707        Args:
708            image_path (str): The file path to the background image.
709        """
710        self.mindm.set_document_background_image(image_path)
711    
712    def get_library_folder(self):
713        """
714        Get the library folder used by MindManager.
715
716        Returns:
717            The path to the library folder.
718        """
719        return self.mindm.get_library_folder()
720    
721    def get_grounding_information(self):
722        """
723        Extract grounding information from the mindmap, including the central topic and selected subtopics.
724
725        Returns:
726            tuple: (top_most_topic, subtopics) where top_most_topic is the central topic or a concatenated string
727                   of non-selected topics, and subtopics is a comma-separated string of selected subtopics.
728        """
729        central_topic_text = self.mindmap.text
730        self.get_selection()
731        subtopics = ""
732        if len(self.selected_topic_texts) == 0: 
733            top_most_topic = central_topic_text
734        else:
735            if self.central_topic_selected:
736                top_most_topic = central_topic_text
737                subtopics =  ",".join(self.selected_topic_texts)
738            else:
739                min_level = min(self.selected_topic_levels)
740                max_level = max(self.selected_topic_levels)
741                if (min_level == max_level):
742                    top_most_topic = central_topic_text
743                    subtopics =  ",".join(self.selected_topic_texts)
744                else:
745                    top_most_topic = ""
746                    for i in range(len(self.selected_topic_levels)):
747                        if self.selected_topic_levels[i] != max_level:
748                            top_most_topic += self.selected_topic_texts[i] + "/"
749                        else:
750                            subtopics += self.selected_topic_texts[i] + ","
751
752                    if top_most_topic.endswith("/"):
753                        top_most_topic = top_most_topic[:-1]
754                    if subtopics.endswith(","):
755                        subtopics = subtopics[:-1]        
756        return top_most_topic, subtopics