[
  {
    "path": ".github/workflows/test-publish.yml",
    "content": "name: Test and Publish\n\non:\n  push:\n    branches: [main, master]\n    tags:\n      - \"v*\"\n  pull_request:\n    branches: [main, master]\n\npermissions:\n  contents: read\n\njobs:\n  test:\n    runs-on: ubuntu-latest\n    strategy:\n      matrix:\n        python-version: [\"3.9\", \"3.10\", \"3.11\", \"3.12\", \"3.13\", \"3.14\"]\n\n    steps:\n      - uses: actions/checkout@v4\n\n      - name: Set up Python ${{ matrix.python-version }}\n        uses: actions/setup-python@v5\n        with:\n          python-version: ${{ matrix.python-version }}\n\n      - name: Install dependencies\n        run: |\n          python -m pip install --upgrade pip\n          pip install pytest dicttoxml pyyaml\n\n      - name: Run tests\n        run: |\n          pytest tests/ -v\n\n  build:\n    needs: test\n    runs-on: ubuntu-latest\n    if: startsWith(github.ref, 'refs/tags/v')\n    permissions:\n      contents: write\n\n    steps:\n      - uses: actions/checkout@v4\n\n      - name: Set up Python\n        uses: actions/setup-python@v5\n        with:\n          python-version: \"3.13\"\n\n      - name: Install build dependencies\n        run: |\n          python -m pip install --upgrade pip\n          pip install build twine\n\n      - name: Set version from tag\n        run: |\n          VERSION=${GITHUB_REF#refs/tags/v}\n          echo \"VERSION=$VERSION\" >> $GITHUB_ENV\n          sed -i \"s/version=\\\"[^\\\"]*\\\"/version=\\\"$VERSION\\\"/\" setup.py\n\n      - name: Build package\n        run: |\n          python -m build\n\n      - name: Check package\n        run: |\n          twine check dist/*\n\n      - name: Publish to PyPI\n        env:\n          TWINE_USERNAME: __token__\n          TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }}\n        run: |\n          twine upload --skip-existing dist/*\n\n  build-sdist:\n    needs: test\n    runs-on: ubuntu-latest\n    if: startsWith(github.ref, 'refs/tags/v')\n    permissions:\n      contents: write\n\n    steps:\n      - uses: actions/checkout@v4\n\n      - name: Set up Python\n        uses: actions/setup-python@v5\n        with:\n          python-version: \"3.13\"\n\n      - name: Install build dependencies\n        run: |\n          python -m pip install --upgrade pip\n          pip install build twine\n\n      - name: Build sdist\n        run: |\n          python -m build --sdist\n\n      - name: Upload to GitHub Release\n        uses: softprops/action-gh-release@v1\n        with:\n          files: dist/*.tar.gz\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n"
  },
  {
    "path": ".gitignore",
    "content": "# Byte-compiled / optimized / DLL files\n__pycache__/\n*.py[cod]\n*$py.class\n.cache\nout/\n.idea/\n.pytest_cache/\nout/\n.vscode/\n\n# C extensions\n*.so\n\n# Distribution / packaging\n.Python\nenv/\nbuild/\ndevelop-eggs/\ndist/\ndownloads/\neggs/\n.eggs/\nlib/\nlib64/\nparts/\nsdist/\nvar/\n*.egg-info/\n.installed.cfg\n*.egg\n\n# PyInstaller\n#  Usually these files are written by a python script from a template\n#  before PyInstaller builds the exe, so as to inject date/other infos into it.\n*.manifest\n*.spec\n\n# Installer logs\npip-log.txt\npip-delete-this-directory.txt\n\n# Unit test / coverage reports\nhtmlcov/\n.tox/\n.coverage\n.coverage.*\n.cache\nnosetests.xml\ncoverage.xml\n*,cover\n.hypothesis/\n\n# Translations\n*.mo\n*.pot\n\n# Django stuff:\n*.log\nlocal_settings.py\n\n# Flask stuff:\ninstance/\n.webassets-cache\n\n# Scrapy stuff:\n.scrapy\n\n# Sphinx documentation\ndocs/_build/\n\n# PyBuilder\ntarget/\n\n# IPython Notebook\n.ipynb_checkpoints\n\n# pyenv\n.python-version\n\n# celery beat schedule file\ncelerybeat-schedule\n\n# dotenv\n.env\n\n# virtualenv\nvenv/\nENV/\nuploads/\n\n# Spyder project settings\n.spyderproject\n\n# Rope project settings\n.ropeproject\n\n# sqlite\n*.sqlite3\n*.db3\n\n.DS_Store\n\n# Claude Code\nCLAUDE.md\n.claude/\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "## Change Log\n\n1.2.2\n\n- Add new config options: `showStructure` (default True) to include/exclude sheet structure info, and `showRelationship` (default False) to include relationship info for Xmind Zen/2026 files.\n- Add support for parsing relationships in Xmind Zen/2026 files when `showRelationship` is enabled.\n- Make structure inclusion conditional for Xmind Zen/2026 files based on `showStructure` config.\n- Add comprehensive tests for the new config options.\n- Update documentation in both English and Chinese READMEs.\n\n  1.2.1\n\n- Fix config system to support dynamic logger reconfiguration.\n- Add `apply_config()` function to apply logging configuration changes.\n- Add comprehensive tests for all config options (hideEmptyValue, logFormat, logLevel, logName).\n- Update documentation with proper config usage examples.\n\n  1.2.0\n\n- Add YAML export support with `xmind_to_yaml()` function.\n- Add support for Xmind 2026 file format.\n- Update documentation with local Chinese README and developer guide.\n- Rename Xmind legacy to Xmind 8, Xmind Zen to Xmind.\n\n  1.1.2\n\n- Fix Chinese character encoding issue in JSON export by adding `ensure_ascii=False` to `json.dumps()`.\n\n  1.1.1\n\n- Add Python 3.14 support.\n\n  1.1.0\n\n- Add support for converting xmind to markdown format.\n- New function `xmind_to_markdown()` to convert xmind file to markdown.\n- Command line support: `xmindparser your.xmind -markdown`.\n\n  1.0.9\n\n- Update Python version classifiers to support 3.9, 3.10, 3.11, 3.12, 3.13.\n- Fix DeprecationWarning for element truth value testing in Python 3.13.\n\n  1.0.8\n\n- Handle empty title name for xmind zen in some cases.\n\n  1.0.6\n\n- Keep empty topic title as null but not \"[Blank]\"\n\n  1.0.5\n\n- Support xmind zen file type.\n\n  1.0.4\n\n- Support parse label feature.\n\n  1.0.2\n\n- Rename config key names.\n\n  1.0.1\n\n- Support parse xmind to xml file type.\n\n  1.0.0\n\n- Support parse xmind to dict data type with Python.\n- Support parse xmind to json file type.\n"
  },
  {
    "path": "DEVELOPER.md",
    "content": "# For Developers\n\n## Publish New Version\n\nThis project uses GitHub Actions to automatically publish to PyPI when a new version tag is pushed.\n\n1. Update version in [`setup.py`](setup.py:42) and [`CHANGELOG.md`](CHANGELOG.md)\n2. Commit and push changes to GitHub\n3. Create and push a new version tag:\n   ```shell\n   git tag v1.1.0\n   git push origin v1.1.0\n   ```\n4. GitHub Actions will automatically:\n   - Run tests on Python 3.9-3.13\n   - Build and publish to PyPI\n   - Create GitHub Release with source distribution\n\n**Note:** Requires `PYPI_API_TOKEN` secret to be configured in repository settings.\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2018 Toby Qin\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "MANIFEST.in",
    "content": "include LICENSE\ninclude README.md\ninclude CHANGELOG.md\ninclude setup.py"
  },
  {
    "path": "README.md",
    "content": "# xmindparser\n\n[![PyPI](https://img.shields.io/pypi/v/xmindparser.svg)](https://pypi.org/project/xmindparser/)\n\nParse xmind file to programmable data type (e.g. json, xml). Python 3.x required. Now it supports Xmind (including Xmind Zen and Xmind 2026) file type as well.\n\nSee also: [xmind2testlink](https://github.com/tobyqin/xmind2testlink) / [中文文档](README_CN.md)\n\n## Installation\n\n```shell\npip install xmindparser\n```\n\n## Usage - Command Line\n\n```shell\ncd /your/xmind/dir\n\nxmindparser your.xmind -json\nxmindparser your.xmind -xml\nxmindparser your.xmind -yaml\nxmindparser your.xmind -markdown\n```\n\nNote: Parse to xml/yaml file types require additional packages:\n\n- xml: [dicttoxml](https://pypi.org/project/dicttoxml/)\n- yaml: [pyyaml](https://pypi.org/project/pyyaml/)\n\n## Usage - via Python\n\n```python\nfrom xmindparser import xmind_to_dict\n\nd = xmind_to_dict('/path/to/your/xmind')\nprint(d)\n```\n\nSee example output: [json](doc/example.json)\n\n### Convert to Markdown\n\n```python\nfrom xmindparser import xmind_to_markdown\n\n# Convert xmind to markdown file\noutput_file = xmind_to_markdown('/path/to/your/xmind')\nprint(f'Generated: {output_file}')\n```\n\nOr use the generic `xmind_to_file` function:\n\n```python\nfrom xmindparser import xmind_to_file\n\n# Convert to markdown\noutput_file = xmind_to_file('/path/to/your/xmind', 'markdown')\nprint(f'Generated: {output_file}')\n```\n\n## Configuration\n\nIf you use `xmindparser` via Python, it provides a `config` object, check this example:\n\n```python\nimport logging\nfrom xmindparser import xmind_to_dict, config, apply_config\n\n# Modify config settings\nconfig['logName'] = 'your_log_name'\nconfig['logLevel'] = logging.DEBUG\nconfig['logFormat'] = '%(asctime)s %(levelname)-8s: %(message)s'\nconfig['showTopicId'] = True  # internal id will be included, default = False\nconfig['hideEmptyValue'] = False  # empty values will be hidden, default = True\nconfig['showStructure'] = False  # include structure info for sheets, default = True\nconfig['showRelationship'] = True  # include relationship info for Zen/2026 files, default = False\n\n# Apply the config changes (required for logging settings to take effect)\napply_config()\n\nd = xmind_to_dict('/path/to/your/xmind')\nprint(d)\n\n```\n\n**Note:** After modifying logging-related config options (`logName`, `logLevel`, `logFormat`), you must call `apply_config()` to apply the changes. The `showTopicId`, `hideEmptyValue`, `showStructure`, and `showRelationship` options take effect immediately without calling `apply_config()`.\n\n## Limitations (for Xmind 8)\n\nPlease note, following xmind features will not be supported or partially supported.\n\n- Will not parse Pro features, e.g. Task Info, Audio Note\n- Will not parse floating topics.\n- Will not parse linked topics.\n- Will not parse summary info.\n- Will not parse relationship info (except for Xmind Zen/2026 when `showRelationship` is enabled).\n- Will not parse boundary info.\n- Will not parse attachment object, only name it as `[Attachment] - name`\n- Will not parse image object, only name it as `[Image]`\n- Rich text format in notes will be parsed as plain text.\n\n## Xmind (including Zen and 2026)\n\n`xmindparser` will auto detect xmind file created by Xmind (including Zen/2026 version) or Xmind 8, you can pass in the file as usual.\n\n```python\nfrom xmindparser import xmind_to_dict\n\nd = xmind_to_dict('/path/to/your/xmind_zen_file')\nprint(d)\n```\n\nPlease note, there are a few differences between Xmind 8 and Xmind (Zen/2026).\n\n- Comments feature removed, so I will not parse it in ZEN.\n- Add feature - sticker, I parse it as `image` dict type.\n- Add feature - callout, I parse it as `list` type. (not sure existed in legacy?)\n\nSince Xmind (Zen/2026) uses json as the internal content file, you can read it by code like this:\n\n```python\nimport json\n\ndef get_xmind_zen_json(file_path):\n    name = \"content.json\"\n    with ZipFile(file_path) as xmind:\n        if name in xmind.namelist():\n            content = xmind.open(name).read().decode('utf-8')\n            return json.loads(content)\n\n        raise AssertionError(\"Not a xmind zen file type!\")\n\n# xmindparser also provides a shortcut\nfrom xmindparser import get_xmind_zen_builtin_json\n\ncontent_json = get_xmind_zen_builtin_json(xmind_zen_file)\n```\n\n## Examples\n\n![Xmind Example](doc/xmind.png)\n\n- [Download xmind 8 example](tests/xmind_pro.xmind)\n- [Download xmind (Zen/2026) example](tests/xmind_zen.xmind)\n- [Download xmind 2026 example](tests/xmind_2026.xmind)\n- Output: [json example](doc/example.json)\n- Output: [xml example](doc/example.xml)\n- Output: [yaml example](doc/example.yaml)\n- Output: [markdown example](doc/example.md)\n\n## License\n\nMIT\n"
  },
  {
    "path": "README_CN.md",
    "content": "# xmindparser\n\n[![PyPI](https://img.shields.io/pypi/v/xmindparser.svg)](https://pypi.org/project/xmindparser/)\n\n将 xmind 文件转换为可编程的数据类型（如 json、xml）。需要 Python 3.x。支持 Xmind（包括 Xmind Zen 和 Xmind 2026）文件类型。\n\n相关项目：[xmind2testlink](https://github.com/tobyqin/xmind2testlink)\n\nEnglish version: [README.md](README.md)\n\n## 安装\n\n```shell\npip install xmindparser\n```\n\n## 使用方法 - 命令行\n\n```shell\ncd /your/xmind/dir\n\nxmindparser your.xmind -json\nxmindparser your.xmind -xml\nxmindparser your.xmind -yaml\nxmindparser your.xmind -markdown\n```\n\n注意：解析为 xml/yaml 文件类型需要安装额外依赖：\n\n- xml：[dicttoxml](https://pypi.org/project/dicttoxml/)\n- yaml：[pyyaml](https://pypi.org/project/pyyaml/)\n\n## 使用方法 - Python 代码\n\n```python\nfrom xmindparser import xmind_to_dict\n\nd = xmind_to_dict('/path/to/your/xmind')\nprint(d)\n```\n\n查看示例输出：[json 示例](doc/example.json)\n\n### 转换为 Markdown\n\n```python\nfrom xmindparser import xmind_to_markdown\n\n# 将 xmind 转换为 markdown 文件\noutput_file = xmind_to_markdown('/path/to/your/xmind')\nprint(f'Generated: {output_file}')\n```\n\n或者使用通用的 `xmind_to_file` 函数：\n\n```python\nfrom xmindparser import xmind_to_file\n\n# 转换为 markdown\noutput_file = xmind_to_file('/path/to/your/xmind', 'markdown')\nprint(f'Generated: {output_file}')\n```\n\n## 配置\n\n如果您通过 Python 使用 `xmindparser`，它提供了一个 `config` 对象，请查看以下示例：\n\n```python\nimport logging\nfrom xmindparser import xmind_to_dict, config, apply_config\n\n# 修改配置设置\nconfig['logName'] = 'your_log_name'\nconfig['logLevel'] = logging.DEBUG\nconfig['logFormat'] = '%(asctime)s %(levelname)-8s: %(message)s'\nconfig['showTopicId'] = True  # 包含内部 id，默认为 False\nconfig['hideEmptyValue'] = False  # 隐藏空值，默认为 True\nconfig['showStructure'] = False  # 包含工作表的结构信息，默认为 True\nconfig['showRelationship'] = True  # 包含关系信息（针对 Zen/2026 文件），默认为 False\n\n# 应用配置更改（日志相关设置需要调用此函数才能生效）\napply_config()\n\nd = xmind_to_dict('/path/to/your/xmind')\nprint(d)\n\n```\n\n**注意：** 修改日志相关的配置选项（`logName`、`logLevel`、`logFormat`）后，必须调用 `apply_config()` 来应用更改。`showTopicId`、`hideEmptyValue`、`showStructure` 和 `showRelationship` 选项无需调用 `apply_config()` 即可立即生效。\n\n## 限制（针对 Xmind 8）\n\n请注意，以下 xmind 功能将不被支持或仅部分支持。\n\n- 不会解析 Pro 功能，例如任务信息、音频笔记\n- 不会解析浮动主题\n- 不会解析链接主题\n- 不会解析摘要信息\n- 不会解析关系信息（除非针对 Xmind Zen/2026 启用 `showRelationship` 时）\n- 不会解析边界信息\n- 不会解析附件对象，仅将其命名为 `[Attachment] - name`\n- 不会解析图片对象，仅将其命名为 `[Image]`\n- 笔记中的富文本格式将解析为纯文本\n\n## Xmind 支持（包括 Zen 和 2026）\n\n`xmindparser` 会自动检测 Xmind（包括 Zen/2026 版本）或 Xmind 8 创建的 xmind 文件，您可以像平常一样传入文件。\n\n```python\nfrom xmindparser import xmind_to_dict\n\nd = xmind_to_dict('/path/to/your/xmind_zen_file')\nprint(d)\n```\n\n请注意，Xmind 8 和 Xmind（Zen/2026）之间存在一些差异。\n\n- 评论功能已移除，因此我不会在 ZEN 中解析它\n- 新增功能 - 贴纸，我将其解析为 `image` 字典类型\n- 新增功能 - 标注，我将其解析为 `list` 类型（不确定传统版中是否存在？）\n\n由于 Xmind（Zen/2026）使用 json 作为内部内容文件，您可以通过以下代码读取：\n\n```python\nimport json\n\ndef get_xmind_zen_json(file_path):\n    name = \"content.json\"\n    with ZipFile(file_path) as xmind:\n        if name in xmind.namelist():\n            content = xmind.open(name).read().decode('utf-8')\n            return json.loads(content)\n\n        raise AssertionError(\"Not a xmind zen file type!\")\n\n# xmindparser 也提供了快捷方式\nfrom xmindparser import get_xmind_zen_builtin_json\n\ncontent_json = get_xmind_zen_builtin_json(xmind_zen_file)\n```\n\n## 示例\n\n![Xmind 示例](doc/xmind.png)\n\n- [下载 xmind 8 示例](tests/xmind_pro.xmind)\n- [下载 xmind（Zen/2026）示例](tests/xmind_zen.xmind)\n- [下载 xmind 2026 示例](tests/xmind_2026.xmind)\n- 输出：[json 示例](doc/example.json)\n- 输出：[xml 示例](doc/example.xml)\n- 输出：[yaml 示例](doc/example.yaml)\n- 输出：[markdown 示例](doc/example.md)\n\n## 许可证\n\nMIT\n"
  },
  {
    "path": "doc/example.json",
    "content": "[\n  {\n    \"title\": \"Sheet 1\",\n    \"topic\": {\n      \"title\": \"test\",\n      \"makers\": [\n        \"star-orange\"\n      ],\n      \"topics\": [\n        {\n          \"title\": \"a\",\n          \"link\": \"http://test.com\",\n          \"topics\": [\n            {\n              \"title\": \"e\",\n              \"topics\": [\n                {\n                  \"title\": \"f\",\n                  \"topics\": [\n                    {\n                      \"title\": \"g\"\n                    }\n                  ]\n                }\n              ]\n            },\n            {\n              \"title\": \"d\",\n              \"topics\": [\n                {\n                  \"title\": null\n                }\n              ]\n            },\n            {\n              \"title\": \"e\",\n              \"makers\": [\n                \"symbol-plus\"\n              ],\n              \"topics\": [\n                {\n                  \"title\": null,\n                  \"note\": \"this is a note\"\n                }\n              ]\n            },\n            {\n              \"title\": null,\n              \"topics\": [\n                {\n                  \"title\": \"h\",\n                  \"topics\": [\n                    {\n                      \"title\": null,\n                      \"comment\": [\n                        {\n                          \"author\": \"toby.qin\",\n                          \"content\": \"this a comments\"\n                        }\n                      ]\n                    }\n                  ]\n                }\n              ]\n            }\n          ]\n        },\n        {\n          \"title\": \"b\",\n          \"makers\": [\n            \"task-done\",\n            \"flag-red\",\n            \"people-red\",\n            \"arrow-up\"\n          ],\n          \"topics\": [\n            {\n              \"title\": \"types\",\n              \"topics\": [\n                {\n                  \"title\": \"comment\",\n                  \"comment\": [\n                    {\n                      \"author\": \"toby.qin\",\n                      \"content\": \"comment1\"\n                    },\n                    {\n                      \"author\": \"toby.qin\",\n                      \"content\": \"comment2\"\n                    },\n                    {\n                      \"author\": \"toby.qin\",\n                      \"content\": \"comment3\\r\\nnew line\"\n                    }\n                  ]\n                },\n                {\n                  \"title\": \"note\",\n                  \"note\": \"1 line note\",\n                  \"topics\": [\n                    {\n                      \"title\": \"note2\",\n                      \"note\": \"line1\\r\\nline2\"\n                    },\n                    {\n                      \"title\": \"note3\",\n                      \"note\": \"note with style\\r\\nline with strike\"\n                    }\n                  ]\n                },\n                {\n                  \"title\": \"label\",\n                  \"labels\": [\n                    \"this is a  label\"\n                  ]\n                },\n                {\n                  \"title\": \"attachment\",\n                  \"topics\": [\n                    {\n                      \"title\": \"[Attachment]test.txt\"\n                    }\n                  ]\n                },\n                {\n                  \"title\": \"pic\",\n                  \"topics\": [\n                    {\n                      \"title\": \"[Image]\"\n                    }\n                  ]\n                },\n                {\n                  \"title\": \"links\",\n                  \"topics\": [\n                    {\n                      \"title\": \"to url\",\n                      \"link\": \"http://test.com\"\n                    },\n                    {\n                      \"title\": \"to file\",\n                      \"link\": \"file:////abc/ef\"\n                    },\n                    {\n                      \"title\": \"to topic\",\n                      \"link\": \"[To another xmind topic!]\"\n                    }\n                  ]\n                }\n              ]\n            },\n            {\n              \"title\": \"\\u6d4b\\u8bd5\",\n              \"topics\": [\n                {\n                  \"title\": \"note\",\n                  \"note\": \"\\u6d4b\\u8bd5\\u4e2d\\u6587\"\n                },\n                {\n                  \"title\": \"comments\",\n                  \"comment\": [\n                    {\n                      \"author\": \"toby.qin\",\n                      \"content\": \"\\u4e2d\\u6587\\u6d4b\\u8bd5\"\n                    }\n                  ]\n                }\n              ]\n            },\n            {\n              \"title\": \"</>{}[]*+-\"\n            }\n          ]\n        },\n        {\n          \"title\": \"c\",\n          \"topics\": [\n            {\n              \"title\": \"a\"\n            },\n            {\n              \"title\": \"a\"\n            },\n            {\n              \"title\": \"a\",\n              \"link\": \"[To another xmind topic!]\"\n            }\n          ]\n        }\n      ]\n    },\n    \"structure\": \"org.xmind.ui.map.unbalanced\"\n  },\n  {\n    \"title\": \"Sheet 2\",\n    \"topic\": {\n      \"title\": \"test2\",\n      \"topics\": [\n        {\n          \"title\": \"a\",\n          \"topics\": [\n            {\n              \"title\": \"1\"\n            },\n            {\n              \"title\": \"2\"\n            },\n            {\n              \"title\": \"3\"\n            }\n          ]\n        },\n        {\n          \"title\": \"b\"\n        }\n      ]\n    },\n    \"structure\": \"org.xmind.ui.map.unbalanced\"\n  }\n]"
  },
  {
    "path": "doc/example.md",
    "content": "# Sheet 1\n\n## test\n\n### a\n\n[Link](http://test.com)\n\n#### e\n\n##### f\n\n###### g\n#### d\n\n#### e\n\n\n> this is a note\n\n##### h\n\n\n**Comments:**\n- **toby.qin**: this a comments\n### b\n\n#### types\n\n##### comment\n\n**Comments:**\n- **toby.qin**: comment1\n- **toby.qin**: comment2\n- **toby.qin**: comment3\r new line\n##### note\n\n> 1 line note\n\n###### note2\n\n> line1\r\n> line2\n###### note3\n\n> note with style\r\n> line with strike\n##### label\n\n**Labels:** this is a  label\n##### attachment\n\n###### [Attachment]test.txt\n##### pic\n\n###### [Image]\n##### links\n\n###### to url\n\n[Link](http://test.com)\n###### to file\n\n[Link](file:////abc/ef)\n###### to topic\n#### 测试\n\n##### note\n\n> 测试中文\n##### comments\n\n**Comments:**\n- **toby.qin**: 中文测试\n#### </>{}[]*+-\n### c\n\n#### a\n#### a\n#### a\n\n# Sheet 2\n\n## test2\n\n### a\n\n#### 1\n#### 2\n#### 3\n### b\n"
  },
  {
    "path": "doc/example.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf8\"?>\n<root>\n\t<item type=\"dict\">\n\t\t<structure type=\"str\">org.xmind.ui.map.unbalanced</structure>\n\t\t<topic type=\"dict\">\n\t\t\t<topics type=\"list\">\n\t\t\t\t<item type=\"dict\">\n\t\t\t\t\t<link type=\"str\">http://test.com</link>\n\t\t\t\t\t<topics type=\"list\">\n\t\t\t\t\t\t<item type=\"dict\">\n\t\t\t\t\t\t\t<topics type=\"list\">\n\t\t\t\t\t\t\t\t<item type=\"dict\">\n\t\t\t\t\t\t\t\t\t<topics type=\"list\">\n\t\t\t\t\t\t\t\t\t\t<item type=\"dict\">\n\t\t\t\t\t\t\t\t\t\t\t<title type=\"str\">g</title>\n\t\t\t\t\t\t\t\t\t\t</item>\n\t\t\t\t\t\t\t\t\t</topics>\n\t\t\t\t\t\t\t\t\t<title type=\"str\">f</title>\n\t\t\t\t\t\t\t\t</item>\n\t\t\t\t\t\t\t</topics>\n\t\t\t\t\t\t\t<title type=\"str\">e</title>\n\t\t\t\t\t\t</item>\n\t\t\t\t\t\t<item type=\"dict\">\n\t\t\t\t\t\t\t<topics type=\"list\">\n\t\t\t\t\t\t\t\t<item type=\"dict\">\n\t\t\t\t\t\t\t\t\t<title type=\"str\">[Blank]</title>\n\t\t\t\t\t\t\t\t</item>\n\t\t\t\t\t\t\t</topics>\n\t\t\t\t\t\t\t<title type=\"str\">d</title>\n\t\t\t\t\t\t</item>\n\t\t\t\t\t\t<item type=\"dict\">\n\t\t\t\t\t\t\t<topics type=\"list\">\n\t\t\t\t\t\t\t\t<item type=\"dict\">\n\t\t\t\t\t\t\t\t\t<note type=\"str\">this is a note</note>\n\t\t\t\t\t\t\t\t\t<title type=\"str\">[Blank]</title>\n\t\t\t\t\t\t\t\t</item>\n\t\t\t\t\t\t\t</topics>\n\t\t\t\t\t\t\t<title type=\"str\">e</title>\n\t\t\t\t\t\t\t<makers type=\"list\">\n\t\t\t\t\t\t\t\t<item type=\"str\">symbol-plus</item>\n\t\t\t\t\t\t\t</makers>\n\t\t\t\t\t\t</item>\n\t\t\t\t\t\t<item type=\"dict\">\n\t\t\t\t\t\t\t<topics type=\"list\">\n\t\t\t\t\t\t\t\t<item type=\"dict\">\n\t\t\t\t\t\t\t\t\t<topics type=\"list\">\n\t\t\t\t\t\t\t\t\t\t<item type=\"dict\">\n\t\t\t\t\t\t\t\t\t\t\t<comment type=\"list\">\n\t\t\t\t\t\t\t\t\t\t\t\t<item type=\"dict\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t<content type=\"str\">this a comments</content>\n\t\t\t\t\t\t\t\t\t\t\t\t\t<author type=\"str\">toby.qin</author>\n\t\t\t\t\t\t\t\t\t\t\t\t</item>\n\t\t\t\t\t\t\t\t\t\t\t</comment>\n\t\t\t\t\t\t\t\t\t\t\t<title type=\"str\">[Blank]</title>\n\t\t\t\t\t\t\t\t\t\t</item>\n\t\t\t\t\t\t\t\t\t</topics>\n\t\t\t\t\t\t\t\t\t<title type=\"str\">h</title>\n\t\t\t\t\t\t\t\t</item>\n\t\t\t\t\t\t\t</topics>\n\t\t\t\t\t\t\t<title type=\"str\">[Blank]</title>\n\t\t\t\t\t\t</item>\n\t\t\t\t\t</topics>\n\t\t\t\t\t<title type=\"str\">a</title>\n\t\t\t\t</item>\n\t\t\t\t<item type=\"dict\">\n\t\t\t\t\t<topics type=\"list\">\n\t\t\t\t\t\t<item type=\"dict\">\n\t\t\t\t\t\t\t<topics type=\"list\">\n\t\t\t\t\t\t\t\t<item type=\"dict\">\n\t\t\t\t\t\t\t\t\t<comment type=\"list\">\n\t\t\t\t\t\t\t\t\t\t<item type=\"dict\">\n\t\t\t\t\t\t\t\t\t\t\t<content type=\"str\">comment1</content>\n\t\t\t\t\t\t\t\t\t\t\t<author type=\"str\">toby.qin</author>\n\t\t\t\t\t\t\t\t\t\t</item>\n\t\t\t\t\t\t\t\t\t\t<item type=\"dict\">\n\t\t\t\t\t\t\t\t\t\t\t<content type=\"str\">comment2</content>\n\t\t\t\t\t\t\t\t\t\t\t<author type=\"str\">toby.qin</author>\n\t\t\t\t\t\t\t\t\t\t</item>\n\t\t\t\t\t\t\t\t\t\t<item type=\"dict\">\n\t\t\t\t\t\t\t\t\t\t\t<content type=\"str\">comment3\n\t\t\t\t\t\t\t\t\t\t\t\tnew line\n\t\t\t\t\t\t\t\t\t\t\t</content>\n\t\t\t\t\t\t\t\t\t\t\t<author type=\"str\">toby.qin</author>\n\t\t\t\t\t\t\t\t\t\t</item>\n\t\t\t\t\t\t\t\t\t</comment>\n\t\t\t\t\t\t\t\t\t<title type=\"str\">comment</title>\n\t\t\t\t\t\t\t\t</item>\n\t\t\t\t\t\t\t\t<item type=\"dict\">\n\t\t\t\t\t\t\t\t\t<note type=\"str\">1 line note</note>\n\t\t\t\t\t\t\t\t\t<topics type=\"list\">\n\t\t\t\t\t\t\t\t\t\t<item type=\"dict\">\n\t\t\t\t\t\t\t\t\t\t\t<note type=\"str\">line1\n\t\t\t\t\t\t\t\t\t\t\t\tline2\n\t\t\t\t\t\t\t\t\t\t\t</note>\n\t\t\t\t\t\t\t\t\t\t\t<title type=\"str\">note2</title>\n\t\t\t\t\t\t\t\t\t\t</item>\n\t\t\t\t\t\t\t\t\t\t<item type=\"dict\">\n\t\t\t\t\t\t\t\t\t\t\t<note type=\"str\">note with style\n\t\t\t\t\t\t\t\t\t\t\t\tline with strike\n\t\t\t\t\t\t\t\t\t\t\t</note>\n\t\t\t\t\t\t\t\t\t\t\t<title type=\"str\">note3</title>\n\t\t\t\t\t\t\t\t\t\t</item>\n\t\t\t\t\t\t\t\t\t</topics>\n\t\t\t\t\t\t\t\t\t<title type=\"str\">note</title>\n\t\t\t\t\t\t\t\t</item>\n\t\t\t\t\t\t\t\t<item type=\"dict\">\n\t\t\t\t\t\t\t\t\t<labels type=\"list\">\n\t\t\t\t\t\t\t\t\t\t<item type=\"str\">this is a label</item>\n\t\t\t\t\t\t\t\t\t</labels>\n\t\t\t\t\t\t\t\t\t<title type=\"str\">label</title>\n\t\t\t\t\t\t\t\t</item>\n\t\t\t\t\t\t\t\t<item type=\"dict\">\n\t\t\t\t\t\t\t\t\t<topics type=\"list\">\n\t\t\t\t\t\t\t\t\t\t<item type=\"dict\">\n\t\t\t\t\t\t\t\t\t\t\t<title type=\"str\">[Attachment]test.txt</title>\n\t\t\t\t\t\t\t\t\t\t</item>\n\t\t\t\t\t\t\t\t\t</topics>\n\t\t\t\t\t\t\t\t\t<title type=\"str\">attachment</title>\n\t\t\t\t\t\t\t\t</item>\n\t\t\t\t\t\t\t\t<item type=\"dict\">\n\t\t\t\t\t\t\t\t\t<topics type=\"list\">\n\t\t\t\t\t\t\t\t\t\t<item type=\"dict\">\n\t\t\t\t\t\t\t\t\t\t\t<title type=\"str\">[Image]</title>\n\t\t\t\t\t\t\t\t\t\t</item>\n\t\t\t\t\t\t\t\t\t</topics>\n\t\t\t\t\t\t\t\t\t<title type=\"str\">pic</title>\n\t\t\t\t\t\t\t\t</item>\n\t\t\t\t\t\t\t\t<item type=\"dict\">\n\t\t\t\t\t\t\t\t\t<topics type=\"list\">\n\t\t\t\t\t\t\t\t\t\t<item type=\"dict\">\n\t\t\t\t\t\t\t\t\t\t\t<link type=\"str\">http://test.com</link>\n\t\t\t\t\t\t\t\t\t\t\t<title type=\"str\">to url</title>\n\t\t\t\t\t\t\t\t\t\t</item>\n\t\t\t\t\t\t\t\t\t\t<item type=\"dict\">\n\t\t\t\t\t\t\t\t\t\t\t<link type=\"str\">file:////abc/ef</link>\n\t\t\t\t\t\t\t\t\t\t\t<title type=\"str\">to file</title>\n\t\t\t\t\t\t\t\t\t\t</item>\n\t\t\t\t\t\t\t\t\t\t<item type=\"dict\">\n\t\t\t\t\t\t\t\t\t\t\t<link type=\"str\">[To another xmind topic!]</link>\n\t\t\t\t\t\t\t\t\t\t\t<title type=\"str\">to topic</title>\n\t\t\t\t\t\t\t\t\t\t</item>\n\t\t\t\t\t\t\t\t\t</topics>\n\t\t\t\t\t\t\t\t\t<title type=\"str\">links</title>\n\t\t\t\t\t\t\t\t</item>\n\t\t\t\t\t\t\t</topics>\n\t\t\t\t\t\t\t<title type=\"str\">types</title>\n\t\t\t\t\t\t</item>\n\t\t\t\t\t\t<item type=\"dict\">\n\t\t\t\t\t\t\t<topics type=\"list\">\n\t\t\t\t\t\t\t\t<item type=\"dict\">\n\t\t\t\t\t\t\t\t\t<note type=\"str\">测试中文</note>\n\t\t\t\t\t\t\t\t\t<title type=\"str\">note</title>\n\t\t\t\t\t\t\t\t</item>\n\t\t\t\t\t\t\t\t<item type=\"dict\">\n\t\t\t\t\t\t\t\t\t<comment type=\"list\">\n\t\t\t\t\t\t\t\t\t\t<item type=\"dict\">\n\t\t\t\t\t\t\t\t\t\t\t<content type=\"str\">中文测试</content>\n\t\t\t\t\t\t\t\t\t\t\t<author type=\"str\">toby.qin</author>\n\t\t\t\t\t\t\t\t\t\t</item>\n\t\t\t\t\t\t\t\t\t</comment>\n\t\t\t\t\t\t\t\t\t<title type=\"str\">comments</title>\n\t\t\t\t\t\t\t\t</item>\n\t\t\t\t\t\t\t</topics>\n\t\t\t\t\t\t\t<title type=\"str\">测试</title>\n\t\t\t\t\t\t</item>\n\t\t\t\t\t\t<item type=\"dict\">\n\t\t\t\t\t\t\t<title type=\"str\">&lt;/&gt;{}[]*+-</title>\n\t\t\t\t\t\t</item>\n\t\t\t\t\t</topics>\n\t\t\t\t\t<title type=\"str\">b</title>\n\t\t\t\t\t<makers type=\"list\">\n\t\t\t\t\t\t<item type=\"str\">task-done</item>\n\t\t\t\t\t\t<item type=\"str\">flag-red</item>\n\t\t\t\t\t\t<item type=\"str\">people-red</item>\n\t\t\t\t\t\t<item type=\"str\">arrow-up</item>\n\t\t\t\t\t</makers>\n\t\t\t\t</item>\n\t\t\t\t<item type=\"dict\">\n\t\t\t\t\t<topics type=\"list\">\n\t\t\t\t\t\t<item type=\"dict\">\n\t\t\t\t\t\t\t<title type=\"str\">a</title>\n\t\t\t\t\t\t</item>\n\t\t\t\t\t\t<item type=\"dict\">\n\t\t\t\t\t\t\t<title type=\"str\">a</title>\n\t\t\t\t\t\t</item>\n\t\t\t\t\t\t<item type=\"dict\">\n\t\t\t\t\t\t\t<link type=\"str\">[To another xmind topic!]</link>\n\t\t\t\t\t\t\t<title type=\"str\">a</title>\n\t\t\t\t\t\t</item>\n\t\t\t\t\t</topics>\n\t\t\t\t\t<title type=\"str\">c</title>\n\t\t\t\t</item>\n\t\t\t</topics>\n\t\t\t<title type=\"str\">test</title>\n\t\t\t<makers type=\"list\">\n\t\t\t\t<item type=\"str\">star-orange</item>\n\t\t\t</makers>\n\t\t</topic>\n\t\t<title type=\"str\">Sheet 1</title>\n\t</item>\n\t<item type=\"dict\">\n\t\t<structure type=\"str\">org.xmind.ui.map.unbalanced</structure>\n\t\t<topic type=\"dict\">\n\t\t\t<topics type=\"list\">\n\t\t\t\t<item type=\"dict\">\n\t\t\t\t\t<topics type=\"list\">\n\t\t\t\t\t\t<item type=\"dict\">\n\t\t\t\t\t\t\t<title type=\"str\">1</title>\n\t\t\t\t\t\t</item>\n\t\t\t\t\t\t<item type=\"dict\">\n\t\t\t\t\t\t\t<title type=\"str\">2</title>\n\t\t\t\t\t\t</item>\n\t\t\t\t\t\t<item type=\"dict\">\n\t\t\t\t\t\t\t<title type=\"str\">3</title>\n\t\t\t\t\t\t</item>\n\t\t\t\t\t</topics>\n\t\t\t\t\t<title type=\"str\">a</title>\n\t\t\t\t</item>\n\t\t\t\t<item type=\"dict\">\n\t\t\t\t\t<title type=\"str\">b</title>\n\t\t\t\t</item>\n\t\t\t</topics>\n\t\t\t<title type=\"str\">test2</title>\n\t\t</topic>\n\t\t<title type=\"str\">Sheet 2</title>\n\t</item>\n</root>\n"
  },
  {
    "path": "doc/example.yaml",
    "content": "- structure: org.xmind.ui.map.unbalanced\n  title: Sheet 1\n  topic:\n    makers:\n    - star-orange\n    title: test\n    topics:\n    - link: http://test.com\n      title: a\n      topics:\n      - title: e\n        topics:\n        - title: f\n          topics:\n          - title: g\n      - title: d\n        topics:\n        - title: null\n      - makers:\n        - symbol-plus\n        title: e\n        topics:\n        - note: this is a note\n          title: null\n      - title: null\n        topics:\n        - title: h\n          topics:\n          - comment:\n            - author: toby.qin\n              content: this a comments\n            title: null\n    - makers:\n      - task-done\n      - flag-red\n      - people-red\n      - arrow-up\n      title: b\n      topics:\n      - title: types\n        topics:\n        - comment:\n          - author: toby.qin\n            content: comment1\n          - author: toby.qin\n            content: comment2\n          - author: toby.qin\n            content: \"comment3\\r\\nnew line\"\n          title: comment\n        - note: 1 line note\n          title: note\n          topics:\n          - note: \"line1\\r\\nline2\"\n            title: note2\n          - note: \"note with style\\r\\nline with strike\"\n            title: note3\n        - labels:\n          - this is a  label\n          title: label\n        - title: attachment\n          topics:\n          - title: '[Attachment]test.txt'\n        - title: pic\n          topics:\n          - title: '[Image]'\n        - title: links\n          topics:\n          - link: http://test.com\n            title: to url\n          - link: file:////abc/ef\n            title: to file\n          - link: '[To another xmind topic!]'\n            title: to topic\n      - title: 测试\n        topics:\n        - note: 测试中文\n          title: note\n        - comment:\n          - author: toby.qin\n            content: 中文测试\n          title: comments\n      - title: </>{}[]*+-\n    - title: c\n      topics:\n      - title: a\n      - title: a\n      - link: '[To another xmind topic!]'\n        title: a\n- structure: org.xmind.ui.map.unbalanced\n  title: Sheet 2\n  topic:\n    title: test2\n    topics:\n    - title: a\n      topics:\n      - title: '1'\n      - title: '2'\n      - title: '3'\n    - title: b\n"
  },
  {
    "path": "doc/example_2026.json",
    "content": "[\n  {\n    \"title\": \"test\",\n    \"topic\": {\n      \"title\": \"test\",\n      \"topics\": [\n        {\n          \"title\": \"a\",\n          \"topics\": [\n            {\n              \"title\": \"e\",\n              \"topics\": [\n                {\n                  \"title\": \"f\",\n                  \"topics\": [\n                    {\n                      \"title\": \"g\"\n                    }\n                  ]\n                }\n              ]\n            },\n            {\n              \"title\": \"d\"\n            },\n            {\n              \"title\": \"e\",\n              \"topics\": [\n                {\n                  \"title\": \"\"\n                }\n              ]\n            },\n            {\n              \"title\": \"\",\n              \"topics\": [\n                {\n                  \"title\": \"h\",\n                  \"topics\": [\n                    {\n                      \"title\": \"\"\n                    }\n                  ]\n                }\n              ]\n            }\n          ]\n        },\n        {\n          \"title\": \"b\",\n          \"topics\": [\n            {\n              \"title\": \"types\",\n              \"topics\": [\n                {\n                  \"title\": \"comment\"\n                },\n                {\n                  \"title\": \"note\",\n                  \"note\": \"hello\",\n                  \"topics\": [\n                    {\n                      \"title\": \"note2\",\n                      \"note\": \"hello\"\n                    },\n                    {\n                      \"title\": \"note3\",\n                      \"note\": \"hello\"\n                    }\n                  ]\n                },\n                {\n                  \"title\": \"label\",\n                  \"labels\": [\n                    \"this\",\n                    \"that\",\n                    \"then\"\n                  ]\n                },\n                {\n                  \"title\": \"attachment\",\n                  \"topics\": [\n                    {\n                      \"title\": \"test.txt\"\n                    }\n                  ]\n                },\n                {\n                  \"title\": \"marker\",\n                  \"makers\": [\n                    \"tag-orange\",\n                    \"priority-2\",\n                    \"task-oct\",\n                    \"flag-orange\",\n                    \"star-orange\",\n                    \"people-orange\",\n                    \"c_symbol_like\"\n                  ]\n                },\n                {\n                  \"title\": \"pic\",\n                  \"topics\": [\n                    {\n                      \"title\": \"0.jpeg\",\n                      \"image\": {\n                        \"src\": \"xap:resources/9afc60beae59d849e741ae11d19d638a7b89090ee6c7b772159db271142bcee6.jpeg\"\n                      }\n                    }\n                  ]\n                },\n                {\n                  \"title\": \"sticker1\",\n                  \"image\": {\n                    \"src\": \"xap:resources/dd46d29fc64e6eb4e2aac3f78ab162b7177ae0608be431ebf261cad179b2fdad.svg\"\n                  }\n                },\n                {\n                  \"title\": \"sticker2\",\n                  \"image\": {\n                    \"src\": \"xap:resources/129b1b21c5946e3f1692f39dcd7d18e0241c66c0b92959cbdbf414a2d40defbd.svg\"\n                  }\n                },\n                {\n                  \"title\": \"link\",\n                  \"topics\": [\n                    {\n                      \"title\": \"to url\",\n                      \"link\": \"http://github.com\"\n                    },\n                    {\n                      \"title\": \"to file\"\n                    },\n                    {\n                      \"title\": \"to topic\"\n                    }\n                  ]\n                },\n                {\n                  \"title\": \"测试\",\n                  \"topics\": [\n                    {\n                      \"title\": \" note\"\n                    },\n                    {\n                      \"title\": \"comment\"\n                    }\n                  ]\n                },\n                {\n                  \"title\": \"</>{}[]*+-\"\n                }\n              ]\n            }\n          ]\n        },\n        {\n          \"title\": \"c\",\n          \"topics\": [\n            {\n              \"title\": \"a\"\n            },\n            {\n              \"title\": \"a\"\n            },\n            {\n              \"title\": \"a\"\n            }\n          ]\n        }\n      ]\n    },\n    \"structure\": \"org.xmind.ui.map.clockwise\"\n  }\n]"
  },
  {
    "path": "doc/example_2026.yaml",
    "content": "- structure: org.xmind.ui.map.clockwise\n  title: test\n  topic:\n    title: test\n    topics:\n    - title: a\n      topics:\n      - title: e\n        topics:\n        - title: f\n          topics:\n          - title: g\n      - title: d\n      - title: e\n        topics:\n        - title: ''\n      - title: ''\n        topics:\n        - title: h\n          topics:\n          - title: ''\n    - title: b\n      topics:\n      - title: types\n        topics:\n        - title: comment\n        - note: hello\n          title: note\n          topics:\n          - note: hello\n            title: note2\n          - note: hello\n            title: note3\n        - labels:\n          - this\n          - that\n          - then\n          title: label\n        - title: attachment\n          topics:\n          - title: test.txt\n        - makers:\n          - tag-orange\n          - priority-2\n          - task-oct\n          - flag-orange\n          - star-orange\n          - people-orange\n          - c_symbol_like\n          title: marker\n        - title: pic\n          topics:\n          - image:\n              src: xap:resources/9afc60beae59d849e741ae11d19d638a7b89090ee6c7b772159db271142bcee6.jpeg\n            title: 0.jpeg\n        - image:\n            src: xap:resources/dd46d29fc64e6eb4e2aac3f78ab162b7177ae0608be431ebf261cad179b2fdad.svg\n          title: sticker1\n        - image:\n            src: xap:resources/129b1b21c5946e3f1692f39dcd7d18e0241c66c0b92959cbdbf414a2d40defbd.svg\n          title: sticker2\n        - title: link\n          topics:\n          - link: http://github.com\n            title: to url\n          - title: to file\n          - title: to topic\n        - title: 测试\n          topics:\n          - title: ' note'\n          - title: comment\n        - title: </>{}[]*+-\n    - title: c\n      topics:\n      - title: a\n      - title: a\n      - title: a\n"
  },
  {
    "path": "doc/example_2026_with_id.json",
    "content": "[\n  {\n    \"title\": \"test\",\n    \"topic\": {\n      \"title\": \"test\",\n      \"topics\": [\n        {\n          \"title\": \"a\",\n          \"topics\": [\n            {\n              \"title\": \"e\",\n              \"topics\": [\n                {\n                  \"title\": \"f\",\n                  \"topics\": [\n                    {\n                      \"title\": \"g\",\n                      \"id\": \"d139621e-fcfd-47d9-8c88-baee2520c9d2\"\n                    }\n                  ],\n                  \"id\": \"50e70127-fd85-4f3d-8499-607cf40ba2f6\"\n                }\n              ],\n              \"id\": \"8bd84d0e-2b5c-4909-99a8-c86602aecb97\"\n            },\n            {\n              \"title\": \"d\",\n              \"id\": \"ae1b8692-74a8-4b31-8a6d-53c1b9081b37\"\n            },\n            {\n              \"title\": \"e\",\n              \"topics\": [\n                {\n                  \"title\": \"\",\n                  \"id\": \"19689765-cc6f-4f2d-a8f3-29ff975f483e\"\n                }\n              ],\n              \"id\": \"d48acb35-acbd-4355-84ef-3bb88cdd3df0\"\n            },\n            {\n              \"title\": \"\",\n              \"topics\": [\n                {\n                  \"title\": \"h\",\n                  \"topics\": [\n                    {\n                      \"title\": \"\",\n                      \"id\": \"dbd07b55-4cdb-4bb9-88d7-18eec584de9e\"\n                    }\n                  ],\n                  \"id\": \"76b89635-c5b2-4050-a709-946eb722887b\"\n                }\n              ],\n              \"id\": \"b5dadecb-9115-4e36-a440-f3adf707e445\"\n            }\n          ],\n          \"id\": \"662b6d569ce8f4377df40a693f\"\n        },\n        {\n          \"title\": \"b\",\n          \"topics\": [\n            {\n              \"title\": \"types\",\n              \"topics\": [\n                {\n                  \"title\": \"comment\",\n                  \"id\": \"7e18864a-02cd-4cd0-a826-4ea28a095ac0\"\n                },\n                {\n                  \"title\": \"note\",\n                  \"note\": \"hello\",\n                  \"topics\": [\n                    {\n                      \"title\": \"note2\",\n                      \"note\": \"hello\",\n                      \"id\": \"68429427-5328-470c-99f8-b851b5174495\"\n                    },\n                    {\n                      \"title\": \"note3\",\n                      \"note\": \"hello\",\n                      \"id\": \"88c0585a-c331-4893-b1e6-489afae8a8d2\"\n                    }\n                  ],\n                  \"id\": \"f83cd16a-0b67-49a4-a050-4822325bd9b0\"\n                },\n                {\n                  \"title\": \"label\",\n                  \"labels\": [\n                    \"this\",\n                    \"that\",\n                    \"then\"\n                  ],\n                  \"id\": \"1cf6a76f-a16f-47eb-b104-ac145a825bbf\"\n                },\n                {\n                  \"title\": \"attachment\",\n                  \"topics\": [\n                    {\n                      \"title\": \"test.txt\",\n                      \"id\": \"2b877785-e174-4bd8-b6f3-b386e8846658\"\n                    }\n                  ],\n                  \"id\": \"9e1820fa-c600-4db9-ab7e-5a53108c2399\"\n                },\n                {\n                  \"title\": \"marker\",\n                  \"makers\": [\n                    \"tag-orange\",\n                    \"priority-2\",\n                    \"task-oct\",\n                    \"flag-orange\",\n                    \"star-orange\",\n                    \"people-orange\",\n                    \"c_symbol_like\"\n                  ],\n                  \"id\": \"53e7347d-fcd1-4181-81d3-8fad62e9288c\"\n                },\n                {\n                  \"title\": \"pic\",\n                  \"topics\": [\n                    {\n                      \"title\": \"0.jpeg\",\n                      \"image\": {\n                        \"src\": \"xap:resources/9afc60beae59d849e741ae11d19d638a7b89090ee6c7b772159db271142bcee6.jpeg\"\n                      },\n                      \"id\": \"b0d151f3-52c2-4b62-8fed-6475fe0220db\"\n                    }\n                  ],\n                  \"id\": \"4977ba82-de19-47ab-9871-8fd95d6f1fca\"\n                },\n                {\n                  \"title\": \"sticker1\",\n                  \"image\": {\n                    \"src\": \"xap:resources/dd46d29fc64e6eb4e2aac3f78ab162b7177ae0608be431ebf261cad179b2fdad.svg\"\n                  },\n                  \"id\": \"fca4f88c-f65f-428e-a0ad-d73758f97e5a\"\n                },\n                {\n                  \"title\": \"sticker2\",\n                  \"image\": {\n                    \"src\": \"xap:resources/129b1b21c5946e3f1692f39dcd7d18e0241c66c0b92959cbdbf414a2d40defbd.svg\"\n                  },\n                  \"id\": \"8bc05dd2-9417-4087-9ccf-089bd64f81bb\"\n                },\n                {\n                  \"title\": \"link\",\n                  \"topics\": [\n                    {\n                      \"title\": \"to url\",\n                      \"link\": \"http://github.com\",\n                      \"id\": \"fe681a0b-665c-4da0-9e47-335b070591d7\"\n                    },\n                    {\n                      \"title\": \"to file\",\n                      \"id\": \"1b670faa-accb-4c7d-a6cb-5c9ac4af9834\"\n                    },\n                    {\n                      \"title\": \"to topic\",\n                      \"id\": \"e738e21f-ad7a-4e3f-bef8-7deccb6ce537\"\n                    }\n                  ],\n                  \"id\": \"68aea8cf-2f73-4727-9e01-0348ea1cda6e\"\n                },\n                {\n                  \"title\": \"测试\",\n                  \"topics\": [\n                    {\n                      \"title\": \" note\",\n                      \"id\": \"97e8e40f-0231-4f19-90df-b84049175c56\"\n                    },\n                    {\n                      \"title\": \"comment\",\n                      \"id\": \"a3082b40-7cc8-4d0e-a6f5-22797e5706c9\"\n                    }\n                  ],\n                  \"id\": \"5ee53dbc-8e85-4aed-8ccd-e9759f761392\"\n                },\n                {\n                  \"title\": \"</>{}[]*+-\",\n                  \"id\": \"02ff821b-6ff7-4ece-8a1f-e7f3f9cdf697\"\n                }\n              ],\n              \"id\": \"ff301ff5-2823-474c-9c73-dce071016a0e\"\n            }\n          ],\n          \"id\": \"86e396ce-f4b0-4b7f-9f6c-6af452212532\"\n        },\n        {\n          \"title\": \"c\",\n          \"topics\": [\n            {\n              \"title\": \"a\",\n              \"id\": \"84722f1f-8d30-48c2-b113-0f0f0a51a093\"\n            },\n            {\n              \"title\": \"a\",\n              \"id\": \"4178f058-7c9e-407e-b97f-1387aeaf3bd3\"\n            },\n            {\n              \"title\": \"a\",\n              \"id\": \"fa64feb7-d141-40ef-b905-ee8241d8cb9e\"\n            }\n          ],\n          \"id\": \"b802e9b4-5823-4959-aaf2-277960434259\"\n        }\n      ],\n      \"id\": \"d37854166e0a3adf36413a3c20\"\n    },\n    \"structure\": \"org.xmind.ui.map.clockwise\",\n    \"id\": \"4b48628d41abafc49e9119e2f4\"\n  }\n]"
  },
  {
    "path": "doc/example_with_id.json",
    "content": "[\n  {\n    \"title\": \"Sheet 1\",\n    \"topic\": {\n      \"title\": \"test\",\n      \"makers\": [\n        \"star-orange\"\n      ],\n      \"topics\": [\n        {\n          \"title\": \"a\",\n          \"link\": \"http://test.com\",\n          \"topics\": [\n            {\n              \"title\": \"e\",\n              \"topics\": [\n                {\n                  \"title\": \"f\",\n                  \"topics\": [\n                    {\n                      \"title\": \"g\",\n                      \"id\": \"19u3o14ss5ib73ckoiuo4c614m\"\n                    }\n                  ],\n                  \"id\": \"015ofcedrsice6eak3gqk3lj7v\"\n                }\n              ],\n              \"id\": \"04m9bbu1mrurjo5ssp5o7eharf\"\n            },\n            {\n              \"title\": \"d\",\n              \"topics\": [\n                {\n                  \"title\": null,\n                  \"id\": \"4hkgeo72lekalvffgj3n88cgk4\"\n                }\n              ],\n              \"id\": \"219qo6qe1lmqjihk8st81o42oi\"\n            },\n            {\n              \"title\": \"e\",\n              \"makers\": [\n                \"symbol-plus\"\n              ],\n              \"topics\": [\n                {\n                  \"title\": null,\n                  \"note\": \"this is a note\",\n                  \"id\": \"26o5l2ejqp08l515h14tr0qetm\"\n                }\n              ],\n              \"id\": \"5kmgtg6emhuk3da6p8gvl4ksnm\"\n            },\n            {\n              \"title\": null,\n              \"topics\": [\n                {\n                  \"title\": \"h\",\n                  \"topics\": [\n                    {\n                      \"title\": null,\n                      \"comment\": [\n                        {\n                          \"author\": \"toby.qin\",\n                          \"content\": \"this a comments\",\n                          \"id\": \"1p36uh37vablt0qpcehk4668te\"\n                        }\n                      ],\n                      \"id\": \"1p36uh37vablt0qpcehk4668te\"\n                    }\n                  ],\n                  \"id\": \"5l94qndjrpqdu76q35amhe1oh9\"\n                }\n              ],\n              \"id\": \"4a5aa28e149gnrlcl22f2d83t2\"\n            }\n          ],\n          \"id\": \"2raf1ghptbsc7hvvb65kib62bp\"\n        },\n        {\n          \"title\": \"b\",\n          \"makers\": [\n            \"task-done\",\n            \"flag-red\",\n            \"people-red\",\n            \"arrow-up\"\n          ],\n          \"topics\": [\n            {\n              \"title\": \"types\",\n              \"topics\": [\n                {\n                  \"title\": \"comment\",\n                  \"comment\": [\n                    {\n                      \"author\": \"toby.qin\",\n                      \"content\": \"comment1\",\n                      \"id\": \"0f7kql863ll2fdd66i91pn8h3p\"\n                    },\n                    {\n                      \"author\": \"toby.qin\",\n                      \"content\": \"comment2\",\n                      \"id\": \"0f7kql863ll2fdd66i91pn8h3p\"\n                    },\n                    {\n                      \"author\": \"toby.qin\",\n                      \"content\": \"comment3\\r\\nnew line\",\n                      \"id\": \"0f7kql863ll2fdd66i91pn8h3p\"\n                    }\n                  ],\n                  \"id\": \"0f7kql863ll2fdd66i91pn8h3p\"\n                },\n                {\n                  \"title\": \"note\",\n                  \"note\": \"1 line note\",\n                  \"topics\": [\n                    {\n                      \"title\": \"note2\",\n                      \"note\": \"line1\\r\\nline2\",\n                      \"id\": \"09bb0va5jm47h4qa4jm2p1u2gk\"\n                    },\n                    {\n                      \"title\": \"note3\",\n                      \"note\": \"note with style\\r\\nline with strike\",\n                      \"id\": \"0o9pe3lfq50ojsbhmsevc7laav\"\n                    }\n                  ],\n                  \"id\": \"041j66ar1uslmdh1ee2esiqjus\"\n                },\n                {\n                  \"title\": \"label\",\n                  \"labels\": [\n                    \"this is a  label\"\n                  ],\n                  \"id\": \"01k5217iqhgi53qijfktbg5rep\"\n                },\n                {\n                  \"title\": \"attachment\",\n                  \"topics\": [\n                    {\n                      \"title\": \"[Attachment]test.txt\",\n                      \"id\": \"1e37na1d9junmpol5n75mv240i\"\n                    }\n                  ],\n                  \"id\": \"4lnpmvpofafvqalvemhd45ufoo\"\n                },\n                {\n                  \"title\": \"pic\",\n                  \"topics\": [\n                    {\n                      \"title\": \"[Image]\",\n                      \"id\": \"6pk67puq3vu9n7u8tta4rfgmae\"\n                    }\n                  ],\n                  \"id\": \"0j2vmutou9c0ophg25fgu2gt2f\"\n                },\n                {\n                  \"title\": \"links\",\n                  \"topics\": [\n                    {\n                      \"title\": \"to url\",\n                      \"link\": \"http://test.com\",\n                      \"id\": \"6n690hf3cot3crbj9e3kh0f60k\"\n                    },\n                    {\n                      \"title\": \"to file\",\n                      \"link\": \"file:////abc/ef\",\n                      \"id\": \"56m7ffgbla4nmj6dp2pqqcj113\"\n                    },\n                    {\n                      \"title\": \"to topic\",\n                      \"link\": \"[To another xmind topic!]\",\n                      \"id\": \"5roos0gmob6ndc1vpb5btka8ne\"\n                    }\n                  ],\n                  \"id\": \"29s8gjuk3cp0cmam9pqhtl2l5t\"\n                }\n              ],\n              \"id\": \"7g9vfb0ehndv6no9qtng3pt8tt\"\n            },\n            {\n              \"title\": \"\\u6d4b\\u8bd5\",\n              \"topics\": [\n                {\n                  \"title\": \"note\",\n                  \"note\": \"\\u6d4b\\u8bd5\\u4e2d\\u6587\",\n                  \"id\": \"107n29rkvobbaf0qk8ibgeifiq\"\n                },\n                {\n                  \"title\": \"comments\",\n                  \"comment\": [\n                    {\n                      \"author\": \"toby.qin\",\n                      \"content\": \"\\u4e2d\\u6587\\u6d4b\\u8bd5\",\n                      \"id\": \"3nt5tccmen04cjgfth7ehf8fqb\"\n                    }\n                  ],\n                  \"id\": \"3nt5tccmen04cjgfth7ehf8fqb\"\n                }\n              ],\n              \"id\": \"67mpm88oa240d004l7acfr3m77\"\n            },\n            {\n              \"title\": \"</>{}[]*+-\",\n              \"id\": \"3ib375f8hmg0abviusakg856sq\"\n            }\n          ],\n          \"id\": \"0l01he3lh6r31omo23if7f486l\"\n        },\n        {\n          \"title\": \"c\",\n          \"topics\": [\n            {\n              \"title\": \"a\",\n              \"id\": \"2djipd1uflks01smve6nl2tb1n\"\n            },\n            {\n              \"title\": \"a\",\n              \"id\": \"4bjb1tgv0g2f3nfaeieshrdeas\"\n            },\n            {\n              \"title\": \"a\",\n              \"link\": \"[To another xmind topic!]\",\n              \"id\": \"2iblin5mer66nq9omm98htnmdm\"\n            }\n          ],\n          \"id\": \"1jtiomk7kgh8n2lv99qgt7gi85\"\n        }\n      ],\n      \"id\": \"4daq1j6fik7noda59olakoeu74\"\n    },\n    \"structure\": \"org.xmind.ui.map.unbalanced\",\n    \"id\": \"7c3c1opth8b21r6en8b8bj4aah\"\n  },\n  {\n    \"title\": \"Sheet 2\",\n    \"topic\": {\n      \"title\": \"test2\",\n      \"topics\": [\n        {\n          \"title\": \"a\",\n          \"topics\": [\n            {\n              \"title\": \"1\",\n              \"id\": \"2d9q5c8nsl0eh8ftroipptgan7\"\n            },\n            {\n              \"title\": \"2\",\n              \"id\": \"4kpslik9lmsb5qrhtj5n5kfprp\"\n            },\n            {\n              \"title\": \"3\",\n              \"id\": \"02ngl6ho0tv44u63hoinitotkj\"\n            }\n          ],\n          \"id\": \"7hqpdbkseqls512fac08vkff7m\"\n        },\n        {\n          \"title\": \"b\",\n          \"id\": \"1bndoi9nc63r4g3f2ude6rjbg2\"\n        }\n      ],\n      \"id\": \"4tmccl0gm2i50cploeik8jd9pc\"\n    },\n    \"structure\": \"org.xmind.ui.map.unbalanced\",\n    \"id\": \"0aqfgfibdj3enu5qmj45viis2q\"\n  }\n]"
  },
  {
    "path": "doc/example_zen.json",
    "content": "[\n  {\n    \"title\": \"Map 1\",\n    \"topic\": {\n      \"title\": \"Central Topic\",\n      \"image\": {\n        \"src\": \"xap:resources/6cca606c3f2028e5ee7794459510ce1fd8855e5e8af44c5af91a212bc7f71462.svg\",\n        \"type\": \"image\"\n      },\n      \"topics\": [\n        {\n          \"title\": \"topic 1\",\n          \"makers\": [\n            \"priority-1\"\n          ],\n          \"topics\": [\n            {\n              \"title\": \"note topic\",\n              \"note\": \"note info\\nnew line of note\"\n            },\n            {\n              \"title\": \"label topic\",\n              \"labels\": [\n                \"great\"\n              ],\n              \"topics\": [\n                {\n                  \"title\": \"formatted note\",\n                  \"note\": \"hello world\"\n                }\n              ]\n            },\n            {\n              \"title\": \"link topic\",\n              \"link\": \"http://www.link.com\"\n            },\n            {\n              \"title\": \"callout topic\",\n              \"callout\": [\n                \"Callout\"\n              ]\n            },\n            {\n              \"title\": \"makers topic\",\n              \"makers\": [\n                \"priority-2\",\n                \"smiley-smile\",\n                \"task-oct\",\n                \"flag-orange\"\n              ]\n            },\n            {\n              \"title\": \"sticker topic\",\n              \"image\": {\n                \"src\": \"xap:resources/82d831f17e8c6cad32ff1791060d24488fab8c64186b07894e38b3a35013f736.svg\",\n                \"type\": \"image\"\n              }\n            },\n            {\n              \"title\": \"image topic\",\n              \"image\": {\n                \"src\": \"xap:resources/40af36d11c22d4747e35dd2d82cfa2c0e6ed91a333fa77b18c8d2e675e7b9fa0.png\",\n                \"type\": \"image\"\n              }\n            },\n            {\n              \"title\": \"attachment topic\",\n              \"topics\": [\n                {\n                  \"title\": \"test.txt\",\n                  \"link\": \"xap:resources/e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855.txt\"\n                }\n              ]\n            }\n          ]\n        },\n        {\n          \"title\": \"topic 2\",\n          \"makers\": [\n            \"priority-2\"\n          ],\n          \"topics\": [\n            {\n              \"title\": \"123\"\n            },\n            {\n              \"title\": \"\",\n              \"topics\": [\n                {\n                  \"title\": \"topic after empty\"\n                }\n              ]\n            },\n            {\n              \"title\": \"\\u4e2d\\u6587\"\n            },\n            {\n              \"title\": \"<>{}*&^^&*^%@!\",\n              \"topics\": [\n                {\n                  \"title\": \"topic with special chars\"\n                }\n              ]\n            },\n            {\n              \"title\": \"formatted topic\"\n            }\n          ]\n        }\n      ]\n    },\n    \"structure\": \"org.xmind.ui.map.unbalanced\"\n  },\n  {\n    \"title\": \"Map 2\",\n    \"topic\": {\n      \"title\": \"another sheet\",\n      \"topics\": [\n        {\n          \"title\": \"Main Topic 1\"\n        },\n        {\n          \"title\": \"Main Topic 2\"\n        }\n      ]\n    },\n    \"structure\": \"org.xmind.ui.map.unbalanced\"\n  }\n]"
  },
  {
    "path": "doc/example_zen.yaml",
    "content": "- structure: org.xmind.ui.map.unbalanced\n  title: Map 1\n  topic:\n    image:\n      src: xap:resources/6cca606c3f2028e5ee7794459510ce1fd8855e5e8af44c5af91a212bc7f71462.svg\n      type: image\n    title: Central Topic\n    topics:\n    - makers:\n      - priority-1\n      title: topic 1\n      topics:\n      - note: 'note info\n\n          new line of note'\n        title: note topic\n      - labels:\n        - great\n        title: label topic\n        topics:\n        - note: hello world\n          title: formatted note\n      - link: http://www.link.com\n        title: link topic\n      - callout:\n        - Callout\n        title: callout topic\n      - makers:\n        - priority-2\n        - smiley-smile\n        - task-oct\n        - flag-orange\n        title: makers topic\n      - image:\n          src: xap:resources/82d831f17e8c6cad32ff1791060d24488fab8c64186b07894e38b3a35013f736.svg\n          type: image\n        title: sticker topic\n      - image:\n          src: xap:resources/40af36d11c22d4747e35dd2d82cfa2c0e6ed91a333fa77b18c8d2e675e7b9fa0.png\n          type: image\n        title: image topic\n      - title: attachment topic\n        topics:\n        - link: xap:resources/e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855.txt\n          title: test.txt\n    - makers:\n      - priority-2\n      title: topic 2\n      topics:\n      - title: '123'\n      - title: ''\n        topics:\n        - title: topic after empty\n      - title: 中文\n      - title: <>{}*&^^&*^%@!\n        topics:\n        - title: topic with special chars\n      - title: formatted topic\n- structure: org.xmind.ui.map.unbalanced\n  title: Map 2\n  topic:\n    title: another sheet\n    topics:\n    - title: Main Topic 1\n    - title: Main Topic 2\n"
  },
  {
    "path": "doc/example_zen_with_id.json",
    "content": "[\n  {\n    \"title\": \"Map 1\",\n    \"topic\": {\n      \"title\": \"Central Topic\",\n      \"image\": {\n        \"src\": \"xap:resources/6cca606c3f2028e5ee7794459510ce1fd8855e5e8af44c5af91a212bc7f71462.svg\",\n        \"type\": \"image\"\n      },\n      \"topics\": [\n        {\n          \"title\": \"topic 1\",\n          \"makers\": [\n            \"priority-1\"\n          ],\n          \"topics\": [\n            {\n              \"title\": \"note topic\",\n              \"note\": \"note info\\nnew line of note\",\n              \"id\": \"62406618-32b1-486d-82b4-57f319fa633a\"\n            },\n            {\n              \"title\": \"label topic\",\n              \"labels\": [\n                \"great\"\n              ],\n              \"topics\": [\n                {\n                  \"title\": \"formatted note\",\n                  \"note\": \"hello world\",\n                  \"id\": \"f5a46c76-3dc0-48a4-b93f-59a8ecfc3340\"\n                }\n              ],\n              \"id\": \"68405d18-e77d-4fb4-9db5-8bb003baa758\"\n            },\n            {\n              \"title\": \"link topic\",\n              \"link\": \"http://www.link.com\",\n              \"id\": \"b588cc21-4a33-4108-8a3d-4a066714581a\"\n            },\n            {\n              \"title\": \"callout topic\",\n              \"callout\": [\n                \"Callout\"\n              ],\n              \"id\": \"1a14cbd3-9ead-4867-af1e-9781d2fe8927\"\n            },\n            {\n              \"title\": \"makers topic\",\n              \"makers\": [\n                \"priority-2\",\n                \"smiley-smile\",\n                \"task-oct\",\n                \"flag-orange\"\n              ],\n              \"id\": \"44d2dea9-9d68-4237-b5c0-1d02632381d1\"\n            },\n            {\n              \"title\": \"sticker topic\",\n              \"image\": {\n                \"src\": \"xap:resources/82d831f17e8c6cad32ff1791060d24488fab8c64186b07894e38b3a35013f736.svg\",\n                \"type\": \"image\"\n              },\n              \"id\": \"6657af1e-4770-4ff4-9a09-66f546e4c720\"\n            },\n            {\n              \"title\": \"image topic\",\n              \"image\": {\n                \"src\": \"xap:resources/40af36d11c22d4747e35dd2d82cfa2c0e6ed91a333fa77b18c8d2e675e7b9fa0.png\",\n                \"type\": \"image\"\n              },\n              \"id\": \"a5ffd21b-2fda-4dd4-8a64-6490a9aaa46d\"\n            },\n            {\n              \"title\": \"attachment topic\",\n              \"topics\": [\n                {\n                  \"title\": \"test.txt\",\n                  \"link\": \"xap:resources/e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855.txt\",\n                  \"id\": \"2ba4c9fa-5bb4-4715-985f-d8316e1c6b55\"\n                }\n              ],\n              \"id\": \"2cac3535-1d74-44d9-9621-04708a134ac5\"\n            }\n          ],\n          \"id\": \"b58888b5ceebbf0e68dada0656\"\n        },\n        {\n          \"title\": \"topic 2\",\n          \"makers\": [\n            \"priority-2\"\n          ],\n          \"topics\": [\n            {\n              \"title\": \"123\",\n              \"id\": \"68bb7a52-1e41-4a29-b9b8-204b3d17ee5f\"\n            },\n            {\n              \"title\": \"\",\n              \"topics\": [\n                {\n                  \"title\": \"topic after empty\",\n                  \"id\": \"357e5088-f207-41c9-b920-01f4d955dcaa\"\n                }\n              ],\n              \"id\": \"810acef9-36a1-4580-bec0-7cce95661838\"\n            },\n            {\n              \"title\": \"\\u4e2d\\u6587\",\n              \"id\": \"fae3efe0-dbcc-46c9-975c-d60ca8fc6455\"\n            },\n            {\n              \"title\": \"<>{}*&^^&*^%@!\",\n              \"topics\": [\n                {\n                  \"title\": \"topic with special chars\",\n                  \"id\": \"f8942451-183b-4dc6-9816-bc4b9baa7686\"\n                }\n              ],\n              \"id\": \"6b9e69af-6e90-456b-b2d7-1fe564ec97ed\"\n            },\n            {\n              \"title\": \"formatted topic\",\n              \"id\": \"f0d31da3-59aa-4c68-b909-ab44a6dc76d7\"\n            }\n          ],\n          \"id\": \"193b56735e689ae86a01d91513\"\n        }\n      ],\n      \"id\": \"b9aa22deba98b3b20c7ac8aca2\"\n    },\n    \"structure\": \"org.xmind.ui.map.unbalanced\",\n    \"id\": \"1fb9723a328b06763d296b8fee\"\n  },\n  {\n    \"title\": \"Map 2\",\n    \"topic\": {\n      \"title\": \"another sheet\",\n      \"topics\": [\n        {\n          \"title\": \"Main Topic 1\",\n          \"id\": \"da88f9a5ad9a201fb6618a0d65\"\n        },\n        {\n          \"title\": \"Main Topic 2\",\n          \"id\": \"33284f5d0e89964530c77a7299\"\n        }\n      ],\n      \"id\": \"495938ff229b6b4f6ef178ea3f\"\n    },\n    \"structure\": \"org.xmind.ui.map.unbalanced\",\n    \"id\": \"f3e8391738b81b4beac919ce7c\"\n  }\n]"
  },
  {
    "path": "setup.py",
    "content": "\"\"\"\nDocumentation\n-------------\nxmindparser is a tool to help you convert xmind file to programmable data type, e.g. json, xml.\n\nDetail usage: https://github.com/tobyqin/xmindparser\n\n\"\"\"\nfrom codecs import open\nfrom os import path\n\nfrom setuptools import setup, find_packages\n\ncurrent_dir = path.abspath(path.dirname(__file__))\nlong_description = __doc__\n\nwith open(path.join(current_dir, \"CHANGELOG.md\"), encoding=\"utf-8\") as f:\n    long_description += \"\\n\" + f.read()\n\nclassifiers = [\"License :: OSI Approved :: MIT License\",\n               \"Topic :: Software Development\",\n               \"Topic :: Utilities\",\n               \"Operating System :: Microsoft :: Windows\",\n               \"Operating System :: MacOS :: MacOS X\"] + [\n                  (\"Programming Language :: Python :: %s\" % x) for x in \"3.9 3.10 3.11 3.12 3.13 3.14\".split()]\n\n\ndef command_line():\n    target = \"xmindparser.main:main\"\n    entry_points = []\n    entry_points.append(\"xmindparser=%s\" % target)\n    return entry_points\n\n\ndef main():\n    setup(\n        name=\"xmindparser\",\n        description=\"Convert xmind to programmable data types, support xmind pro and xmind zen file types.\",\n        keywords=\"xmind parser converter json xml markdown\",\n        long_description=long_description,\n        classifiers=classifiers,\n        version=\"1.2.2\",\n        author=\"Toby Qin\",\n        author_email=\"toby.qin@live.com\",\n        url=\"https://github.com/tobyqin/xmindparser\",\n        packages=find_packages(exclude=['tests', 'tests.*']),\n        package_data={},\n        install_requires=[],\n        entry_points={\"console_scripts\": command_line(), },\n        zip_safe=False\n    )\n\n\nif __name__ == \"__main__\":\n    main()\n"
  },
  {
    "path": "tests/__init__.py",
    "content": ""
  },
  {
    "path": "tests/test_xmindparser.py",
    "content": "from json import dumps, loads\nfrom os import chdir\nfrom os.path import join, dirname, exists\nfrom pathlib import Path\n\nimport xmindparser\nfrom xmindparser import *\nfrom xmindparser import apply_config\n\nchdir(dirname(dirname(__file__)))\nxmind_pro_file = join(dirname(__file__), \"xmind_pro.xmind\")\nxmind_zen_file = join(dirname(__file__), \"xmind_zen.xmind\")\nxmind_2026_file = join(dirname(__file__), \"xmind_2026.xmind\")\nexpected_json_pro = join(dirname(dirname(__file__)), \"doc/example.json\")\nexpected_json_pro_with_id = join(dirname(dirname(__file__)), \"doc/example_with_id.json\")\nexpected_json_zen = join(dirname(dirname(__file__)), \"doc/example_zen.json\")\nexpected_json_zen_with_id = join(\n    dirname(dirname(__file__)), \"doc/example_zen_with_id.json\"\n)\nexpected_json_2026 = join(dirname(dirname(__file__)), \"doc/example_2026.json\")\nexpected_json_2026_with_id = join(\n    dirname(dirname(__file__)), \"doc/example_2026_with_id.json\"\n)\nset_logger_level(logging.DEBUG)\n\n# Get logger reference\nlogger = xmindparser.logger\n\n\ndef load_json(f):\n    return loads(Path(f).read_text())\n\n\ndef test_xmind_to_dict_debug_pro():\n    config[\"showTopicId\"] = True\n    d = xmind_to_dict(xmind_pro_file)\n    logger.info(dumps(d))\n    assert load_json(expected_json_pro_with_id) == d\n\n\ndef test_xmind_to_dict_default_pro():\n    config[\"showTopicId\"] = False\n    d = xmind_to_dict(xmind_pro_file)\n    logger.info(dumps(d))\n    assert load_json(expected_json_pro) == d\n\n\ndef test_xmind_to_dict_debug_zen():\n    config[\"showTopicId\"] = True\n    d = xmind_to_dict(xmind_zen_file)\n    logger.info(dumps(d))\n    assert load_json(expected_json_zen_with_id) == d\n\n\ndef test_xmind_to_dict_default_zen():\n    config[\"showTopicId\"] = False\n    d = xmind_to_dict(xmind_zen_file)\n    logger.info(dumps(d))\n    assert load_json(expected_json_zen) == d\n\n\ndef convert_to_file(method, xmind_file, out_file):\n    if exists(out_file):\n        os.remove(out_file)\n\n    method(xmind_file)\n    assert os.path.exists(out_file)\n    os.remove(out_file)\n\n\ndef test_xmind_to_json():\n    convert_to_file(xmind_to_json, xmind_pro_file, \"tests/xmind_pro.json\")\n    convert_to_file(xmind_to_json, xmind_zen_file, \"tests/xmind_zen.json\")\n\n\ndef test_xmind_to_xml():\n    convert_to_file(xmind_to_xml, xmind_pro_file, \"tests/xmind_pro.xml\")\n    convert_to_file(xmind_to_json, xmind_zen_file, \"tests/xmind_zen.json\")\n\n\ndef test_xmind_to_markdown():\n    convert_to_file(xmind_to_markdown, xmind_pro_file, \"tests/xmind_pro.md\")\n    convert_to_file(xmind_to_markdown, xmind_zen_file, \"tests/xmind_zen.md\")\n\n\ndef test_xmind_to_yaml():\n    try:\n        import yaml\n    except ImportError:\n        import pytest\n\n        pytest.skip(\"pyyaml not installed\")\n\n    convert_to_file(xmind_to_yaml, xmind_pro_file, \"tests/xmind_pro.yaml\")\n    convert_to_file(xmind_to_yaml, xmind_zen_file, \"tests/xmind_zen.yaml\")\n\n\ndef test_xmind_to_yaml_content():\n    \"\"\"Test that YAML output matches the expected structure.\"\"\"\n    try:\n        import yaml\n    except ImportError:\n        import pytest\n\n        pytest.skip(\"pyyaml not installed\")\n\n    import io\n\n    # Test xmind pro\n    config[\"showTopicId\"] = False\n    d = xmind_to_dict(xmind_pro_file)\n    yaml_str = yaml.dump(d, allow_unicode=True, default_flow_style=False)\n    yaml_data = yaml.safe_load(yaml_str)\n    assert yaml_data == d\n\n    # Test xmind zen\n    d = xmind_to_dict(xmind_zen_file)\n    yaml_str = yaml.dump(d, allow_unicode=True, default_flow_style=False)\n    yaml_data = yaml.safe_load(yaml_str)\n    assert yaml_data == d\n\n    # Test xmind 2026\n    d = xmind_to_dict(xmind_2026_file)\n    yaml_str = yaml.dump(d, allow_unicode=True, default_flow_style=False)\n    yaml_data = yaml.safe_load(yaml_str)\n    assert yaml_data == d\n\n\ndef test_read_builtin_xmind_zen():\n    out = get_xmind_zen_builtin_json(xmind_zen_file)\n    assert out\n\n\ndef test_xmind_to_dict_debug_2026():\n    config[\"showTopicId\"] = True\n    d = xmind_to_dict(xmind_2026_file)\n    logger.info(dumps(d))\n    assert load_json(expected_json_2026_with_id) == d\n\n\ndef test_xmind_to_dict_default_2026():\n    config[\"showTopicId\"] = False\n    d = xmind_to_dict(xmind_2026_file)\n    logger.info(dumps(d))\n    assert load_json(expected_json_2026) == d\n\n\ndef test_config_hide_empty_value():\n    \"\"\"Test hideEmptyValue config option.\"\"\"\n    # Test with hideEmptyValue = True (default)\n    config[\"showTopicId\"] = False\n    config[\"hideEmptyValue\"] = True\n    d = xmind_to_dict(xmind_pro_file)\n\n    # Check that empty values are hidden\n    def check_no_empty_values(obj):\n        if isinstance(obj, dict):\n            for key, value in obj.items():\n                # title can be empty string\n                if key != \"title\":\n                    assert value, f\"Found empty value for key: {key}\"\n                if isinstance(value, (dict, list)):\n                    check_no_empty_values(value)\n        elif isinstance(obj, list):\n            for item in obj:\n                check_no_empty_values(item)\n\n    check_no_empty_values(d)\n\n    # Test with hideEmptyValue = False\n    config[\"hideEmptyValue\"] = False\n    d2 = xmind_to_dict(xmind_pro_file)\n\n    # The result with hideEmptyValue=False should have more or equal keys\n    def count_keys(obj):\n        count = 0\n        if isinstance(obj, dict):\n            count += len(obj)\n            for value in obj.values():\n                count += count_keys(value)\n        elif isinstance(obj, list):\n            for item in obj:\n                count += count_keys(item)\n        return count\n\n    # With hideEmptyValue=False, we should have at least as many keys\n    assert count_keys(d2) >= count_keys(d)\n\n    # Reset to default\n    config[\"hideEmptyValue\"] = True\n\n\ndef test_config_logging_format():\n    \"\"\"Test that logging config options work correctly.\"\"\"\n    import io\n\n    # Create a string buffer to capture log output\n    log_capture = io.StringIO()\n\n    # Configure custom logging format\n    config[\"logFormat\"] = \"CUSTOM: %(levelname)s - %(message)s\"\n    config[\"logLevel\"] = logging.INFO\n    apply_config()\n\n    # The handler should now be writing to stdout, but we need to check the handler's stream\n    # Get the current logger and its handler\n    current_logger = xmindparser.logger\n\n    # Temporarily replace the handler's stream\n    original_stream = xmindparser._console_handler.stream\n    xmindparser._console_handler.stream = log_capture\n\n    try:\n        # Generate some log output\n        current_logger.info(\"Test message\")\n\n        # Get the captured output\n        log_output = log_capture.getvalue()\n\n        # Verify custom format is applied\n        assert \"CUSTOM:\" in log_output, f\"Custom format not found in: {log_output}\"\n        assert \"INFO\" in log_output, f\"Log level not found in: {log_output}\"\n        assert \"Test message\" in log_output, f\"Message not found in: {log_output}\"\n\n    finally:\n        # Restore original stream\n        xmindparser._console_handler.stream = original_stream\n\n        # Reset to default config\n        config[\"logFormat\"] = \"%(asctime)s %(levelname)-8s: %(message)s\"\n        config[\"logLevel\"] = None\n        apply_config()\n\n\ndef test_config_logging_level():\n    \"\"\"Test that logLevel config option works correctly.\"\"\"\n    import io\n\n    original_stream = xmindparser._console_handler.stream\n\n    try:\n        # Test with WARNING level (should not show INFO)\n        config[\"logLevel\"] = logging.WARNING\n        apply_config()\n\n        log_capture = io.StringIO()\n        xmindparser._console_handler.stream = log_capture\n\n        xmindparser.logger.info(\"This should not appear\")\n        xmindparser.logger.warning(\"This should appear\")\n\n        log_output = log_capture.getvalue()\n        assert \"This should not appear\" not in log_output\n        assert \"This should appear\" in log_output\n\n        # Test with DEBUG level (should show everything)\n        config[\"logLevel\"] = logging.DEBUG\n        apply_config()\n\n        log_capture = io.StringIO()\n        xmindparser._console_handler.stream = log_capture\n\n        xmindparser.logger.debug(\"Debug message\")\n        xmindparser.logger.info(\"Info message\")\n\n        log_output = log_capture.getvalue()\n        assert \"Debug message\" in log_output\n        assert \"Info message\" in log_output\n\n    finally:\n        xmindparser._console_handler.stream = original_stream\n        config[\"logLevel\"] = None\n        apply_config()\n\n\ndef test_config_logging_name():\n    \"\"\"Test that logName config option works correctly.\"\"\"\n    # Test with custom log name\n    config[\"logName\"] = \"custom_xmind_logger\"\n    apply_config()\n\n    # Verify logger name is updated\n    current_logger = xmindparser.logger\n    assert current_logger.name == \"custom_xmind_logger\"\n\n    # Reset to default\n    config[\"logName\"] = \"xmindparser\"\n    apply_config()\n\n\ndef test_config_show_structure():\n    \"\"\"Test showStructure config option.\"\"\"\n    # Test with showStructure = True (default)\n    config[\"showStructure\"] = True\n    d = xmind_to_dict(xmind_zen_file)\n    # Check that structure is included\n    for sheet in d:\n        assert \"structure\" in sheet\n\n    # Test with showStructure = False\n    config[\"showStructure\"] = False\n    d2 = xmind_to_dict(xmind_zen_file)\n    # Check that structure is not included\n    for sheet in d2:\n        assert \"structure\" not in sheet\n\n    # Reset to default\n    config[\"showStructure\"] = True\n\n\ndef test_config_show_relationship():\n    \"\"\"Test showRelationship config option.\"\"\"\n    # Test with showRelationship = True\n    config[\"showRelationship\"] = True\n    d = xmind_to_dict(xmind_zen_file)\n    # Check if relationships key exists (may be empty if no relationships)\n    for sheet in d:\n        assert \"relationships\" in sheet\n\n    # Test with showRelationship = False (default)\n    config[\"showRelationship\"] = False\n    d2 = xmind_to_dict(xmind_zen_file)\n    # Check that relationships is not included\n    for sheet in d2:\n        assert \"relationships\" not in sheet\n"
  },
  {
    "path": "tests/xmind_2026.yaml",
    "content": "- structure: org.xmind.ui.map.clockwise\n  title: test\n  topic:\n    title: test\n    topics:\n    - title: a\n      topics:\n      - title: e\n        topics:\n        - title: f\n          topics:\n          - title: g\n      - title: d\n      - title: e\n        topics:\n        - title: ''\n      - title: ''\n        topics:\n        - title: h\n          topics:\n          - title: ''\n    - title: b\n      topics:\n      - title: types\n        topics:\n        - title: comment\n        - note: hello\n          title: note\n          topics:\n          - note: hello\n            title: note2\n          - note: hello\n            title: note3\n        - labels:\n          - this\n          - that\n          - then\n          title: label\n        - title: attachment\n          topics:\n          - title: test.txt\n        - makers:\n          - tag-orange\n          - priority-2\n          - task-oct\n          - flag-orange\n          - star-orange\n          - people-orange\n          - c_symbol_like\n          title: marker\n        - title: pic\n          topics:\n          - image:\n              src: xap:resources/9afc60beae59d849e741ae11d19d638a7b89090ee6c7b772159db271142bcee6.jpeg\n            title: 0.jpeg\n        - image:\n            src: xap:resources/dd46d29fc64e6eb4e2aac3f78ab162b7177ae0608be431ebf261cad179b2fdad.svg\n          title: sticker1\n        - image:\n            src: xap:resources/129b1b21c5946e3f1692f39dcd7d18e0241c66c0b92959cbdbf414a2d40defbd.svg\n          title: sticker2\n        - title: link\n          topics:\n          - link: http://github.com\n            title: to url\n          - title: to file\n          - title: to topic\n        - title: 测试\n          topics:\n          - title: ' note'\n          - title: comment\n        - title: </>{}[]*+-\n    - title: c\n      topics:\n      - title: a\n      - title: a\n      - title: a\n"
  },
  {
    "path": "xmindparser/__init__.py",
    "content": "\"\"\"\nParse xmind to programmable data types.\n\"\"\"\n\nimport json\nimport logging\nimport os\nimport sys\nfrom zipfile import ZipFile\n\ntry:\n    import yaml\nexcept ImportError:\n    yaml = None\n\nconfig = {\n    \"logName\": __name__,\n    \"logLevel\": None,\n    \"logFormat\": \"%(asctime)s %(levelname)-8s: %(message)s\",\n    \"showTopicId\": False,\n    \"hideEmptyValue\": True,\n    \"showRelationship\": False,\n    \"showStructure\": True,\n}\n\ncache = {}\n\n# Initialize logger with default config\nlogger = None\n_console_handler = None\n\n\ndef _init_logger():\n    \"\"\"Initialize or reinitialize the logger based on current config.\"\"\"\n    global logger, _console_handler\n\n    log_name = config[\"logName\"] or __name__\n    log_level = config[\"logLevel\"] or logging.WARNING\n    log_fmt = config[\"logFormat\"] or \"%(asctime)s %(levelname)-8s: %(message)s\"\n\n    # Remove existing handler if present\n    if logger and _console_handler:\n        logger.removeHandler(_console_handler)\n\n    # Create or get logger\n    logger = logging.getLogger(log_name)\n    logger.setLevel(log_level)\n\n    # Create new console handler with current format\n    _console_handler = logging.StreamHandler(sys.stdout)\n    _console_handler.setFormatter(logging.Formatter(log_fmt))\n    logger.addHandler(_console_handler)\n\n    return logger\n\n\ndef set_logger_level(new_level):\n    \"\"\"Set logger level.\"\"\"\n    if logger:\n        logger.setLevel(new_level)\n\n\ndef apply_config():\n    \"\"\"Apply current config settings, reinitializing logger if needed.\"\"\"\n    _init_logger()\n\n\n# Initialize logger on module import\n_init_logger()\n\n\ndef is_xmind_zen(file_path):\n    \"\"\"Determine if this is a xmind zen file type.\"\"\"\n    with ZipFile(file_path) as xmind:\n        return \"content.json\" in xmind.namelist()\n\n\ndef get_xmind_zen_builtin_json(file_path):\n    \"\"\"Read internal content.json from xmind zen file.\"\"\"\n    name = \"content.json\"\n    with ZipFile(file_path) as xmind:\n        if name in xmind.namelist():\n            content = xmind.open(name).read().decode(\"utf-8\")\n            return json.loads(content)\n\n        raise AssertionError(\"Not a xmind zen file type!\")\n\n\ndef _get_out_file_name(xmind_file, suffix):\n    assert isinstance(xmind_file, str) and xmind_file.endswith(\".xmind\"), (\n        \"Invalid xmind file!\"\n    )\n    name = os.path.abspath(xmind_file[0:-5] + suffix)\n\n    return name\n\n\ndef xmind_to_dict(file_path):\n    \"\"\"Open and convert xmind to dict type.\"\"\"\n    if is_xmind_zen(file_path):\n        from .zenreader import open_xmind, get_sheets, sheet_to_dict\n    else:\n        from .xreader import open_xmind, get_sheets, sheet_to_dict\n\n    open_xmind(file_path)\n    data = []\n\n    for s in get_sheets():\n        data.append(sheet_to_dict(s))\n\n    return data\n\n\ndef xmind_to_file(file_path, file_type):\n    if file_type == \"json\":\n        return xmind_to_json(file_path)\n\n    elif file_type == \"xml\":\n        return xmind_to_xml(file_path)\n\n    elif file_type == \"markdown\" or file_type == \"md\":\n        return xmind_to_markdown(file_path)\n\n    elif file_type == \"yaml\" or file_type == \"yml\":\n        return xmind_to_yaml(file_path)\n\n    else:\n        raise ValueError(\"Not supported file type: {}\".format(file_type))\n\n\ndef xmind_to_json(file_path):\n    target = _get_out_file_name(file_path, \"json\")\n\n    with open(target, \"w\", encoding=\"utf8\") as f:\n        f.write(json.dumps(xmind_to_dict(file_path), indent=2, ensure_ascii=False))\n\n    return target\n\n\ndef xmind_to_xml(file_path):\n    try:\n        from dicttoxml import dicttoxml\n        from xml.dom.minidom import parseString\n\n        target = _get_out_file_name(file_path, \"xml\")\n        xml = dicttoxml(xmind_to_dict(file_path), custom_root=\"root\")\n        xml = parseString(xml.decode(\"utf8\")).toprettyxml(encoding=\"utf8\")\n\n        with open(target, \"w\", encoding=\"utf8\") as f:\n            f.write(xml.decode(\"utf8\"))\n\n        return target\n    except ImportError:\n        raise ImportError(\n            'Parse xmind to xml require \"dicttoxml\", try install via pip:\\n'\n            + \"> pip install dicttoxml\"\n        )\n\n\ndef xmind_to_markdown(file_path):\n    \"\"\"Convert xmind file to markdown format.\"\"\"\n    data = xmind_to_dict(file_path)\n    target = _get_out_file_name(file_path, \"md\")\n\n    markdown_lines = []\n\n    for sheet in data:\n        markdown_lines.extend(_sheet_to_markdown(sheet))\n        markdown_lines.append(\"\")  # Add empty line between sheets\n\n    with open(target, \"w\", encoding=\"utf8\") as f:\n        f.write(\"\\n\".join(markdown_lines))\n\n    return target\n\n\ndef xmind_to_yaml(file_path):\n    \"\"\"Convert xmind file to yaml format.\"\"\"\n    if yaml is None:\n        raise ImportError(\n            'Parse xmind to yaml require \"pyyaml\", try install via pip:\\n'\n            + \"> pip install pyyaml\"\n        )\n\n    data = xmind_to_dict(file_path)\n    target = _get_out_file_name(file_path, \"yaml\")\n\n    with open(target, \"w\", encoding=\"utf8\") as f:\n        yaml.dump(data, f, allow_unicode=True, default_flow_style=False)\n\n    return target\n\n\ndef _sheet_to_markdown(sheet):\n    \"\"\"Convert a sheet to markdown lines.\"\"\"\n    lines = []\n\n    # Sheet title as h1\n    title = sheet.get(\"title\", \"Untitled\")\n    lines.append(\"# {}\".format(title))\n    lines.append(\"\")\n\n    # Process root topic\n    topic = sheet.get(\"topic\", {})\n    if topic:\n        lines.extend(_topic_to_markdown(topic, level=2))\n\n    return lines\n\n\ndef _topic_to_markdown(topic, level=2):\n    \"\"\"Convert a topic and its children to markdown lines.\"\"\"\n    lines = []\n\n    # Topic title as heading\n    title = topic.get(\"title\", \"\")\n    if title:\n        heading = \"#\" * level\n        lines.append(\"{} {}\".format(heading, title))\n\n    # Add note if exists\n    note = topic.get(\"note\", \"\")\n    if note:\n        lines.append(\"\")\n        lines.append(\"> {}\".format(note.replace(\"\\n\", \"\\n> \")))\n\n    # Add labels if exists\n    labels = topic.get(\"labels\", [])\n    if labels:\n        lines.append(\"\")\n        lines.append(\"**Labels:** {}\".format(\", \".join(labels)))\n\n    # Add link if exists\n    link = topic.get(\"link\", \"\")\n    if link and not link.startswith(\"[\"):\n        lines.append(\"\")\n        lines.append(\"[Link]({})\".format(link))\n\n    # Add comments if exists\n    comments = topic.get(\"comment\", [])\n    if comments:\n        lines.append(\"\")\n        lines.append(\"**Comments:**\")\n        for comment in comments:\n            author = comment.get(\"author\", \"Unknown\")\n            content = comment.get(\"content\", \"\")\n            lines.append(\"- **{}**: {}\".format(author, content.replace(\"\\n\", \" \")))\n\n    # Process children topics\n    children = topic.get(\"topics\", [])\n    if children:\n        lines.append(\"\")\n        for child in children:\n            lines.extend(_topic_to_markdown(child, level + 1))\n\n    return lines\n"
  },
  {
    "path": "xmindparser/main.py",
    "content": "\"\"\"\nA tool to parse xmind file into programmable data types.\nCheck https://github.com/tobyqin/xmindparser to see supported types.\n\nUsage:\n xmindparser [path_to_xmind_file] -[type]\n\nExample:\n xmindparser C:\\\\tests\\\\my.xmind -json\n xmindparser C:\\\\tests\\\\my.xmind -xml\n xmindparser C:\\\\tests\\\\my.xmind -markdown\n\n\"\"\"\n\nimport sys\n\nfrom xmindparser import *\n\n\ndef main():\n    if len(sys.argv) == 3 and sys.argv[1].endswith('.xmind'):\n        xmind, out_types = sys.argv[1], sys.argv[2][1:]\n        out = xmind_to_file(xmind, out_types)\n        print('Generated: {}'.format(out))\n    else:\n        print(__doc__)\n\n\nif __name__ == '__main__':\n    main()\n"
  },
  {
    "path": "xmindparser/xreader.py",
    "content": "import re\nfrom xml.etree import ElementTree as ET\nfrom xml.etree.ElementTree import Element\nfrom zipfile import ZipFile\n\nfrom . import cache, config, logger\n\ncontent_xml = \"content.xml\"\ncomments_xml = \"comments.xml\"\n\n\ndef open_xmind(file_path):\n    \"\"\"open xmind as zip file and cache the content.\"\"\"\n    cache.clear()\n    with ZipFile(file_path) as xmind:\n        for f in xmind.namelist():\n            for key in [content_xml, comments_xml]:\n                if f == key:\n                    cache[key] = xmind.open(f).read().decode(\"utf-8\")\n\n\ndef get_sheets():\n    \"\"\"get all sheet as generator and yield.\"\"\"\n    tree = xmind_content_to_etree(cache[content_xml])\n    assert isinstance(tree, Element)\n\n    for sheet in tree.findall(\"sheet\"):\n        yield sheet\n\n\ndef sheet_to_dict(sheet):\n    \"\"\"convert a sheet to dict type.\"\"\"\n    topic = sheet.find(\"topic\")\n    result = {\n        \"title\": title_of(sheet),\n        \"topic\": node_to_dict(topic),\n        \"structure\": get_sheet_structure(sheet),\n    }\n\n    if config[\"showTopicId\"]:\n        result[\"id\"] = sheet.attrib[\"id\"]\n\n    if config[\"hideEmptyValue\"]:\n        result = {k: v for k, v in result.items() if v}\n\n    return result\n\n\ndef get_sheet_structure(sheet):\n    root_topic = sheet.find(\"topic\")\n    return root_topic.attrib.get(\"structure-class\", None)\n\n\ndef node_to_dict(node):\n    \"\"\"parse Element to dict data type.\"\"\"\n    child = children_topics_of(node)\n\n    d = {\n        \"title\": title_of(node),\n        \"comment\": comments_of(node),\n        \"note\": note_of(node),\n        \"makers\": maker_of(node),\n        \"labels\": labels_of(node),\n        \"link\": link_of(node),\n    }\n\n    if d[\"link\"]:\n        if d[\"link\"].startswith(\"xmind\"):\n            d[\"link\"] = \"[To another xmind topic!]\"\n\n        if d[\"link\"].startswith(\"xap:attachments\"):\n            del d[\"link\"]\n            d[\"title\"] = \"[Attachment]{0}\".format(d[\"title\"])\n\n    if child:\n        d[\"topics\"] = []\n        for c in child:\n            d[\"topics\"].append(node_to_dict(c))\n\n    if config[\"showTopicId\"]:\n        d[\"id\"] = id_of(node)\n\n    if config[\"hideEmptyValue\"]:\n        d = {k: v for k, v in d.items() if v or k == \"title\"}\n\n    return d\n\n\ndef xmind_content_to_etree(content):\n    # Remove the default namespace definition (xmlns=\"http://some/namespace\")\n    xml_content = re.sub(r'\\sxmlns=\"[^\"]+\"', \"\", content, count=1)\n\n    # Replace xml tag with namespace\n    xml_content = xml_content.replace(\"<xhtml:img\", \"<img\")\n\n    # Replace link attrib with namespace\n    xml_content = xml_content.replace(\"xlink:href\", \"href\")\n    return ET.fromstring(xml_content.encode(\"utf-8\"))\n\n\ndef xmind_xml_to_etree(xml_path):\n    with open(xml_path) as f:\n        content = f.read()\n        return xmind_content_to_etree(content)\n\n\ndef comments_of(node):\n    if cache.get(comments_xml, None):\n        node_id = node.attrib.get(\"id\", None)\n\n        if node_id:\n            xml_root = xmind_content_to_etree(cache[comments_xml])\n            result = []\n\n            for c in xml_root.findall(\"comment\"):\n                if c.attrib[\"object-id\"] == node_id:\n                    i = {\n                        \"author\": c.attrib[\"author\"],\n                        \"content\": c.find(\"content\").text,\n                    }\n\n                    if config[\"showTopicId\"]:\n                        i[\"id\"] = c.attrib[\"object-id\"]\n\n                    result.append(i)\n\n            return result if result else None\n\n\ndef id_of(node):\n    return node.attrib.get(\"id\", None)\n\n\ndef image_of(node):\n    img = node.find(\"img\")\n\n    if img is not None:\n        return \"[Image]\"\n\n\ndef link_of(node):\n    return node.attrib.get(\"href\", None)\n\n\ndef title_of(node):\n    if image_of(node):\n        return image_of(node)\n\n    title = node.find(\"title\")\n\n    if title is not None:\n        return title.text\n\n\ndef labels_of(node):\n    label_node = node.find(\"labels\")\n\n    if label_node is not None:\n        labels = []\n        for _ in label_node.findall(\"label\"):\n            labels.append(_.text)\n\n        return labels if labels else None\n\n\ndef note_of(node):\n    note_node = node.find(\"notes\")\n\n    if note_node is not None:\n        note = note_node.find(\"plain\").text\n        return note.strip()\n\n\ndef debug_node(node, comments):\n    s = ET.tostring(node)\n    logger.debug(\"{}: {}\".format(comments, s))\n    return s\n\n\ndef maker_of(topic_node):\n    maker_node = topic_node.find(\"marker-refs\")\n    if maker_node is not None:\n        makers = []\n        for maker in maker_node:\n            makers.append(maker.attrib[\"marker-id\"])\n\n        return makers\n\n\ndef children_topics_of(topic_node):\n    children = topic_node.find(\"children\")\n\n    if children is not None:\n        return children.find('./topics[@type=\"attached\"]')\n"
  },
  {
    "path": "xmindparser/zenreader.py",
    "content": "import json\nfrom zipfile import ZipFile\n\nfrom . import cache, config\n\ncontent_json = \"content.json\"\n\n\ndef open_xmind(file_path):\n    \"\"\"open xmind as zip file and cache the content.\"\"\"\n    cache.clear()\n    with ZipFile(file_path) as xmind:\n        for f in xmind.namelist():\n            for key in [content_json]:\n                if f == key:\n                    cache[key] = xmind.open(f).read().decode(\"utf-8\")\n\n\ndef get_sheets():\n    \"\"\"get all sheet as generator and yield.\"\"\"\n    for sheet in json.loads(cache[content_json]):\n        yield sheet\n\n\ndef sheet_to_dict(sheet):\n    \"\"\"convert a sheet to dict type.\"\"\"\n    topic = sheet[\"rootTopic\"]\n    result = {\"title\": sheet[\"title\"], \"topic\": node_to_dict(topic)}\n\n    if config[\"showTopicId\"]:\n        result[\"id\"] = sheet[\"id\"]\n\n    if config[\"showStructure\"]:\n        result[\"structure\"] = get_sheet_structure(sheet)\n\n    if config[\"hideEmptyValue\"]:\n        result = {k: v for k, v in result.items() if v}\n\n    if config[\"showRelationship\"]:\n        result[\"relationships\"] = get_sheet_relationship(sheet)\n\n    return result\n\n\ndef get_sheet_structure(sheet):\n    root_topic = sheet[\"rootTopic\"]\n    return root_topic.get(\"structureClass\", None)\n\n\ndef get_sheet_relationship(sheet):\n    if sheet.get(\"relationships\", None) is None:\n        return []\n    relationship = sheet[\"relationships\"]\n    for r in relationship:\n        if r.get(\"controlPoints\", None) is not None:\n            del [r[\"controlPoints\"]]\n        if r.get(\"titleUnedited\", None) is not None:\n            del [r[\"titleUnedited\"]]\n        if not config[\"showTopicId\"]:\n            del [r[\"id\"]]\n    return relationship\n\n\ndef node_to_dict(node):\n    \"\"\"parse Element to dict data type.\"\"\"\n    child = children_topics_of(node)\n\n    d = {\n        \"title\": node.get(\"title\", \"\"),\n        \"note\": note_of(node),\n        \"makers\": maker_of(node),\n        \"labels\": labels_of(node),\n        \"link\": link_of(node),\n        \"image\": image_of(node),\n        \"callout\": callout_of(node),\n    }\n\n    if d[\"link\"]:\n        \"\"\"if d['link'].startswith('xmind'):\n            d['link'] = '[To another xmind topic!]\"\"\" \"\"\n\n        if d[\"link\"].startswith(\"xap:attachments\"):\n            del d[\"link\"]\n            d[\"title\"] = \"[Attachment]{0}\".format(d[\"title\"])\n\n    if child:\n        d[\"topics\"] = []\n        for c in child:\n            d[\"topics\"].append(node_to_dict(c))\n\n    if config[\"showTopicId\"]:\n        d[\"id\"] = node[\"id\"]\n\n    if config[\"hideEmptyValue\"]:\n        d = {k: v for k, v in d.items() if v or k == \"title\"}\n\n    return d\n\n\ndef children_topics_of(topic_node):\n    children = topic_node.get(\"children\", None)\n\n    if children:\n        return children.get(\"attached\", None)\n\n\ndef link_of(node):\n    return node.get(\"href\", None)\n\n\ndef image_of(node):\n    return node.get(\"image\", None)\n\n\ndef labels_of(node):\n    return node.get(\"labels\", None)\n\n\ndef note_of(node):\n    note_node = node.get(\"notes\", None)\n\n    if note_node:\n        note = note_node.get(\"plain\", None)\n        if note:\n            return note.get(\"content\", \"\").strip()\n\n\ndef maker_of(topic_node):\n    maker_node = topic_node.get(\"markers\", None)\n    if maker_node is not None:\n        makers = []\n        for maker in maker_node:\n            makers.append(maker.get(\"markerId\", None))\n\n        return makers\n\n\ndef callout_of(topic_node):\n    callout = topic_node.get(\"children\", None)\n    if callout:\n        callout = callout.get(\"callout\", None)\n        if callout:\n            return [x[\"title\"] for x in callout]\n"
  }
]