diff --git a/.settings/org.eclipse.core.resources.prefs b/.settings/org.eclipse.core.resources.prefs new file mode 100644 index 0000000..b985da1 --- /dev/null +++ b/.settings/org.eclipse.core.resources.prefs @@ -0,0 +1,16 @@ +eclipse.preferences.version=1 +encoding//xmind/__init__.py=utf-8 +encoding//xmind/core/__init__.py=utf-8 +encoding//xmind/core/const.py=utf-8 +encoding//xmind/core/loader.py=utf-8 +encoding//xmind/core/notes.py=utf-8 +encoding//xmind/core/workbook.py=utf-8 +encoding//xmind/framework/Enum.py=utf-8 +encoding//xmind/framework/UnitTest.py=utf-8 +encoding//xmind/framework/__init__.py=utf-8 +encoding//xmind/import_export/ExportFilter.py=utf-8 +encoding//xmind/import_export/GraphvizExportFilter.py=utf-8 +encoding//xmind/import_export/__init__.py=utf-8 +encoding//xmind/utils.py=utf-8 +encoding/example.py=utf-8 +encoding/setup.py=utf-8 diff --git a/README.md b/README.md index 029b622..7d236d7 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -#XMind SDK for python +#Fork of XMind SDK for python with extensions for import / export (e.g. Graphviz) **XMind SDK for python** to help Python developers to easily work with XMind files and build XMind extensions. @@ -6,7 +6,7 @@ Clone the repository to a local working directory - git clone https://github.com/xmindltd/xmind-sdk-python.git + git clone https://github.com/Almerxsese/xmind-sdk-python Now there will be a directory named `xmind-sdk-python` under the current directory. Change to the directory `xmind-sdk-python` and install **XMind SDK for python**. @@ -14,6 +14,12 @@ Now there will be a directory named `xmind-sdk-python` under the current directo *It is highly recommended to install __XMind SDK for python__ under an isolated python environment using [virtualenv](https://pypi.python.org/pypi/virtualenv)* +##Set PYTHONPATH +You must set PYTHONPATH environment variable to '.'. + +In Eclipse (with PyDev plugin): modify the 'Run Configuration' and define varaiable PYTHONPATH = . (with the +'Environment' Tab) + ##Usage Open an existing XMind file or create a new XMind file and place it into a given path diff --git a/setup.py b/setup.py index ea1eba3..5078ec5 100644 --- a/setup.py +++ b/setup.py @@ -5,15 +5,15 @@ setup( name="xmind", - version="0.1a.0", + version="0.0.1", packages=find_packages(), install_requires=["distribute"], - author="Woody Ai", - author_email="aiqi@xmind.net", - description="The offical XMind python SDK", + author="Michel Kern", + author_email="echopraxium@yahoo.com", + description="A fork of XMind python SDK with extensions for import / export (e.g. Graphviz)", license="MIT", - keywords="XMind, SDK, mind mapping", - url="https://github.com/xmindltd/xmind-sdk-python" + keywords="XMind, SDK, mind mapping, extension, import, export, graphviz", + url="https://github.com/Almerxsese/xmind-sdk-python" ) diff --git a/xmind/data/sample_text_file.txt b/xmind/data/sample_text_file.txt new file mode 100644 index 0000000..9daeafb --- /dev/null +++ b/xmind/data/sample_text_file.txt @@ -0,0 +1 @@ +test diff --git a/xmind/framework/Enum.py b/xmind/framework/Enum.py new file mode 100644 index 0000000..10c27e5 --- /dev/null +++ b/xmind/framework/Enum.py @@ -0,0 +1,74 @@ +#!/usr/bin/env python +#-*- coding: utf-8 -*- +""" + xmind.framework.Enum + ~~~~~~~~~~~~~~~~~ + :mod:``xmind.framework.Enum`` provides an support for an enumerated type + :copyright: http://code.activestate.com/recipes/413486-first-class-enums-in-python/ + :copyright: Zoran Isailovski + :license: PSF + :note: source code copied from 'First Class Enums in Python (Python recipe)': + http://code.activestate.com/recipes/413486-first-class-enums-in-python/ +""" + +__author__ = "http://code.activestate.com/recipes/users/2400454/ " + +#------------------- Enum ------------------- +def Enum(*names): + ##assert names, "Empty enums are not supported" # <- Don't like empty enums? Uncomment! + + class EnumClass(object): + __slots__ = names + def __iter__(self): return iter(constants) + def __len__(self): return len(constants) + def __getitem__(self, i): return constants[i] + def __repr__(self): return 'Enum' + str(names) + def __str__(self): return 'enum ' + str(constants) + + class EnumValue(object): + __slots__ = ('__value') + def __init__(self, value): self.__value = value + Value = property(lambda self: self.__value) + EnumType = property(lambda self: EnumType) + def __hash__(self): return hash(self.__value) + def __cmp__(self, other): + # C fans might want to remove the following assertion + # to make all enums comparable by ordinal value {;)) + assert self.EnumType is other.EnumType, "Only values from the same enum are comparable" + return cmp(self.__value, other.__value) + def __invert__(self): return constants[maximum - self.__value] + def __nonzero__(self): return bool(self.__value) + def __repr__(self): return str(names[self.__value]) + + maximum = len(names) - 1 + constants = [None] * len(names) + for i, each in enumerate(names): + val = EnumValue(i) + setattr(EnumClass, each, val) + constants[i] = val + constants = tuple(constants) + EnumType = EnumClass() + return EnumType +#------------------- Enum + +#======================== main ======================== +def main(): + print("** xmind.framework.Enum **") + + print '\n*** Enum Demo ***' + print '--- Days of week ---' + Days = Enum('Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa', 'Su') + print Days + print Days.Mo + print Days.Fr + print Days.Mo < Days.Fr + print list(Days) + for each in Days: + print 'Day:', each + print '--- Yes/No ---' + Confirmation = Enum('No', 'Yes') + answer = Confirmation.No + print 'Your answer is not', ~answer + +if __name__ == '__main__': + main() diff --git a/xmind/framework/UnitTest.py b/xmind/framework/UnitTest.py new file mode 100644 index 0000000..6b3897b --- /dev/null +++ b/xmind/framework/UnitTest.py @@ -0,0 +1,30 @@ +#!/usr/bin/env python +#-*- coding: utf-8 -*- +""" + xmind.framework.UnitTest + ------------------------ + :mod: ``xmind.framework.UnitTest`` provides unit test superclass + :copyright: Michel Kern + :license: MIT +""" + +__author__ = "echopraxium@yahoo.com " + +#------------------- UnitTest ------------------- +class UnitTest: + def __init__(self): + self.name = "unit_test" + + def run(self): + print("> run '" + self.name + "'") +#------------------- UnitTest + +#======================== main ======================== +def main(): + print("** xmind.framework.UnitTest **") + + ut = UnitTest() + ut.run() + +if __name__ == '__main__': + main() diff --git a/xmind/framework/__init__.py b/xmind/framework/__init__.py new file mode 100644 index 0000000..e382d5c --- /dev/null +++ b/xmind/framework/__init__.py @@ -0,0 +1,17 @@ +#!/usr/bin/env python +#-*- coding: utf-8 -*- +""" + xmind.framework + --------------- + :copyright: Michel Kern + :license: MIT + +""" +__author__ = "echopraxium@yahoo.com " + +#======================== main ======================== +def main(): + print("** xmind.framework module **") + +if __name__ == '__main__': + main() diff --git a/xmind/import_export/ExportFilter.py b/xmind/import_export/ExportFilter.py new file mode 100644 index 0000000..807cf04 --- /dev/null +++ b/xmind/import_export/ExportFilter.py @@ -0,0 +1,46 @@ +#!/usr/bin/env python +#-*- coding: utf-8 -*- +""" + xmind.import_export.ExportFilter + -------------------------------- + :mod: ``xmind.import_export.ExportFilter`` provide a handy way for exporting / importing + XMind files to other formats (e.g: GraphViz). + :copyright: Michel Kern + :license: MIT +""" + +__author__ = "echopraxium@yahoo.com " + +import os +import os.path +from xmind.framework.Enum import Enum + +FILE_NOT_FOUND_ERROR = 1 +EMPTY_OUTPUT_FILE_PATH_ERROR = 2 +RETURN_CODE = Enum('OK', 'FILE_NOT_FOUND_ERROR', 'EMPTY_OUTPUT_FILE_PATH_ERROR') + +#------------------- ExportFilter ------------------- +class ExportFilter(): + def __init__(self): + self.name = "export_filter" + + #---------- export() ---------- + def export(self, source_path, target_path): + print(self.name + ".export ") + if (not os.path.isfile(source_path)): + print("source_path: '" + source_path + "' not found") + exit(RETURN_CODE.FILE_NOT_FOUND_ERROR) + return RETURN_CODE.OK + #---------- export() +#------------------- ExportFilter + +#======================== main ======================== +def main(): + print("** xmind.ExportFilter **") + export_filter = ExportFilter() + rc = export_filter.export("../data/sample_text_file.txt", "") + print(rc) + #pass + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/xmind/import_export/GraphvizExportFilter.py b/xmind/import_export/GraphvizExportFilter.py new file mode 100644 index 0000000..c38e142 --- /dev/null +++ b/xmind/import_export/GraphvizExportFilter.py @@ -0,0 +1,172 @@ +#!/usr/bin/env python +#-*- coding: utf-8 -*- +""" + xmind.import_export.GraphvizExportFilter + ---------------------------------------- + :mod: ``xmind.import_export.ExportFilter`` provide a handy way for exporting + XMind files to dot format (see http://www.graphviz.org/pdf/dotguide.pdf) + GraphViz (http://www.graphviz.org/) + :copyright: Michel Kern + :license: MIT +""" + +__author__ = "echopraxium@yahoo.com " + +import os +import os.path +import xmind +from xmind.core import workbook,saver +from xmind.core.topic import TopicElement +from ExportFilter import ExportFilter +from ExportFilter import FILE_NOT_FOUND_ERROR, EMPTY_OUTPUT_FILE_PATH_ERROR + +#------------------- GraphvizExportFilter ------------------- +class GraphvizExportFilter(ExportFilter): + #---------- constructor ---------- + def __init__(self): + ExportFilter.__init__(self) + self.name = "graphviz_export_filter" + self.output_str = '' + self.topic_count = 0 + + #---------- traverse() ---------- + def getOutput(self): + return self.output_str + + #---------- export() ---------- + def export(self, source_path, target_path): + print(self.name + ".export ") + + if (not os.path.isfile(source_path)): + print("> *ERROR* source_path: '" + source_path + "' not found") + exit(FILE_NOT_FOUND_ERROR) + + # load an existing file or create a new workbook if nothing is found + workbook = xmind.load(source_path) + worksheet = workbook.getPrimarySheet() # get the first sheet + root_topic = worksheet.getRootTopic() # get the root topic of this sheet + + self.generateGraphvizDotString(root_topic) + self.writeGraphvizDotToFile(target_path) + self.generateImageFile(target_path, '../data/out.pdf') + + print(">> export done") + + #---------- generateGraphvizDotString() ---------- + def generateGraphvizDotString(self, root_topic): + self.output_str = u"digraph G {\n" + + #self.output_str = self.output_str + u' overlap=scalexy;'; + #self.output_str = self.output_str + u' ranksep=3;\n' + #self.output_str = self.output_str + u' ratio=auto;\n' + + self.output_str = self.output_str + u'root=topic_1;' + self.output_str = self.output_str + u'overlap=false;'; + + self.traverse(root_topic) + self.output_str = self.output_str + u"}" + #---------- generateGraphvizDotString() + + #---------- writeGraphvizDotToFile() ---------- + def writeGraphvizDotToFile(self, target_path): + if (target_path == ''): + print("> *ERROR* target_path is empty") + exit(EMPTY_OUTPUT_FILE_PATH_ERROR) + + out_fd = open(target_path, "w") + out_fd.write(self.output_str.encode(('utf8'))) + out_fd.close() + #---------- writeGraphvizDotToFile() + + #---------- generateImageFile() ---------- + def generateImageFile(self, dot_input_path, img_output_path): + if (img_output_path == ''): + print("> *ERROR* output_path is empty") + exit(EMPTY_OUTPUT_FILE_PATH_ERROR) + + #graphviz_tool = 'dot' + graphviz_tool = 'twopi' + graphviz_dot_path = 'C:\\Program Files (x86)\\Graphviz\\bin\\' + graphviz_tool + '.exe' + if (not os.path.isfile(graphviz_dot_path)): + print("> *ERROR* Graphviz dot.exe: '" + graphviz_dot_path + "' not found") + exit(FILE_NOT_FOUND_ERROR) + + # dot -Tps -l lib.ps file.gv -o file.ps + print(os.getcwd()) + cmd = '\"' + graphviz_dot_path + '\" -Tpdf ' + dot_input_path + ' -o ' + img_output_path + print(cmd) + os.system(cmd) + #---------- generateImageFile() + + #---------- traverse() ---------- + def traverse(self, topic, parent_id='', parent_index=0, level=0): + self.topic_count = self.topic_count + 1 + topic_index = self.topic_count + + if (parent_id != ''): + topic_id = u'topic_' + str(parent_index) + '_' + str(topic_index) + self.output_str = self.output_str + u' ' + parent_id + u'->' + topic_id + u';\n' + else: + topic_id = u'topic_' + str(topic_index) + + indent = ' ' * (level * 2) + topic_title = unicode(topic.getTitle()) + + msg = indent + '> ' + str(level) + ': ' + topic_title + + font_size = 30 - level*2.5 + shape_attribute = ' shape=box, ' + + # http://rich-iannone.github.io/DiagrammeR/graphviz.html + color_attribute = ' style=filled, color=Beige, ' + + if (level == 0): + font_size = 35 + shape_attribute = ' shape=box, margin=\"0.2,0.1\", ' + elif (level == 1): + font_size = 30 + shape_attribute = ' shape=box, margin=\"0.2,0.07\", ' + else: + font_size = 30 - level*2.5 + shape_attribute = ' shape=plaintext, ' + color_attribute = ' style=filled, color=LightCyan, ' + + font_attribute = u'fontname = \"Helvetica\", fontsize = ' + str(font_size) + ', ' + + #if (len(topic_title) > 15): + # words = topic_title.split(' ') + # word_count = len(words) + # multiline_topic_title= '' + # word_index = 0 + # for word in words: + # multiline_topic_title = multiline_topic_title + word[word_index] + + label_attribute = ' label=\"' + topic_title + '\", ' + + self.output_str = self.output_str + u' ' + topic_id + u' [' + self.output_str = self.output_str + font_attribute + label_attribute + shape_attribute + color_attribute + self.output_str = self.output_str + u' ];\n' + + topics = topic.getSubTopics() + + if (topics == None): + print(msg + ' is a LEAF') + return + else: + print(msg) + + for child_topic in topics: + self.traverse(child_topic, topic_id, topic_index, level+1) + #---------- export() +#------------------- GraphvizExportFilter + +#======================== main ======================== +def main(): + print("** xmind.GraphvizExportFilter **") + export_filter = GraphvizExportFilter() + export_filter.export("../data/Hindouisme.xmind", "../data/map.dot") + print(export_filter.getOutput()) + #pass + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/xmind/import_export/__init__.py b/xmind/import_export/__init__.py new file mode 100644 index 0000000..6f9cdf1 --- /dev/null +++ b/xmind/import_export/__init__.py @@ -0,0 +1,17 @@ +#!/usr/bin/env python +#-*- coding: utf-8 -*- +""" + xmind.import_export module + -------------------------- + :copyright: Michel Kern + :license: MIT + +""" +__author__ = "echopraxium@yahoo.com " + +#======================== main ======================== +def main(): + print("** xmind.import_export module **") + +if __name__ == '__main__': + main()