├── .appveyor.yml ├── .gitignore ├── LICENSE ├── Nuget.Config ├── README.md ├── XMindAPI.Tests ├── XMindAPI.Tests.csproj ├── XMindConfigurationLoaderTest.cs ├── XMindConfigurationTest.cs ├── XMindFileBuilderTest.cs ├── XMindFileWriterTest.cs ├── XMindSheetTest.cs ├── XMindTopicTest.cs └── XMindWorkBookTest.cs ├── XMindAPI ├── Configuration │ ├── XMindConfiguration.cs │ ├── XMindConfigurationExtensions.cs │ └── XMindConfigurationLoader.cs ├── Core │ ├── AbstractWorkbook.cs │ ├── Builders │ │ ├── IXMindDocumentBuilder.cs │ │ ├── XMindDocumentBuilder.cs │ │ └── XMindFileDocumentBuilder.cs │ ├── Core.cs │ ├── DOM │ │ ├── DOMConstants.cs │ │ ├── DOMUtils.cs │ │ ├── IDKey.cs │ │ └── NodeAdaptableRegistry.cs │ ├── IAdaptable.cs │ ├── IHyperlinked.cs │ ├── IIdFactory.cs │ ├── IIdentifiable.cs │ ├── ILabeled.cs │ ├── INodeAdaptableFactory.cs │ ├── IPositioned.cs │ ├── IRelationship.cs │ ├── IRelationshipEnd.cs │ ├── ISheet.cs │ ├── ISheetComponent.cs │ ├── ITitled.cs │ ├── ITopic.cs │ ├── ITopicComponent.cs │ ├── IWorkbook.cs │ ├── IWorkbookComponent.cs │ ├── IdFactory.cs │ └── TopicType.cs ├── Infrastructure │ ├── Logger.cs │ └── SmallGuidGenerator.cs ├── Models │ ├── XMindMarkers.cs │ ├── XMindRelationship.cs │ ├── XMindSheet.cs │ ├── XMindStructure.cs │ ├── XMindTopic.cs │ └── XMindWorkBook.cs ├── Properties │ └── AssemblyInfo.cs ├── Utils │ ├── XMindUtils.cs │ └── ZipStorer.cs ├── XMindAPI.csproj ├── XMindWriters │ ├── FileWriter │ │ ├── FileWriter.cs │ │ ├── FileWriterFactory.cs │ │ ├── FileWriterOutputConfig.cs │ │ └── FileWriterStandardOutput.cs │ ├── IXMindWriter.cs │ ├── IXMindWriterOutputConfig.cs │ ├── InMemoryWriter │ │ ├── InMemoryWriter.cs │ │ └── InMemoryWriterOutputConfig.cs │ ├── LoggerWriter │ │ ├── LoggerWriter.cs │ │ └── LoggerWriterOutputConfig.cs │ ├── XMindWriterConfiguration.cs │ └── XMindWriterContext.cs ├── icon.png └── xmindsettings.json ├── _config.yml ├── docs └── example_output1.png ├── examples ├── .gitignore └── simple │ ├── Program.cs │ ├── markers.xml │ ├── scripts │ └── unzip-xmind.ps1 │ ├── simple.csproj │ └── xmind-test │ ├── test.xmind │ └── tmp │ ├── content.xml │ ├── manifest.xml │ ├── meta.xml │ └── test.zip └── xmindapicsharp.sln /.appveyor.yml: -------------------------------------------------------------------------------- 1 | environment: 2 | major: 1 3 | minor: 0 4 | patch: 0 5 | 6 | version: $(major).$(minor).$(patch)+{branch}-{build} 7 | 8 | image: Visual Studio 2019 Preview 9 | configuration: Release 10 | 11 | dotnet_csproj: 12 | patch: true 13 | version: $(major).$(minor).$(patch) 14 | file: XMindAPI\XMindAPI.csproj 15 | 16 | before_build: 17 | - nuget restore 18 | 19 | build: 20 | publish_nuget: true 21 | publish_nuget_symbols: false 22 | 23 | deploy: 24 | - provider: NuGet 25 | api_key: 26 | secure: oKNCxXJqEmiJi69UmE12ZHtesKcRGXcrFfxatNzc2j9rd1g5zF9DAzKbKzBoMkhB 27 | skip_symbols: false 28 | on: 29 | branch: master 30 | - provider: NuGet 31 | server: https://nuget.pkg.github.com/NikiforovAll/index.json 32 | skip_symbols: true 33 | symbol_server: 34 | artifact: /.nupkg/ 35 | username: NikiforovAll 36 | api_key: 37 | secure: jXimydKwuBAN67ZfrEbLcYfbfnCu/S5Sbxj43fUoi3OXSjFzhQuibFlDNwowN0WY 38 | 39 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.rsuser 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Build results 17 | [Dd]ebug/ 18 | [Dd]ebugPublic/ 19 | [Rr]elease/ 20 | [Rr]eleases/ 21 | x64/ 22 | x86/ 23 | [Aa][Rr][Mm]/ 24 | [Aa][Rr][Mm]64/ 25 | bld/ 26 | [Bb]in/ 27 | [Oo]bj/ 28 | [Ll]og/ 29 | 30 | # Visual Studio 2015/2017 cache/options directory 31 | .vs/ 32 | # Uncomment if you have tasks that create the project's static files in wwwroot 33 | #wwwroot/ 34 | 35 | # Visual Studio 2017 auto generated files 36 | Generated\ Files/ 37 | 38 | # MSTest test Results 39 | [Tt]est[Rr]esult*/ 40 | [Bb]uild[Ll]og.* 41 | 42 | # NUNIT 43 | *.VisualState.xml 44 | TestResult.xml 45 | 46 | # Build Results of an ATL Project 47 | [Dd]ebugPS/ 48 | [Rr]eleasePS/ 49 | dlldata.c 50 | 51 | # Benchmark Results 52 | BenchmarkDotNet.Artifacts/ 53 | 54 | # .NET Core 55 | project.lock.json 56 | project.fragment.lock.json 57 | artifacts/ 58 | 59 | # StyleCop 60 | StyleCopReport.xml 61 | 62 | # Files built by Visual Studio 63 | *_i.c 64 | *_p.c 65 | *_h.h 66 | *.ilk 67 | *.meta 68 | *.obj 69 | *.iobj 70 | *.pch 71 | *.pdb 72 | *.ipdb 73 | *.pgc 74 | *.pgd 75 | *.rsp 76 | *.sbr 77 | *.tlb 78 | *.tli 79 | *.tlh 80 | *.tmp 81 | *.tmp_proj 82 | *_wpftmp.csproj 83 | *.log 84 | *.vspscc 85 | *.vssscc 86 | .builds 87 | *.pidb 88 | *.svclog 89 | *.scc 90 | 91 | # Chutzpah Test files 92 | _Chutzpah* 93 | 94 | # Visual C++ cache files 95 | ipch/ 96 | *.aps 97 | *.ncb 98 | *.opendb 99 | *.opensdf 100 | *.sdf 101 | *.cachefile 102 | *.VC.db 103 | *.VC.VC.opendb 104 | 105 | # Visual Studio profiler 106 | *.psess 107 | *.vsp 108 | *.vspx 109 | *.sap 110 | 111 | # Visual Studio Trace Files 112 | *.e2e 113 | 114 | # TFS 2012 Local Workspace 115 | $tf/ 116 | 117 | # Guidance Automation Toolkit 118 | *.gpState 119 | 120 | # ReSharper is a .NET coding add-in 121 | _ReSharper*/ 122 | *.[Rr]e[Ss]harper 123 | *.DotSettings.user 124 | 125 | # JustCode is a .NET coding add-in 126 | .JustCode 127 | 128 | # TeamCity is a build add-in 129 | _TeamCity* 130 | 131 | # DotCover is a Code Coverage Tool 132 | *.dotCover 133 | 134 | # AxoCover is a Code Coverage Tool 135 | .axoCover/* 136 | !.axoCover/settings.json 137 | 138 | # Visual Studio code coverage results 139 | *.coverage 140 | *.coveragexml 141 | 142 | # NCrunch 143 | _NCrunch_* 144 | .*crunch*.local.xml 145 | nCrunchTemp_* 146 | 147 | # MightyMoose 148 | *.mm.* 149 | AutoTest.Net/ 150 | 151 | # Web workbench (sass) 152 | .sass-cache/ 153 | 154 | # Installshield output folder 155 | [Ee]xpress/ 156 | 157 | # DocProject is a documentation generator add-in 158 | DocProject/buildhelp/ 159 | DocProject/Help/*.HxT 160 | DocProject/Help/*.HxC 161 | DocProject/Help/*.hhc 162 | DocProject/Help/*.hhk 163 | DocProject/Help/*.hhp 164 | DocProject/Help/Html2 165 | DocProject/Help/html 166 | 167 | # Click-Once directory 168 | publish/ 169 | 170 | # Publish Web Output 171 | *.[Pp]ublish.xml 172 | *.azurePubxml 173 | # Note: Comment the next line if you want to checkin your web deploy settings, 174 | # but database connection strings (with potential passwords) will be unencrypted 175 | *.pubxml 176 | *.publishproj 177 | 178 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 179 | # checkin your Azure Web App publish settings, but sensitive information contained 180 | # in these scripts will be unencrypted 181 | PublishScripts/ 182 | 183 | # NuGet Packages 184 | *.nupkg 185 | # The packages folder can be ignored because of Package Restore 186 | **/[Pp]ackages/* 187 | # except build/, which is used as an MSBuild target. 188 | !**/[Pp]ackages/build/ 189 | # Uncomment if necessary however generally it will be regenerated when needed 190 | #!**/[Pp]ackages/repositories.config 191 | # NuGet v3's project.json files produces more ignorable files 192 | *.nuget.props 193 | *.nuget.targets 194 | 195 | # Microsoft Azure Build Output 196 | csx/ 197 | *.build.csdef 198 | 199 | # Microsoft Azure Emulator 200 | ecf/ 201 | rcf/ 202 | 203 | # Windows Store app package directories and files 204 | AppPackages/ 205 | BundleArtifacts/ 206 | Package.StoreAssociation.xml 207 | _pkginfo.txt 208 | *.appx 209 | 210 | # Visual Studio cache files 211 | # files ending in .cache can be ignored 212 | *.[Cc]ache 213 | # but keep track of directories ending in .cache 214 | !?*.[Cc]ache/ 215 | 216 | # Others 217 | ClientBin/ 218 | ~$* 219 | *~ 220 | *.dbmdl 221 | *.dbproj.schemaview 222 | *.jfm 223 | *.pfx 224 | *.publishsettings 225 | orleans.codegen.cs 226 | 227 | # Including strong name files can present a security risk 228 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 229 | #*.snk 230 | 231 | # Since there are multiple workflows, uncomment next line to ignore bower_components 232 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 233 | #bower_components/ 234 | # ASP.NET Core default setup: bower directory is configured as wwwroot/lib/ and bower restore is true 235 | **/wwwroot/lib/ 236 | 237 | # RIA/Silverlight projects 238 | Generated_Code/ 239 | 240 | # Backup & report files from converting an old project file 241 | # to a newer Visual Studio version. Backup files are not needed, 242 | # because we have git ;-) 243 | _UpgradeReport_Files/ 244 | Backup*/ 245 | UpgradeLog*.XML 246 | UpgradeLog*.htm 247 | ServiceFabricBackup/ 248 | *.rptproj.bak 249 | 250 | # SQL Server files 251 | *.mdf 252 | *.ldf 253 | *.ndf 254 | 255 | # Business Intelligence projects 256 | *.rdl.data 257 | *.bim.layout 258 | *.bim_*.settings 259 | *.rptproj.rsuser 260 | 261 | # Microsoft Fakes 262 | FakesAssemblies/ 263 | 264 | # GhostDoc plugin setting file 265 | *.GhostDoc.xml 266 | 267 | # Node.js Tools for Visual Studio 268 | .ntvs_analysis.dat 269 | node_modules/ 270 | 271 | # Visual Studio 6 build log 272 | *.plg 273 | 274 | # Visual Studio 6 workspace options file 275 | *.opt 276 | 277 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 278 | *.vbw 279 | 280 | # Visual Studio LightSwitch build output 281 | **/*.HTMLClient/GeneratedArtifacts 282 | **/*.DesktopClient/GeneratedArtifacts 283 | **/*.DesktopClient/ModelManifest.xml 284 | **/*.Server/GeneratedArtifacts 285 | **/*.Server/ModelManifest.xml 286 | _Pvt_Extensions 287 | 288 | # Paket dependency manager 289 | .paket/paket.exe 290 | paket-files/ 291 | 292 | # FAKE - F# Make 293 | .fake/ 294 | 295 | # JetBrains Rider 296 | .idea/ 297 | *.sln.iml 298 | 299 | # CodeRush personal settings 300 | .cr/personal 301 | 302 | # Python Tools for Visual Studio (PTVS) 303 | __pycache__/ 304 | *.pyc 305 | 306 | # Cake - Uncomment if you are using it 307 | # tools/** 308 | # !tools/packages.config 309 | 310 | # Tabs Studio 311 | *.tss 312 | 313 | # Telerik's JustMock configuration file 314 | *.jmconfig 315 | 316 | # BizTalk build output 317 | *.btp.cs 318 | *.btm.cs 319 | *.odx.cs 320 | *.xsd.cs 321 | 322 | # OpenCover UI analysis results 323 | OpenCover/ 324 | 325 | # Azure Stream Analytics local run output 326 | ASALocalRun/ 327 | 328 | # MSBuild Binary and Structured Log 329 | *.binlog 330 | 331 | # NVidia Nsight GPU debugger configuration file 332 | *.nvuser 333 | 334 | # MFractors (Xamarin productivity tool) working folder 335 | .mfractor/ 336 | 337 | # Local History for Visual Studio 338 | .localhistory/ 339 | 340 | # BeatPulse healthcheck temp database 341 | healthchecksdb 342 | 343 | .vscode -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Alexey Nikiforov. All rights reserved. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE -------------------------------------------------------------------------------- /Nuget.Config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # XMindCSharp [![AppVeyor Badge](https://ci.appveyor.com/api/projects/status/xtmsp1i4ot8j6tbs?svg=true)](https://ci.appveyor.com/project/NikiforovAll/xmindcsharp/branch/master) [![NuGet Badge](https://buildstats.info/nuget/xmindcsharp)](https://www.nuget.org/packages/xmindcsharp/) [![XMindCSharp on fuget.org](https://www.fuget.org/packages/XMindCSharp/badge.svg)](https://www.fuget.org/packages/XMindCSharp) [![Conventional Commits](https://img.shields.io/badge/Conventional%20Commits-1.0.0-yellow.svg)](https://conventionalcommits.org) 2 | 3 | XMind API that allows to build .xmind files programmatically 4 | 5 | ## Install 6 | 7 | ```bash 8 | dotnet add package XMindCsharp --version X.Y.Z 9 | ``` 10 | 11 | ## Getting Started 12 | 13 | ```csharp 14 | var book = new XMindConfiguration() 15 | .WithFileWriter("./output", zip: true) 16 | .CreateWorkBook(workbookName: "test.xmind"); 17 | var sheet = book.CreateSheet(); 18 | book.AddSheet(sheet, 0); //replaced primary sheet 19 | ``` 20 | 21 | * Example application could be found at: [examples/simple](https://github.com/NikiforovAll/xmindcsharp/tree/master/examples/simple) 22 | * See [XMindAPI.Tests](https://github.com/NikiforovAll/xmindcsharp/tree/master/XMindAPI.Tests) for more details and examples. 23 | 24 | ### Example 25 | 26 | Here is what you can do with it: ![edu-scope-to-mindmap](docs/example_output1.png) 27 | 28 | Full source code could be found at [edu-scope-to-mindmap](https://github.com/NikiforovAll/edu-scope-to-mindmap). 29 | 30 | ## Running the tests 31 | 32 | Run following command from the root folder: 33 | 34 | ```bash 35 | dotnet test ./XMindAPI.Tests/ 36 | ``` 37 | 38 | ## Scope 39 | 40 | ## Contribute 41 | 42 | Git Commit Guidelines: 43 | 44 | ```text 45 | [optional scope]: 46 | 47 | [optional body] 48 | 49 | [optional footer] 50 | ``` 51 | 52 | #### Type 53 | 54 | Must be one of the following: 55 | 56 | * **build**: Changes that affect the build system or external dependencies (example scopes: gulp, broccoli, npm) 57 | * **ci**: Changes to our CI configuration files and scripts (example scopes: Circle, BrowserStack, SauceLabs) 58 | * **docs**: Documentation only changes 59 | * **feat**: A new feature 60 | * **fix**: A bug fix 61 | * **perf**: A code change that improves performance 62 | * **refactor**: A code change that neither fixes a bug nor adds a feature 63 | * **style**: Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc) 64 | * **test**: Adding missing tests or correcting existing tests 65 | 66 | ### Scope 67 | 68 | The following is the list of supported scopes: 69 | 70 | * project_infrastructure 71 | * readme 72 | * core_functionality 73 | * [TBD] 74 | 75 | ## Info 76 | 77 | * 78 | * 79 | 80 | ## Authors 81 | 82 | * **Alexey Nikiforov** - *Initial work* [NikiforovAll](https://github.com/NikiforovAll) 83 | 84 | ## License 85 | 86 | This project is licensed under the MIT License 87 | 88 | ## Acknowledgments 89 | 90 | * 91 | -------------------------------------------------------------------------------- /XMindAPI.Tests/XMindAPI.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp2.1 5 | false 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /XMindAPI.Tests/XMindConfigurationLoaderTest.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | using FluentAssertions; 3 | using System.Collections.Generic; 4 | 5 | using XMindAPI.Configuration; 6 | namespace Tests 7 | { 8 | [TestFixture] 9 | public class XMindConfigurationLoaderTest 10 | { 11 | [SetUp] 12 | public void Setup() 13 | { 14 | // Log.Logger = new LoggerConfiguration() 15 | // .MinimumLevel.Debug() 16 | // .WriteTo.Sink(new TestCorrelatorSink()) 17 | // .WriteTo.File("log.txt") 18 | // .CreateLogger(); 19 | } 20 | 21 | [Test] 22 | public void GetOutputLocationsDictionary_Success() 23 | { 24 | //TODO: this is not something we want to test since it depends on config 25 | //Act 26 | IDictionary locations = XMindConfigurationLoader 27 | .Configuration.GetOutputFilesLocations(); 28 | //Assert 29 | locations.Should().NotBeEmpty(); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /XMindAPI.Tests/XMindConfigurationTest.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | using System; 3 | using System.IO; 4 | using XMindAPI.Configuration; 5 | using XMindAPI.Writers; 6 | using XMindAPI; 7 | using FluentAssertions; 8 | 9 | namespace Tests 10 | { 11 | [TestFixture] 12 | public class XMindConfigurationTest 13 | { 14 | [SetUp] 15 | public void Setup() 16 | { 17 | } 18 | 19 | [Test] 20 | public void CreateDefaultMetaFile_DefaultCreate_Success() 21 | { 22 | var config = new XMindConfiguration() 23 | .WriteTo 24 | .Writer(new LoggerWriter() 25 | .SetOutput(new LoggerWriterOutputConfig("root"))); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /XMindAPI.Tests/XMindFileBuilderTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Xml.Linq; 4 | using System.Linq; 5 | 6 | using NUnit.Framework; 7 | using FluentAssertions; 8 | 9 | using Serilog.Sinks.TestCorrelator; 10 | using Serilog; 11 | 12 | using XMindAPI; 13 | using XMindAPI.Core.Builders; 14 | 15 | namespace Tests 16 | { 17 | [TestFixture] 18 | public class XMindFileBuilderTest 19 | { 20 | [SetUp] 21 | public void Setup() 22 | { 23 | // Log.Logger = new LoggerConfiguration() 24 | // .MinimumLevel.Debug() 25 | // .WriteTo.Sink(new TestCorrelatorSink()) 26 | // .WriteTo.File("log.txt") 27 | // .CreateLogger(); 28 | } 29 | 30 | [Test] 31 | public void CreateDefaultMetaFile_DefaultCreate_Success() 32 | { 33 | XMindDocumentBuilder build = new XMindDocumentBuilder(); 34 | XDocument doc = build.CreateMetaFile(); 35 | doc.ToString().Should().Be(@""); 36 | } 37 | 38 | [Test] 39 | public void CreateDefaultManifestFile_DefaultCreate_Success() 40 | { 41 | XMindDocumentBuilder build = new XMindDocumentBuilder(); 42 | var doc = build.CreateManifestFile(); 43 | doc.Should().NotBeNull(); 44 | } 45 | 46 | [Test] 47 | public void CreateDefaultContentFile_DefaultCreate_Success() 48 | { 49 | XMindDocumentBuilder build = new XMindDocumentBuilder(); 50 | var doc = build.CreateContentFile(); 51 | doc.Should().NotBeNull(); 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /XMindAPI.Tests/XMindFileWriterTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Xml.Linq; 4 | using System.Linq; 5 | using System.Collections.Generic; 6 | 7 | using Serilog; 8 | using Serilog.Sinks.TestCorrelator; 9 | 10 | using NUnit.Framework; 11 | using FluentAssertions; 12 | 13 | using XMindAPI; 14 | using XMindAPI.Writers; 15 | using System.Threading.Tasks; 16 | 17 | namespace Tests 18 | { 19 | [TestFixture] 20 | public class XMindFileWriterTest 21 | { 22 | private readonly string _customOutputFolderName = "custom-output"; 23 | private readonly string _xmindOutputFolderName = "xmind-output"; 24 | private readonly string[] _files = { "manifest.xml", "meta.xml", "content.xml" }; 25 | private readonly bool _isCleanUpNeeded = true; 26 | 27 | [SetUp] 28 | public void Setup() 29 | { 30 | 31 | } 32 | 33 | [Test] 34 | public async Task Save_CreateEmptyBookWithFileWriterInCaseOfCustomBasePath_Success() 35 | { 36 | 37 | var book = new XMindConfiguration() 38 | .WriteTo.Writers( 39 | new List>{ 40 | new FileWriter() 41 | .SetOutput(new FileWriterOutputConfig(_files[0]) 42 | .SetBasePath(Path.Combine(_customOutputFolderName, "META-INF"))), 43 | new FileWriter() 44 | .SetOutput(new FileWriterOutputConfig(_files[1]).SetBasePath(_customOutputFolderName)), 45 | new FileWriter() 46 | .SetOutput(new FileWriterOutputConfig(_files[2]).SetBasePath(_customOutputFolderName)) 47 | }) 48 | .WriteTo.SetWriterBinding(FileWriterFactory.CreateStandardResolvers()) 49 | .CreateWorkBook(workbookName: "test"); 50 | //Act 51 | await book.Save(); 52 | //Assert 53 | DirectoryInfo di = new DirectoryInfo(_customOutputFolderName); 54 | di.GetFileSystemInfos("*.xml") 55 | .Select(fi => fi.Should().BeOfType().Which.Name.Should().BeOneOf(_files)) 56 | .All(x => true); 57 | 58 | } 59 | 60 | [Test] 61 | public async Task Save_CreateEmptyBookWithFileWriterWithDefaultPath_Success() 62 | { 63 | //Arrange 64 | var outpath = Path.Combine(_customOutputFolderName, 65 | Path.GetRandomFileName()); 66 | var book = new XMindConfiguration() 67 | .WithFileWriter(basePath: outpath, zip: false) 68 | .CreateWorkBook(workbookName: "test"); 69 | //Act 70 | await book.Save(); 71 | //Assert 72 | DirectoryInfo di = new DirectoryInfo(outpath); 73 | di.GetFileSystemInfos("*.xml") 74 | .Select(fi => fi.Should().BeOfType().Which.Name 75 | .Should().BeOneOf(_files)) 76 | .All(x => true); 77 | } 78 | [Test] 79 | public async Task Save_CreateEmptyBookWithFileWriterWithDefaultPathAndZip_Success() 80 | { 81 | //Arrange 82 | var outpath = Path.Combine(_customOutputFolderName, 83 | Path.GetRandomFileName()); 84 | var book = new XMindConfiguration() 85 | .WithFileWriter(basePath: outpath, zip: true) 86 | .CreateWorkBook(workbookName: "test.xmind"); 87 | //Act 88 | await book.Save(); 89 | //Assert 90 | DirectoryInfo di = new DirectoryInfo(outpath); 91 | di.GetFileSystemInfos("*.xmind").Should().ContainSingle(); 92 | } 93 | 94 | [OneTimeTearDown] 95 | public void Cleanup() 96 | { 97 | if (_isCleanUpNeeded) 98 | { 99 | var customOutput = new DirectoryInfo(_customOutputFolderName); 100 | if (customOutput.Exists) 101 | customOutput.Delete(true); 102 | var xmindOutput = new DirectoryInfo(_xmindOutputFolderName); 103 | if (xmindOutput.Exists) 104 | xmindOutput.Delete(true); 105 | } 106 | } 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /XMindAPI.Tests/XMindSheetTest.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | using System.IO; 3 | using Serilog; 4 | using FluentAssertions; 5 | using XMindAPI; 6 | using XMindAPI.Models; 7 | namespace Tests 8 | { 9 | [TestFixture] 10 | public class XMindSheetTest 11 | { 12 | private readonly bool _isCleanUpNeeded = false; 13 | private readonly string _xmindOutputFolderName = "xmind-output"; 14 | [SetUp] 15 | public void Setup() 16 | { 17 | Log.Logger = new LoggerConfiguration() 18 | .MinimumLevel.Debug() 19 | .WriteTo.File("sheet.test.log", retainedFileCountLimit: 3) 20 | .CreateLogger(); 21 | } 22 | 23 | [Test] 24 | public void GetParent_DirectAncestor_Success() 25 | { 26 | //Arrange 27 | var book = new XMindConfiguration() 28 | .WithFileWriter(zip: false) 29 | .CreateWorkBook(workbookName: "test"); 30 | //Act 31 | var sheet = book.CreateSheet(); 32 | book.AddSheet(sheet); 33 | var parent = sheet.GetParent(); 34 | //Arrange 35 | parent.Should().Be((XMindWorkBook)book, $"Sheet as direct ancestor"); 36 | } 37 | 38 | [Test] 39 | public void SetTitle_DefaultFlow_Success() 40 | { 41 | //Arrange 42 | var book = new XMindConfiguration() 43 | .WithFileWriter(zip: true) 44 | .CreateWorkBook(workbookName: "test"); 45 | string title = "Awesome sheet"; 46 | //Act 47 | var sheet = book 48 | .CreateSheet(); 49 | sheet.SetTitle(title); 50 | //Assert 51 | sheet.GetTitle().Should().Be(title, $"because title for topic is specified"); 52 | } 53 | 54 | [Test] 55 | public void ReplaceRootTopic_DefaultFlow_Success() 56 | { 57 | //Arrange 58 | var book = new XMindConfiguration() 59 | .WithFileWriter(zip: true) 60 | .CreateWorkBook(workbookName: "test"); 61 | var topic = book.CreateTopic(); 62 | //Act 63 | book 64 | .GetPrimarySheet() 65 | .ReplaceRootTopic(topic); 66 | //Assert 67 | book.GetPrimarySheet().GetRootTopic().Should().Be(topic, "because we replace root topic with new one"); 68 | } 69 | 70 | [OneTimeTearDown] 71 | public void Cleanup() 72 | { 73 | if (_isCleanUpNeeded) 74 | { 75 | var xmindOutput = new DirectoryInfo(_xmindOutputFolderName); 76 | if (xmindOutput.Exists) 77 | xmindOutput.Delete(true); 78 | } 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /XMindAPI.Tests/XMindTopicTest.cs: -------------------------------------------------------------------------------- 1 | using FluentAssertions; 2 | using NUnit.Framework; 3 | using System.IO; 4 | using XMindAPI.Models; 5 | using XMindAPI.Writers; 6 | using System.Xml.XPath; 7 | using System.Linq; 8 | using static XMindAPI.Core.DOM.DOMConstants; 9 | using XMindAPI; 10 | 11 | namespace Tests 12 | { 13 | [TestFixture] 14 | public class XMindTopicTest 15 | { 16 | private readonly bool _isCleanUpNeeded = false; 17 | private readonly string _xmindOutputFolderName = "xmind-output"; 18 | 19 | public XMindWorkBook WorkBook { get; private set; } 20 | 21 | [SetUp] 22 | public void Setup() 23 | { 24 | // Log.Logger = new LoggerConfiguration() 25 | // .MinimumLevel.Debug() 26 | // .WriteTo.File("topic.test.log", retainedFileCountLimit: 3) 27 | // .CreateLogger(); 28 | WorkBook = new XMindConfiguration() 29 | .WriteTo 30 | .Writer(new InMemoryWriter()) 31 | .CreateWorkBook(workbookName: "test"); 32 | } 33 | 34 | [Test] 35 | 36 | public void SetTitle_DefaultFlow_Success() 37 | { 38 | //Arrange 39 | var book = new XMindConfiguration() 40 | .WithFileWriter(zip: true) 41 | .CreateWorkBook(workbookName: "test"); 42 | string title = "Awesome topic"; 43 | //Act 44 | var topic = book 45 | .CreateTopic(title); 46 | //Assert 47 | topic.GetTitle().Should().Be(title, $"because title for topic is specified"); 48 | topic.OwnedSheet.Should().Be(book.GetPrimarySheet(), $"because topics should be added to primary sheet"); 49 | topic.OwnedWorkbook.Should().Be(book, $"because topics are registered in book"); 50 | } 51 | 52 | [Test] 53 | public void AddChildTopic_SingleAttachedTopic_Success() 54 | { 55 | //Arrange 56 | var root = WorkBook.CreateTopic("Topic") as XMindTopic; 57 | var topic2 = WorkBook.CreateTopic("ChildTopic"); 58 | //Act 59 | root.Add(topic2); 60 | //Assert 61 | root.Implementation.Should().HaveElement("children") 62 | .Which.Should().HaveElement("topics").Which.Should().HaveElement("topic") 63 | .Which.Should().HaveAttribute("id", topic2.GetId()); 64 | // .And.HaveAttribute("type", "attached"); 65 | } 66 | 67 | [Test] 68 | public void AddChildTopics_MultipleInsertedTopics_Success() 69 | { 70 | //Arrange 71 | var root = WorkBook.CreateTopic("Topic") as XMindTopic; 72 | var topic1 = WorkBook.CreateTopic("ChildTopic1"); 73 | var topic2 = WorkBook.CreateTopic("ChildTopic2"); 74 | var topic3 = WorkBook.CreateTopic("ChildTopic3"); 75 | //Act 76 | root.Add(topic1); 77 | root.Add(topic2); 78 | root.Add(topic3, 0); 79 | //Assert 80 | root.Implementation.XPathSelectElement("(/children/topics[@type='attached']/topic)[1]") 81 | .Should().HaveAttribute("id", topic3.GetId()); 82 | } 83 | [Test] 84 | public void AddChildTopics_MultipleTopicsAndOneOfThemIsDetached_Success() 85 | { 86 | //Arrange 87 | var root = WorkBook.CreateTopic("Topic") as XMindTopic; 88 | var topic = WorkBook.CreateTopic("ChildTopic"); 89 | var detachedTopic = WorkBook.CreateTopic("ChildTopic"); 90 | //Act 91 | root.Add(topic); 92 | root.Add(detachedTopic, type: TopicType.Detached); 93 | //Assert 94 | root.Implementation.XPathSelectElement("(/children/topics[@type='detached']/topic)[1]") 95 | .Should().HaveAttribute("id", detachedTopic.GetId()); 96 | } 97 | 98 | [Test] 99 | public void AddLabels_MultipleLabels_Success() 100 | { 101 | //Arrange 102 | var root = WorkBook.CreateTopic("Topic") as XMindTopic; 103 | const string label = "test-label"; 104 | root.AddLabel(label); 105 | root.Implementation.Should().HaveElement("labels") 106 | .Which.Descendants().Should().ContainSingle(); 107 | root.Implementation.XPathSelectElement("/labels[1]") 108 | .Should().HaveValue(label, "because label was created with some text"); 109 | } 110 | 111 | [Test] 112 | public void RemoveLabel_RemoveLastLabel_Success() 113 | { 114 | //Arrange 115 | var root = WorkBook.CreateTopic("Topic") as XMindTopic; 116 | const string label = "test-label"; 117 | //Act 118 | root.AddLabel(label); 119 | root.RemoveLabel(label); 120 | //Assert 121 | root.Implementation.Should().HaveElement("labels") 122 | .Which.Descendants().Should().BeEmpty(); 123 | } 124 | 125 | [Test] 126 | public void SetLabels_NotEmptyCollection_Success() 127 | { 128 | //Arrange 129 | var labelsCollection = new string[] { "l1", "l2" }; 130 | var root = WorkBook.CreateTopic("Topic") as XMindTopic; 131 | //Act 132 | root.SetLabels(labelsCollection); 133 | //Assert 134 | root.Implementation.Should().HaveElement("labels") 135 | .Which.Descendants().Select(elem => elem.Value) 136 | .Should().BeEquivalentTo(labelsCollection, "because exact collection of labels was set"); 137 | root.GetLabels().Count.Should().Be(2, "because two labels added"); 138 | } 139 | 140 | [Test] 141 | public void AddMarker_SingleMarker_Success() 142 | { 143 | //Arrange 144 | var root = WorkBook.CreateTopic("Topic") as XMindTopic; 145 | //Act 146 | root.AddMarker(MAR_priority1); 147 | //Assert 148 | root.Implementation.Should().HaveElement(TAG_MARKER_REFS) 149 | .Which.Should().HaveElement(TAG_MARKER_REF) 150 | .Which.Should().HaveAttribute(ATTR_MARKER_ID, MAR_priority1); 151 | } 152 | 153 | [Test] 154 | public void RemoveMarker_EmptyMarkersAsResult_Success() 155 | { 156 | //Arrange 157 | var root = WorkBook.CreateTopic("Topic") as XMindTopic; 158 | //Act 159 | root.AddMarker(MAR_priority1); 160 | root.RemoveMarker(MAR_priority1); 161 | //Assert 162 | root.Implementation.Should().HaveElement(TAG_MARKER_REFS) 163 | .Which.Descendants().Select(elem => elem.Value) 164 | .Should().BeEmpty(); 165 | root.HasMarker(MAR_priority1).Should().BeFalse(); 166 | } 167 | 168 | [OneTimeTearDown] 169 | public void Cleanup() 170 | { 171 | if (_isCleanUpNeeded) 172 | { 173 | var xmindOutput = new DirectoryInfo(_xmindOutputFolderName); 174 | if (xmindOutput.Exists) 175 | xmindOutput.Delete(true); 176 | } 177 | } 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /XMindAPI.Tests/XMindWorkBookTest.cs: -------------------------------------------------------------------------------- 1 | using FluentAssertions; 2 | using NUnit.Framework; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | using XMindAPI; 7 | using XMindAPI.Models; 8 | using XMindAPI.Writers; 9 | 10 | namespace Tests 11 | { 12 | [TestFixture] 13 | public class XMindWorkBookTest 14 | { 15 | 16 | private readonly string _customOutputFolderName = Path.Combine(Path.GetTempPath(), "test-output"); 17 | private readonly string[] _files = { "manifest.xml", "meta.xml", "content.xml" }; 18 | private readonly bool _isCleanUpNeeded = true; 19 | 20 | // [SetUp] 21 | // public void Setup() 22 | // { 23 | // Log.Logger = new LoggerConfiguration() 24 | // .MinimumLevel.Debug() 25 | // .WriteTo.Sink(new TestCorrelatorSink()) 26 | // .WriteTo.File("book.test.log", retainedFileCountLimit: 3) 27 | // .CreateLogger(); 28 | // } 29 | 30 | // [Test] 31 | // public void Save_CreateEmptyBookWithLogWriter_Success() 32 | // { 33 | // //Arrange 34 | // var book = new XMindConfiguration() 35 | // .WriteTo.Writer(new LoggerWriter() 36 | // .SetOutput(new LoggerWriterOutputConfig(outputName: "root"))) 37 | // .WriteTo.SetFinalizeAction(context => Log.Logger.Information("Finalized")) 38 | // .CreateWorkBook(workbookName: "test"); 39 | 40 | // using (TestCorrelator.CreateContext()) 41 | // { 42 | // //Act 43 | // book.Save(); 44 | // //Assert 45 | // TestCorrelator.GetLogEventsFromCurrentContext() 46 | // .Where(e => e.Level == Serilog.Events.LogEventLevel.Information) 47 | // .Should() 48 | // .HaveCount(4, "empty book initialization had failed"); 49 | 50 | // TestCorrelator.GetLogEventsFromCurrentContext() 51 | // .Where(e => !e.MessageTemplate.ToString().Contains("Finalized", StringComparison.OrdinalIgnoreCase)) 52 | // .Any(e => e.MessageTemplate.ToString().Contains("root")).Should().BeTrue(); 53 | // } 54 | // } 55 | 56 | [Test] 57 | public async Task Save_CreateEmptyBookWithInMemoryWriter_Success() 58 | { 59 | var writer = (InMemoryWriter)new InMemoryWriter() 60 | .SetOutput(new InMemoryWriterOutputConfig(outputName: "root")); 61 | //Arrange 62 | var book = new XMindConfiguration() 63 | .WriteTo 64 | .Writer(writer) 65 | .CreateWorkBook(workbookName: "test"); 66 | //Act 67 | await book.Save(); 68 | //Assert 69 | writer.DocumentStorage.Keys.Should().NotBeEmpty().And 70 | .HaveCount(3).And 71 | .BeEquivalentTo("manifest.xml", "meta.xml", "content.xml"); 72 | } 73 | 74 | [Test] 75 | 76 | public async Task CreateWorkBook_ReadZippedXMindBookFromFileSystem_Success() 77 | { 78 | 79 | //Arrange 80 | var book = new XMindConfiguration() 81 | .WithFileWriter(basePath: _customOutputFolderName, zip: true) 82 | .CreateWorkBook(workbookName: "test.xmind"); 83 | 84 | await book.Save(); 85 | 86 | var writer = new InMemoryWriter() 87 | .SetOutput(new InMemoryWriterOutputConfig(outputName: "root")) as InMemoryWriter; 88 | var book2 = new XMindConfiguration() 89 | .WriteTo 90 | .Writer(writer) 91 | .LoadWorkBookFromLocation(Path.Combine(_customOutputFolderName, "test.xmind")); 92 | //Act 93 | await book2.Save(); 94 | //Assert 95 | writer.DocumentStorage.Keys.Should().NotBeEmpty().And 96 | .HaveCount(3).And 97 | .BeEquivalentTo("manifest.xml", "meta.xml", "content.xml"); 98 | } 99 | 100 | [Test] 101 | 102 | public void GetPrimarySheet_EmptySheet_Success() 103 | { 104 | //Arrange 105 | var book = new XMindConfiguration() 106 | .WithFileWriter(zip: false) 107 | .CreateWorkBook(workbookName: "test"); 108 | //Assert 109 | book.GetPrimarySheet().Should().NotBeNull("because primary sheet is created by default"); 110 | } 111 | 112 | [Test] 113 | public void GetSheets_MultipleSheets_Success() 114 | { 115 | //Arrange 116 | var book = new XMindConfiguration() 117 | .WithFileWriter(zip: true) 118 | .CreateWorkBook(workbookName: "test"); 119 | 120 | int numberOfSheets = 2; 121 | for (int i = 0; i < numberOfSheets; i++) 122 | { 123 | book.AddSheet(book.CreateSheet()); 124 | } 125 | //Act 126 | var sheets = book.GetSheets(); 127 | //Assert 128 | sheets.Count().Should().Be(++numberOfSheets, $"{numberOfSheets} were generated"); 129 | } 130 | 131 | [Test] 132 | public void AddSheet_InsertSheetAsPrimary_Success() 133 | { 134 | //Arrange 135 | var book = new XMindConfiguration() 136 | .WithFileWriter(zip: false) 137 | .CreateWorkBook(workbookName: "test"); 138 | var sheet = book.CreateSheet(); 139 | //Act 140 | book.AddSheet(sheet, 0); 141 | //Assert 142 | book.GetPrimarySheet().Should().Be(sheet, "The last sheet must become primary"); 143 | } 144 | 145 | [Test] 146 | public void RemoveSheet_RemovePrimarySheet() 147 | { 148 | //Arrange 149 | var book = new XMindConfiguration() 150 | .WithFileWriter(zip: false) 151 | .CreateWorkBook(workbookName: "test"); 152 | var primarySheet = book.GetPrimarySheet(); 153 | //Act 154 | book.RemoveSheet(primarySheet); 155 | //Assert 156 | book.GetSheets().Count().Should().Be(0, "because we deleted primary sheet and there are no other sheets"); 157 | } 158 | 159 | [Test] 160 | public void FindTopic_Default_Success() 161 | { 162 | //Arrange 163 | var book = new XMindConfiguration() 164 | .WithFileWriter(zip: false) 165 | .CreateWorkBook(workbookName: "FindTopic_Default_Success"); 166 | //Act 167 | var topic = book.GetPrimarySheet().GetRootTopic(); 168 | //Assert 169 | book.FindTopic(topic.GetId(), book) 170 | .Should().BeOfType("because we specify id of a topic") 171 | .Which.Should().Be(topic); 172 | } 173 | 174 | [OneTimeTearDown] 175 | public void Cleanup() 176 | { 177 | if (_isCleanUpNeeded) 178 | { 179 | var customOutput = new DirectoryInfo(_customOutputFolderName); 180 | if (customOutput.Exists) 181 | customOutput.Delete(true); 182 | } 183 | } 184 | } 185 | } 186 | -------------------------------------------------------------------------------- /XMindAPI/Configuration/XMindConfiguration.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | //TODO: cyclic dependency with XMindAPI.Configuration and XMindAPI.Writers.Configuraiton; 4 | using XMindAPI.Writers.Configuration; 5 | using XMindAPI.Core.Builders; 6 | using XMindAPI.Models; 7 | using System.Threading.Tasks; 8 | 9 | namespace XMindAPI 10 | 11 | { 12 | public class XMindConfiguration 13 | { 14 | public const string ManifestLabel = "output:definition:manifest"; 15 | public const string MetaLabel = "output:definition:meta"; 16 | public const string ContentLabel = "output:definition:content"; 17 | 18 | /// 19 | /// Configures the write that generated files will be emitted to. 20 | /// 21 | public XMindWriterConfiguration WriteTo { get; internal set; } 22 | 23 | // public string WorkbookName { get; internal set;} 24 | public XMindConfiguration() 25 | { 26 | WriteTo = new XMindWriterConfiguration(this); 27 | } 28 | 29 | /// 30 | /// Loads workbook from location. WARNING: currently, book is not de-serialized in XML correctly. 31 | /// 32 | /// Path to .xmind file 33 | /// 34 | public XMindWorkBook LoadWorkBookFromLocation(string sourceFileLocation) 35 | { 36 | // WorkbookName = workbookName; 37 | // could be replaced with factory method 38 | var fi = new FileInfo(sourceFileLocation); 39 | if (!fi.Exists) 40 | { 41 | throw new FileNotFoundException($"{nameof(sourceFileLocation)} is invalid"); 42 | } 43 | var workbook = new XMindWorkBook(fi.Name, this, new XMindFileDocumentBuilder(sourceFileLocation)); 44 | return workbook; 45 | } 46 | 47 | /// 48 | /// Creates new workbook, standard XML assets are generated in-memory. 49 | /// 50 | /// The name of XMindWorkBook 51 | /// 52 | public XMindWorkBook CreateWorkBook(string workbookName) => 53 | new XMindWorkBook(workbookName, this, new XMindDocumentBuilder()); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /XMindAPI/Configuration/XMindConfigurationExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using XMindAPI.Configuration; 4 | using XMindAPI.Models; 5 | using XMindAPI.Writers; 6 | using System.IO; 7 | using System.Linq; 8 | using XMindAPI.Zip; 9 | 10 | // TODO: consider to change to XMindAPI.Extensions to follow convention 11 | // but this functionality is essential 12 | namespace XMindAPI 13 | { 14 | public static class XMindConfigurationExtensions 15 | { 16 | // TODO: add API to write to stream, minor because it is always possible to implement IXMindWriter 17 | 18 | /// 19 | /// Writes file to 20 | /// 21 | /// 22 | /// 23 | /// 24 | /// 25 | public static XMindConfiguration WithFileWriter( 26 | this XMindConfiguration config, 27 | string? basePath = default, 28 | bool zip = true) 29 | { 30 | var result = config 31 | .WriteTo.Writers(FileWriterFactory.CreateStandardWriters(basePath)) 32 | .WriteTo.SetWriterBinding(FileWriterFactory.CreateStandardResolvers()); 33 | if (zip) 34 | { 35 | result.WriteTo.SetFinalizeAction(CreateZipXMindFolderCallback(basePath)); 36 | } 37 | return result; 38 | } 39 | /// 40 | /// Write file to default location - "xmind-output" 41 | /// 42 | /// 43 | /// 44 | /// 45 | public static XMindConfiguration WithFileWriter( 46 | this XMindConfiguration config, 47 | bool zip = true) 48 | { 49 | return config.WithFileWriter(basePath: null, zip: zip); 50 | } 51 | 52 | public static XMindConfiguration WithInMemoryWriter( 53 | this XMindConfiguration config 54 | ) 55 | { 56 | return config.WriteTo 57 | .Writer( 58 | new InMemoryWriter( 59 | new InMemoryWriterOutputConfig($"[in-memory-writer]"))); 60 | } 61 | 62 | private static Action, XMindWorkBook> CreateZipXMindFolderCallback( 63 | string? basePath) 64 | { 65 | var xMindSettings = XMindConfigurationLoader.Configuration.XMindConfigCollection; 66 | if (basePath is null && xMindSettings is object) 67 | { 68 | basePath = xMindSettings["output:base"]; 69 | } 70 | var filesToZipLabels = XMindConfigurationLoader 71 | .Configuration 72 | .GetOutputFilesDefinitions() 73 | .Values; 74 | return (ctx, workBook) => 75 | { 76 | using ZipStorer zip = ZipStorer.Create(Path.Combine(basePath, workBook.Name), string.Empty); 77 | var filesToZip = XMindConfigurationLoader 78 | .Configuration 79 | .GetOutputFilesLocations().Where(kvp => filesToZipLabels.Contains(kvp.Key)); 80 | foreach (var fileToken in filesToZip) 81 | { 82 | var fileDir = Path.Combine(basePath, fileToken.Value); 83 | var fullPath = Path.Combine(fileDir, fileToken.Key); 84 | 85 | zip.AddFile(ZipStorer.Compression.Deflate, fullPath, fileToken.Key, string.Empty); 86 | File.Delete(fullPath); 87 | if (!string.IsNullOrEmpty(fileToken.Value) && Directory.Exists(fileDir)) 88 | { 89 | Directory.Delete(fileDir); 90 | } 91 | } 92 | }; 93 | } 94 | 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /XMindAPI/Configuration/XMindConfigurationLoader.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.Extensions.Configuration; 3 | using System.IO; 4 | using System.Reflection; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using XMindAPI.Infrastructure.Logging; 8 | 9 | namespace XMindAPI.Configuration 10 | { 11 | /// 12 | /// Reads XMindConfiguration from xmindsettings.json file (or memory). It is possible to consume as singleton via 13 | /// 14 | internal sealed class XMindConfigurationLoader 15 | { 16 | public IConfiguration? XMindConfigCollection { get; private set; } 17 | 18 | // TODO: consider to remove lazy instantiated dependency for static field 19 | // it is not possible to have control over this thing, nor to manage configuration life time 20 | // this should be only used internally, solution to load items temporarily 21 | public static XMindConfigurationLoader Configuration { get { return lazy.Value; } } 22 | private static readonly Lazy lazy = 23 | new Lazy( 24 | () => new XMindConfigurationLoader() 25 | .LoadConfigurationFile()); 26 | 27 | private XMindConfigurationLoader() 28 | { 29 | } 30 | 31 | /// 32 | /// Allows to get file locations from config xmindsettings.json file 33 | /// 34 | /// Mapping between filename to location. [filename => location] 35 | internal Dictionary GetOutputFilesLocations() 36 | { 37 | if (XMindConfigCollection is null) 38 | { 39 | throw new InvalidOperationException("Output files are not configured"); 40 | } 41 | // var basePath = XMindConfigCollection["output:base"]; 42 | var sectionGroup = XMindConfigCollection.GetSection("output:files").GetChildren(); 43 | var result = sectionGroup.ToDictionary( 44 | s => s.GetChildren().FirstOrDefault(kvp => kvp.Key == "name").Value, 45 | s => s.GetChildren().FirstOrDefault(kvp => kvp.Key == "location").Value 46 | ); 47 | return result; 48 | } 49 | 50 | /// 51 | /// Entry point for configuration for working with main files. 52 | /// 53 | /// Mapping between manifest config token (label) to filename. [label => filename] 54 | internal Dictionary GetOutputFilesDefinitions() => new Dictionary 55 | { 56 | #nullable disable 57 | [XMindConfiguration.ManifestLabel] = XMindConfigCollection[XMindConfiguration.ManifestLabel], 58 | [XMindConfiguration.MetaLabel] = XMindConfigCollection[XMindConfiguration.MetaLabel], 59 | [XMindConfiguration.ContentLabel] = XMindConfigCollection[XMindConfiguration.ContentLabel] 60 | }; 61 | private XMindConfigurationLoader LoadConfigurationFile() 62 | { 63 | try 64 | { 65 | var builder = new ConfigurationBuilder(); 66 | var settingsFileName = "xmindsettings.json"; 67 | var assemblyPathRoot = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); 68 | var settingsPath = Path.Combine(assemblyPathRoot, settingsFileName); 69 | if (File.Exists(settingsPath)) 70 | { 71 | builder.SetBasePath(assemblyPathRoot) 72 | .AddJsonFile(path: settingsFileName, optional: true, reloadOnChange: true); 73 | } 74 | XMindConfigCollection = builder.Build(); 75 | } 76 | catch (Exception) 77 | { 78 | const string errorMessage = "Failed to load configuration file"; 79 | Logger.Log.Error(errorMessage); 80 | throw new InvalidOperationException(errorMessage); 81 | } 82 | return this; 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /XMindAPI/Core/AbstractWorkbook.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Threading.Tasks; 4 | 5 | namespace XMindAPI.Core 6 | { 7 | public abstract class AbstractWorkbook : IWorkbook 8 | { 9 | public void AddSheet(ISheet sheet) 10 | { 11 | AddSheet(sheet, -1); 12 | } 13 | 14 | public abstract void AddSheet(ISheet sheet, int index); 15 | 16 | public abstract IRelationship CreateRelationship(IRelationshipEnd rel1, IRelationshipEnd rel2); 17 | 18 | public abstract IRelationship CreateRelationship(); 19 | 20 | public abstract ISheet CreateSheet(); 21 | 22 | public abstract ITopic CreateTopic(); 23 | 24 | public abstract object? FindElement(string id, IAdaptable source); 25 | 26 | public ITopic? FindTopic(string id, IAdaptable source) 27 | { 28 | var element = FindElement(id, source); 29 | return element as ITopic; 30 | } 31 | 32 | public ITopic? FindTopic(string id) 33 | { 34 | return FindTopic(id, this); 35 | } 36 | 37 | public virtual T GetAdapter(Type t) 38 | { 39 | return default!; 40 | } 41 | 42 | public object? GetElementById(string id) 43 | { 44 | return FindElement(id, this); 45 | } 46 | 47 | public abstract ISheet GetPrimarySheet(); 48 | 49 | public abstract IEnumerable GetSheets(); 50 | 51 | public abstract void RemoveSheet(ISheet sheet); 52 | 53 | public abstract Task Save(); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /XMindAPI/Core/Builders/IXMindDocumentBuilder.cs: -------------------------------------------------------------------------------- 1 | using System.Xml.Linq; 2 | 3 | namespace XMindAPI.Core.Builders 4 | { 5 | internal interface IXMindDocumentBuilder 6 | { 7 | XDocument CreateMetaFile(); 8 | XDocument CreateManifestFile(); 9 | 10 | XDocument CreateContentFile(); 11 | 12 | XDocument MetaFile { get; } 13 | XDocument ManifestFile { get; } 14 | XDocument ContentFile { get; } 15 | } 16 | } -------------------------------------------------------------------------------- /XMindAPI/Core/Builders/XMindDocumentBuilder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Xml.Linq; 5 | using System.Linq; 6 | using Microsoft.Extensions.Configuration; 7 | 8 | using XMindAPI.Configuration; 9 | using XMindAPI.Infrastructure.Logging; 10 | 11 | namespace XMindAPI.Core.Builders 12 | { 13 | internal class XMindDocumentBuilder : IXMindDocumentBuilder 14 | { 15 | protected readonly IConfiguration? xMindSettings = XMindConfigurationLoader.Configuration.XMindConfigCollection; 16 | 17 | protected XDocument? manifestData; 18 | protected XDocument? metaData; 19 | protected XDocument? contentData; 20 | 21 | public XDocument MetaFile { get => metaData ?? throw new InvalidOperationException($"{nameof(metaData)} is not loaded"); } 22 | public XDocument ManifestFile { get => manifestData ?? throw new InvalidOperationException($"{nameof(manifestData)} is not loaded"); } 23 | public XDocument ContentFile { get => contentData ?? throw new InvalidOperationException($"{nameof(contentData)} is not loaded"); } 24 | 25 | public XMindDocumentBuilder() 26 | { 27 | } 28 | public virtual XDocument CreateMetaFile() 29 | { 30 | metaData = CreateDefaultMetaFile(); 31 | return metaData; 32 | } 33 | 34 | public virtual XDocument CreateManifestFile() 35 | { 36 | manifestData = CreateDefaultManifestFile(); 37 | return manifestData; 38 | } 39 | 40 | public virtual XDocument CreateContentFile() 41 | { 42 | contentData = CreateDefaultContentFile(); 43 | return contentData; 44 | } 45 | 46 | private XDocument CreateDefaultMetaFile() 47 | { 48 | var settings = EnsureXMindSettings(); 49 | XDocument metaFile = new XDocument 50 | { 51 | Declaration = new XDeclaration("1.0", "UTF-8", "no") 52 | }; 53 | metaFile.Add( 54 | new XElement( 55 | XNamespace.Get(settings["metaNamespace"]) + "meta", 56 | new XAttribute("version", "2.0") 57 | ) 58 | ); 59 | return metaFile; 60 | } 61 | 62 | private XDocument CreateDefaultManifestFile() 63 | { 64 | var settings = EnsureXMindSettings(); 65 | var files = XMindConfigurationLoader 66 | .Configuration 67 | .GetOutputFilesDefinitions(); 68 | var fileLocations = XMindConfigurationLoader 69 | .Configuration 70 | .GetOutputFilesLocations(); 71 | var manifest = new XDocument 72 | { 73 | Declaration = new XDeclaration("1.0", "UTF-8", "no") 74 | }; 75 | var manifestNamespace = XNamespace.Get(settings["manifestNamespace"]); 76 | var manifestFileEntryToken = manifestNamespace + "file-entry"; 77 | XElement rootElement = new XElement(manifestNamespace + "manifest"); 78 | rootElement.Add( 79 | new XElement(manifestFileEntryToken, 80 | new XAttribute("full-path", files[XMindConfiguration.ContentLabel]), 81 | new XAttribute("media-type", "text/xml") 82 | )); 83 | 84 | var manifestFileName = files[XMindConfiguration.ManifestLabel]; 85 | var manifestFilePath = fileLocations[manifestFileName]; 86 | rootElement.Add( 87 | new XElement(manifestFileEntryToken, 88 | new XAttribute("full-path", manifestFilePath), 89 | new XAttribute("media-type", "") 90 | )); 91 | 92 | rootElement.Add( 93 | new XElement(manifestFileEntryToken, 94 | new XAttribute("full-path", Path.Combine(manifestFilePath, manifestFileName)), 95 | new XAttribute("media-type", "text/xml") 96 | )); 97 | 98 | rootElement.Add( 99 | new XElement(manifestFileEntryToken, 100 | new XAttribute("full-path", "Thumbnails/"), 101 | new XAttribute("media-type", "") 102 | )); 103 | 104 | manifest.Add(rootElement); 105 | return manifest; 106 | } 107 | 108 | private XDocument CreateDefaultContentFile() 109 | { 110 | var settings = EnsureXMindSettings(); 111 | var content = new XDocument(); 112 | XNamespace ns2 = XNamespace.Get(settings["standardContentNamespaces:xsl"]); 113 | XNamespace ns3 = XNamespace.Get(settings["standardContentNamespaces:svg"]); 114 | XNamespace ns4 = XNamespace.Get(settings["standardContentNamespaces:xhtml"]); 115 | content.Add(new XElement( 116 | XNamespace.Get(settings["contentNamespace"]) + "xmap-content", 117 | new XAttribute(XNamespace.Xmlns + "fo", ns2), 118 | new XAttribute(XNamespace.Xmlns + "svg", ns3), 119 | new XAttribute(XNamespace.Xmlns + "xhtml", ns4), 120 | new XAttribute(XNamespace.Xmlns + "xlink", XNamespace.Get(settings["xlinkNamespace"])), 121 | new XAttribute("version", "2.0") 122 | )); 123 | return content; 124 | } 125 | 126 | private IConfiguration EnsureXMindSettings() 127 | { 128 | if (xMindSettings is null) 129 | { 130 | const string errorMessage = "XMindSettings are not provided"; 131 | Logger.Log.Error(errorMessage); 132 | throw new InvalidOperationException(errorMessage); 133 | } 134 | return xMindSettings; 135 | } 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /XMindAPI/Core/Builders/XMindFileDocumentBuilder.cs: -------------------------------------------------------------------------------- 1 | 2 | using System.Net.Mime; 3 | using System; 4 | using System.IO; 5 | using System.Xml.Linq; 6 | using System.Collections.Generic; 7 | 8 | using XMindAPI.Configuration; 9 | using XMindAPI.Zip; 10 | using System.Threading.Tasks; 11 | using Ardalis.GuardClauses; 12 | 13 | namespace XMindAPI.Core.Builders 14 | { 15 | internal class XMindFileDocumentBuilder : XMindDocumentBuilder 16 | { 17 | // protected readonly IConfiguration xMindSettings = XMindConfigurationLoader.Configuration.XMindConfigCollection; 18 | private readonly string _sourceFileName; 19 | private bool _isLoaded = false; 20 | 21 | public XMindFileDocumentBuilder(string sourceFileName) 22 | { 23 | _sourceFileName = sourceFileName; 24 | if (!_isLoaded) 25 | { 26 | // TODO: bad approach, IO shouldn't be in ctor 27 | Load(_sourceFileName); 28 | _isLoaded = true; 29 | } 30 | } 31 | // public override XDocument CreateMetaFile() 32 | // { 33 | // Guard.Against.Null(metaData, nameof(metaData)); 34 | // return metaData; 35 | // } 36 | // public override XDocument CreateManifestFile() 37 | // { 38 | // Guard.Against.Null(manifestData, nameof(manifestData)); 39 | // return manifestData; 40 | // } 41 | 42 | // public override XDocument CreateContentFile() 43 | // { 44 | // Guard.Against.Null(contentData, nameof(contentData)); 45 | // return contentData; 46 | // } 47 | 48 | /// 49 | /// Loads XMind workbook from drive 50 | /// 51 | /// file name path 52 | private void Load(string fileName) 53 | { 54 | // TODO: this should be absolute path 55 | if (string.IsNullOrEmpty(fileName) || !File.Exists(fileName)) 56 | { 57 | throw new InvalidOperationException($"XMind file {fileName} is not loaded"); 58 | } 59 | FileInfo xMindFileInfo = new FileInfo(fileName); 60 | // Logger.Info($"XMindFile loaded: {xMindFileInfo.FullName}"); 61 | if (xMindFileInfo.Extension.ToLower() != ".xmind") 62 | { 63 | throw new InvalidOperationException( 64 | "Extension of file is not .xmind" 65 | ); 66 | } 67 | var tempPath = string.Empty; 68 | try 69 | { 70 | tempPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); 71 | Directory.CreateDirectory(tempPath); 72 | string zipFileName = xMindFileInfo.Name.Replace(".xmind", ".zip"); 73 | // Make a temporary copy of the XMind file with a .zip extention for J# zip libraries: 74 | string tempSourceFileName = Path.Combine(tempPath, zipFileName); 75 | // Logger.Info($"Read from: {tempSourceFileName}"); 76 | File.Copy(fileName, tempSourceFileName); 77 | // Make sure the .zip temporary file is not read only 78 | // TODO: delete it later 79 | File.SetAttributes(tempSourceFileName, FileAttributes.Normal); 80 | List fileNamesExtracted = new List(3); 81 | using (ZipStorer zip = ZipStorer.Open(tempSourceFileName, FileAccess.Read)) 82 | { 83 | Dictionary locations = XMindConfigurationLoader.Configuration 84 | .GetOutputFilesLocations(); 85 | // Read the central directory collection 86 | foreach (ZipStorer.ZipFileEntry entry in zip.ReadCentralDir()) 87 | { 88 | if (locations.TryGetValue(entry.FilenameInZip, out var location)) 89 | { 90 | string fileEntryFullName = 91 | Path.Combine(tempPath, location, entry.FilenameInZip); 92 | 93 | fileNamesExtracted.Add(fileEntryFullName); 94 | zip.ExtractFile(entry, fileEntryFullName); 95 | } 96 | } 97 | zip.Close(); 98 | } 99 | // foreach (var file in fileNamesExtracted) 100 | // { 101 | // Logger.Info($"FileDocumentBuilder.Load: file {file} extracted from zip"); 102 | // } 103 | var files = XMindConfigurationLoader 104 | .Configuration 105 | .GetOutputFilesDefinitions(); 106 | var fileLocations = XMindConfigurationLoader 107 | .Configuration 108 | .GetOutputFilesLocations(); 109 | 110 | var manifestFileName = files[XMindConfiguration.ManifestLabel]; 111 | var metaFileName = files[XMindConfiguration.MetaLabel]; 112 | var contentFileName = files[XMindConfiguration.ContentLabel]; 113 | 114 | Dictionary docs = new Dictionary(); 115 | foreach (var fileToken in files) 116 | { 117 | string path = Path.Combine(tempPath, fileLocations[fileToken.Value], fileToken.Value); 118 | // string text = File.ReadAllText(path); 119 | // docs.Add(fileToken.Key, XDocument.Parse(text)); 120 | docs.Add(fileToken.Key, XDocument.Load(path)); 121 | } 122 | metaData = docs[XMindConfiguration.MetaLabel]; 123 | manifestData = docs[XMindConfiguration.ManifestLabel]; 124 | contentData = docs[XMindConfiguration.ContentLabel]; 125 | } 126 | finally 127 | { 128 | Directory.Delete(tempPath, true); 129 | } 130 | } 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /XMindAPI/Core/Core.cs: -------------------------------------------------------------------------------- 1 | namespace XMindAPI.Core 2 | { 3 | internal class Core 4 | { 5 | private static IIdFactory _factoryInstance = new IdFactory(); 6 | public static IIdFactory GetFactory() 7 | { 8 | return _factoryInstance; 9 | } 10 | } 11 | } -------------------------------------------------------------------------------- /XMindAPI/Core/DOM/DOMConstants.cs: -------------------------------------------------------------------------------- 1 | namespace XMindAPI.Core.DOM 2 | { 3 | public class DOMConstants 4 | { 5 | // ========================= 6 | // ATTRIBUTES 7 | // ------------------------- 8 | public const string ATTR_ALGORITHM_NAME = "algorithm-name"; //$NON-NLS-1$ 9 | public const string ATTR_ALIGN = "align"; //$NON-NLS-1$ 10 | public const string ATTR_AMOUNT = "amount"; //$NON-NLS-1$ 11 | public const string ATTR_ANGLE = "angle"; //$NON-NLS-1$ 12 | public const string ATTR_ARROW_BEGIN_CLASS = "arrow-begin-class"; //$NON-NLS-1$ 13 | public const string ATTR_ARROW_END_CLASS = "arrow-end-class"; //$NON-NLS-1$ 14 | public const string ATTR_AUTHOR = "author"; //$NON-NLS-1$ 15 | public const string ATTR_BACKGROUND = "background"; //$NON-NLS-1$ 16 | public const string ATTR_BACKGROUND_COLOR = "fo:background-color"; //$NON-NLS-1$ 17 | public const string ATTR_BORDER_LINE_COLOR = "border-line-color"; //$NON-NLS-1$ 18 | public const string ATTR_BORDER_LINE_WIDTH = "border-line-width"; //$NON-NLS-1$ 19 | public const string ATTR_BRANCH = "branch"; //$NON-NLS-1$ 20 | public const string ATTR_CALLOUT_FILL_COLOR = "callout-fill-color"; //$NON-NLS-1$ 21 | public const string ATTR_CALLOUT_LINE_CLASS = "callout-line-class"; //$NON-NLS-1$ 22 | public const string ATTR_CALLOUT_LINE_COLOR = "callout-line-color"; //$NON-NLS-1$ 23 | public const string ATTR_CALLOUT_LINE_CORNER = "callout-line-corner"; //$NON-NLS-1$ 24 | public const string ATTR_CALLOUT_LINE_PATTERN = "callout-line-pattern"; //$NON-NLS-1$ 25 | public const string ATTR_CALLOUT_LINE_WIDTH = "callout-line-width"; //$NON-NLS-1$ 26 | public const string ATTR_CALLOUT_SHAPE_CLASS = "callout-shape-class"; //$NON-NLS-1$ 27 | public const string ATTR_CHECKSUM = "checksum";//$NON-NLS-1$ 28 | public const string ATTR_CHECKSUM_TYPE = "checksum-type";//$NON-NLS-1$ 29 | public const string ATTR_COLOR = "fo:color"; //$NON-NLS-1$ 30 | public const string ATTR_CREATOR_NAME = "creator-name"; //$NON-NLS-1$ 31 | public const string ATTR_CREATOR_VERSION = "creator-version"; //$NON-NLS-1$ 32 | public const string ATTR_DESCRIPTION = "description"; //$NON-NLS-1$ 33 | public const string ATTR_END1 = "end1"; //$NON-NLS-1$ 34 | public const string ATTR_END2 = "end2"; //$NON-NLS-1$ 35 | public const string ATTR_FILL = "svg:fill"; //$NON-NLS-1$ 36 | public const string ATTR_FONT_DECORATION = "fo:text-decoration"; //$NON-NLS-1$ 37 | public const string ATTR_FONT_FAMILY = "fo:font-family"; //$NON-NLS-1$ 38 | public const string ATTR_FONT_SIZE = "fo:font-size"; //$NON-NLS-1$ 39 | public const string ATTR_FONT_STYLE = "fo:font-style"; //$NON-NLS-1$ 40 | public const string ATTR_FONT_WEIGHT = "fo:font-weight"; //$NON-NLS-1$ 41 | public const string ATTR_FULL_PATH = "full-path"; //$NON-NLS-1$ 42 | public const string ATTR_GRADIENT_COLOR = "color-gradient";//$NON-NLS-1$ 43 | public const string ATTR_HEIGHT = "svg:height"; //$NON-NLS-1$ 44 | public const string ATTR_HIDDEN = "hidden"; //$NON-NLS-1$ 45 | public const string ATTR_HREF = "xlink:href"; //$NON-NLS-1$ 46 | public const string ATTR_ID = "id"; //$NON-NLS-1$ 47 | public const string ATTR_INDEX = "index"; //$NON-NLS-1$ 48 | public const string ATTR_ITERATION_COUNT = "iteration-count"; //$NON-NLS-1$ 49 | public const string ATTR_KEY_DERIVATION_NAME = "key-derivation-name"; //$NON-NLS-1$ 50 | public const string ATTR_LINE_CLASS = "line-class"; //$NON-NLS-1$ 51 | public const string ATTR_LINE_COLOR = "line-color"; //$NON-NLS-1$ 52 | public const string ATTR_LINE_CORNER = "line-corner"; //$NON-NLS-1$ 53 | public const string ATTR_LINE_PATTERN = "line-pattern"; //$NON-NLS-1$ 54 | public const string ATTR_LINE_TAPERED = "line-tapered"; //$NON-NLS-1$ 55 | public const string ATTR_LINE_WIDTH = "line-width"; //$NON-NLS-1$ 56 | public const string ATTR_MARGIN_BOTTOM = "fo:margin-bottom"; //$NON-NLS-1$ 57 | public const string ATTR_MARGIN_LEFT = "fo:margin-left"; //$NON-NLS-1$ 58 | public const string ATTR_MARGIN_RIGHT = "fo:margin-right"; //$NON-NLS-1$ 59 | public const string ATTR_MARGIN_TOP = "fo:margin-top"; //$NON-NLS-1$ 60 | public const string ATTR_MARKER_ID = "marker-id"; //$NON-NLS-1$ 61 | public const string ATTR_MEDIA_TYPE = "media-type"; //$NON-NLS-1$ 62 | public const string ATTR_MODE = "mode"; //$NON-NLS-1$ 63 | public const string ATTR_MODIFIED_BY = "modified-by"; //$NON-NLS-1$ 64 | public const string ATTR_MODIFYBY = "modifyby"; //$NON-NLS-1$ 65 | public const string ATTR_MULTI_LINE_COLORS = "multi-line-colors"; //$NON-NLS-1$ 66 | public const string ATTR_NAME = "name"; //$NON-NLS-1$ 67 | public const string ATTR_NEXT_REVISION_NUMBER = "next-rev-num"; //$NON-NLS-1$ 68 | public const string ATTR_NUMBER_FORMAT = "number-format"; //$NON-NLS-1$ 69 | public const string ATTR_NUMBER_SEPARATOR = "number-separator"; //$NON-NLS-1$ 70 | public const string ATTR_NUMBER_DEPTH = "number-depth"; //$NON-NLS-1$ 71 | public const string ATTR_OBJECT_ID = "object-id"; //$NON-NLS-1$ 72 | public const string ATTR_OPACITY = "svg:opacity"; //$NON-NLS-1$ 73 | public const string ATTR_PREPENDING_NUMBERS = "prepending-numbers"; //$NON-NLS-1$ 74 | public const string ATTR_PROVIDER = "provider"; //$NON-NLS-1$ 75 | public const string ATTR_RANGE = "range"; //$NON-NLS-1$ 76 | public const string ATTR_RESOURCE = "resource"; //$NON-NLS-1$ 77 | public const string ATTR_RESOURCE_PATH = "resource-path"; //$NON-NLS-1$ 78 | public const string ATTR_SVG = "svg"; //$NON-NLS-1$ 79 | public const string ATTR_RESOURCE_ID = "resource-id"; //$NON-NLS-1$ 80 | public const string ATTR_RESOURCE_TYPE = "resource-type"; //$NON-NLS-1$ 81 | public const string ATTR_REVISION_NUMBER = "rev-num"; //$NON-NLS-1$ 82 | public const string ATTR_SALT = "salt"; //$NON-NLS-1$ 83 | public const string ATTR_KEY_IV = "iv"; //$NON-NLS-1$ 84 | public const string ATTR_KEY_SIZE = "size"; //$NON-NLS-1$ 85 | public const string ATTR_SHAPE_CLASS = "shape-class"; //$NON-NLS-1$ 86 | public const string ATTR_SHAPE_CORNER = "shape-corner"; //$NON-NLS-1$ 87 | public const string ATTR_SINGLETON = "singleton"; //$NON-NLS-1$ 88 | public const string ATTR_SPACING_MAJOR = "spacing-major"; //$NON-NLS-1$ 89 | public const string ATTR_SPACING_MINOR = "spacing-minor"; //$NON-NLS-1$ 90 | public const string ATTR_SRC = "xhtml:src"; //$NON-NLS-1$ 91 | public const string ATTR_STRUCTURE_CLASS = "structure-class"; //$NON-NLS-1$ 92 | public const string ATTR_STYLE_FAMILY = "style-family"; //$NON-NLS-1$ 93 | public const string ATTR_STYLE_ID = "style-id"; //$NON-NLS-1$ 94 | public const string ATTR_TEXT_ALIGN = "fo:text-align"; //$NON-NLS-1$ 95 | public const string ATTR_TEXT_BULLET = "fo:text-bullet"; //$NON-NLS-1$ 96 | public const string ATTR_TEXT_TRANSFORM = "fo:text-transform"; //$NON-NLS-1$ 97 | public const string ATTR_THEME = "theme"; //$NON-NLS-1$ 98 | public const string ATTR_TIME = "time"; //$NON-NLS-1$ 99 | public const string ATTR_TIMESTAMP = "timestamp"; //$NON-NLS-1$ 100 | public const string ATTR_TOPIC_ID = "topic-id"; //$NON-NLS-1$ 101 | public const string ATTR_TYPE = "type"; //$NON-NLS-1$ 102 | public const string ATTR_VERSION = "version"; //$NON-NLS-1$ 103 | public const string ATTR_VISIBILITY = "visibility"; //$NON-NLS-1$ 104 | public const string ATTR_WIDTH = "svg:width"; //$NON-NLS-1$ 105 | public const string ATTR_X = "svg:x"; //$NON-NLS-1$ 106 | public const string ATTR_Y = "svg:y"; //$NON-NLS-1$ 107 | public const string PASSWORD_HINT = "password-hint"; //$NON-NLS-1$ 108 | 109 | /* 110 | * 111 | */ 112 | public const string AUTHOR_EMAIL = "org.xmind.author.email"; //$NON-NLS-1$ 113 | public const string AUTHOR_NAME = "org.xmind.author.name"; //$NON-NLS-1$ 114 | public const string AUTHOR_ORG = "org.xmind.author.org"; //$NON-NLS-1$ 115 | 116 | // ========================= 117 | // TAGS 118 | // ------------------------- 119 | public const string TAG_A = "xhtml:a"; //$NON-NLS-1$ 120 | public const string TAG_ALGORITHM = "algorithm"; //$NON-NLS-1$ 121 | public const string TAG_ASSIGNEE = "assignee"; //$NON-NLS-1$ 122 | public const string TAG_ASSIGNEE_SHEET = "assignee-sheet"; //$NON-NLS-1$ 123 | public const string TAG_AUTOMATIC_STYLES = "automatic-styles"; //$NON-NLS-1$ 124 | public const string TAG_BOUNDARIES = "boundaries"; //$NON-NLS-1$ 125 | public const string TAG_BOUNDARY = "boundary"; //$NON-NLS-1$ 126 | public const string TAG_CHILDREN = "children"; //$NON-NLS-1$ 127 | public const string TAG_COMMENT = "comment"; //$NON-NLS-1$ 128 | public const string TAG_COMMENTS = "comments"; //$NON-NLS-1$ 129 | public const string TAG_CONTENT = "content"; //$NON-NLS-1$ 130 | public const string TAG_CONTROL_POINT = "control-point"; //$NON-NLS-1$ 131 | public const string TAG_CONTROL_POINTS = "control-points"; //$NON-NLS-1$ 132 | public const string TAG_DEFAULT_STYLE = "default-style"; //$NON-NLS-1$ 133 | public const string TAG_ENCRYPTION_DATA = "encryption-data"; //$NON-NLS-1$ 134 | public const string TAG_EXTENSION = "extension"; //$NON-NLS-1$ 135 | public const string TAG_EXTENSIONS = "extensions"; //$NON-NLS-1$ 136 | public const string TAG_FILE_ENTRY = "file-entry"; //$NON-NLS-1$ 137 | public const string TAG_IMG = "xhtml:img"; //$NON-NLS-1$ 138 | public const string TAG_INFO_ITEM = "info-item"; //$NON-NLS-1$ 139 | public const string TAG_INFO_ITEMS = "info-items"; //$NON-NLS-1$ 140 | public const string TAG_KEY_DERIVATION = "key-derivation"; //$NON-NLS-1$ 141 | public const string TAG_LABEL = "label"; //$NON-NLS-1$ 142 | public const string TAG_LABELS = "labels"; //$NON-NLS-1$ 143 | public const string TAG_LEGEND = "legend"; //$NON-NLS-1$ 144 | public const string TAG_MANIFEST = "manifest"; //$NON-NLS-1$ 145 | public const string TAG_MARKER = "marker"; //$NON-NLS-1$ 146 | public const string TAG_MARKER_DESCRIPTION = "marker-description"; //$NON-NLS-1$ 147 | public const string TAG_MARKER_DESCRIPTIONS = "marker-descriptions"; //$NON-NLS-1$ 148 | public const string TAG_MARKER_GROUP = "marker-group"; //$NON-NLS-1$ 149 | public const string TAG_MARKER_REF = "marker-ref"; //$NON-NLS-1$ 150 | public const string TAG_MARKER_REFS = "marker-refs"; //$NON-NLS-1$ 151 | public const string TAG_MARKER_SHEET = "marker-sheet"; //$NON-NLS-1$ 152 | public const string TAG_MASTER_STYLES = "master-styles"; //$NON-NLS-1$ 153 | public const string TAG_META = "meta"; //$NON-NLS-1$ 154 | public const string TAG_NOTES = "notes"; //$NON-NLS-1$ 155 | public const string TAG_NUMBERING = "numbering"; //$NON-NLS-1$ 156 | public const string TAG_P = "xhtml:p"; //$NON-NLS-1$ 157 | public const string TAG_POSITION = "position"; //$NON-NLS-1$ 158 | public const string TAG_PREFIX = "prefix"; //$NON-NLS-1$ 159 | public const string TAG_PROPERTIES = "properties"; //$NON-NLS-1$ 160 | public const string TAG_RELATIONSHIP = "relationship"; //$NON-NLS-1$ 161 | public const string TAG_RELATIONSHIPS = "relationships"; //$NON-NLS-1$ 162 | public const string TAG_RESOURCE_REF = "resource-ref"; //$NON-NLS-1$ 163 | public const string TAG_RESOURCE_REFS = "resource-refs"; //$NON-NLS-1$ 164 | public const string TAG_REVISION = "revision"; //$NON-NLS-1$ 165 | public const string TAG_REVISION_CONTENT = "xmap-revision-content"; //$NON-NLS-1$ 166 | public const string TAG_REVISIONS = "xmap-revisions"; //$NON-NLS-1$ 167 | public const string TAG_SEPARATOR = "separator"; //$NON-NLS-1$ 168 | public const string TAG_SHEET = "sheet"; //$NON-NLS-1$ 169 | public const string TAG_SHEET_SETTINGS = "sheet-settings"; //$NON-NLS-1$ 170 | public const string TAG_SPAN = "xhtml:span"; //$NON-NLS-1$ 171 | public const string TAG_STYLE = "style"; //$NON-NLS-1$ 172 | public const string TAG_STYLE_SHEET = "xmap-styles"; //$NON-NLS-1$ 173 | public const string TAG_STYLES = "styles"; //$NON-NLS-1$ 174 | public const string TAG_SUFFIX = "suffix"; //$NON-NLS-1$ 175 | public const string TAG_SUMMARIES = "summaries"; //$NON-NLS-1$ 176 | public const string TAG_SUMMARY = "summary"; //$NON-NLS-1$ 177 | public const string TAG_TITLE = "title"; //$NON-NLS-1$ 178 | public const string TAG_TOPIC = "topic"; //$NON-NLS-1$ 179 | public const string TAG_TOPICS = "topics"; //$NON-NLS-1$ 180 | public const string TAG_WORKBOOK = "xmap-content"; //$NON-NLS-1$ 181 | public const string TAG_STORIES = "stories"; //$NON-NLS-1$ 182 | 183 | // ================== 184 | // VALUES 185 | // ------------------ 186 | public const string VAL_BOLD = "bold"; //$NON-NLS-1$ 187 | public const string VAL_BOTTOM = "bottom"; //$NON-NLS-1$ 188 | public const string VAL_BULLET = "bullet"; //$NON-NLS-1$ 189 | public const string VAL_CAPITALIZE = "capitalize"; //$NON-NLS-1$ 190 | public const string VAL_CARDMODE = "card"; //$NON-NLS-1$ 191 | public const string VAL_CENTER = "center"; //$NON-NLS-1$ 192 | public const string VAL_DEFAULT = "default"; //$NON-NLS-1$ 193 | public const string VAL_FOLDED = "folded"; //$NON-NLS-1$ 194 | public const string VAL_GRADIENT = "gradient"; //$NON-NLS-1$ 195 | public const string VAL_HIDDEN = "hidden"; //$NON-NLS-1$ 196 | public const string VAL_ICONMODE = "icon"; //$NON-NLS-1$ 197 | public const string VAL_ITALIC = "italic"; //$NON-NLS-1$ 198 | public const string VAL_LEFT = "left"; //$NON-NLS-1$ 199 | public const string VAL_LINE_DASH = "dash"; //$NON-NLS-1$ 200 | public const string VAL_LINE_DASH_DOT = "dash-dot"; //$NON-NLS-1$ 201 | public const string VAL_LINE_DASH_DOT_DOT = "dash-dot-dot"; //$NON-NLS-1$ 202 | public const string VAL_LINE_DOT = "dot"; //$NON-NLS-1$ 203 | public const string VAL_LINE_SOLID = "solid"; //$NON-NLS-1$ 204 | public const string VAL_LINE_THROUGH = "line-through"; //$NON-NLS-1$ 205 | public const string VAL_LOWERCASE = "lowercase"; //$NON-NLS-1$ 206 | public const string VAL_MANUAL = "manual"; //$NON-NLS-1$ 207 | public const string VAL_MASTER = "master"; //$NON-NLS-1$ 208 | public const string VAL_NONE = "none"; //$NON-NLS-1$ 209 | public const string VAL_NORMAL = "normal"; //$NON-NLS-1$ 210 | public const string VAL_NUMBER = "number"; //$NON-NLS-1$ 211 | public const string VAL_RIGHT = "right"; //$NON-NLS-1$ 212 | public const string VAL_SYSTEM = "$system$"; //$NON-NLS-1$ 213 | public const string VAL_TAPERED = "tapered"; //$NON-NLS-1$ 214 | public const string VAL_TOP = "top"; //$NON-NLS-1$ 215 | public const string VAL_UNDERLINE = "underline"; //$NON-NLS-1$ 216 | public const string VAL_UPPERCASE = "uppercase"; //$NON-NLS-1$ 217 | public const string VAL_VISIBLE = "visible"; //$NON-NLS-1$ 218 | 219 | // MARKERS 220 | 221 | // Smiley Markers 222 | 223 | public const string MAR_smileysmile = "smiley-smile"; 224 | public const string MAR_smileylaugh = "smiley-laugh"; 225 | public const string MAR_smileyangry = "smiley-angry"; 226 | public const string MAR_smileycry = "smiley-cry"; 227 | public const string MAR_smileysurprise = "smiley-surprise"; 228 | public const string MAR_smileyboring = "smiley-boring"; 229 | 230 | // Task Markers 231 | public const string MAR_taskstart = "task-start"; 232 | public const string MAR_taskoct = "task-oct"; 233 | public const string MAR_taskquarter = "task-quarter"; 234 | public const string MAR_task3oct = "task-3oct"; 235 | public const string MAR_taskhalf = "task-half"; 236 | public const string MAR_task5oct = "task-5oct"; 237 | public const string MAR_task3quar = "task-3quar"; 238 | public const string MAR_task7oct = "task-7oct"; 239 | public const string MAR_taskdone = "task-done"; 240 | public const string MAR_taskpause = "task-pause"; 241 | 242 | // People Markers 243 | public const string MAR_peoplered = "people-red"; 244 | public const string MAR_peopleorange = "people-orange"; 245 | public const string MAR_peopleyellow = "people-yellow"; 246 | public const string MAR_peopleblue = "people-blue"; 247 | public const string MAR_peoplegreen = "people-green"; 248 | public const string MAR_peoplepurple = "people-purple"; 249 | 250 | // Priority Markers 251 | 252 | public const string MAR_priority1 = "priority-1"; 253 | public const string MAR_priority2 = "priority-2"; 254 | public const string MAR_priority3 = "priority-3"; 255 | public const string MAR_priority4 = "priority-4"; 256 | public const string MAR_priority5 = "priority-5"; 257 | public const string MAR_priority6 = "priority-6"; 258 | public const string MAR_priority7 = "priority-7"; 259 | public const string MAR_priority8 = "priority-8"; 260 | public const string MAR_priority9 = "priority-9"; 261 | 262 | // Symbol Markers 263 | public const string MAR_symbolplus = "symbol-plus"; 264 | public const string MAR_symbolminus = "symbol-minus"; 265 | public const string MAR_symbolquestion = "symbol-question"; 266 | public const string MAR_symbolexclam = "symbol-exclam"; 267 | public const string MAR_symbolinfo = "symbol-info"; 268 | public const string MAR_symbolwrong = "symbol-wrong"; 269 | public const string MAR_symbolright = "symbol-right"; 270 | public const string MAR_c_simbolplus = "c_simbol-plus"; 271 | public const string MAR_c_simbolminus = "c_simbol-minus"; 272 | public const string MAR_c_simbolquestion = "c_simbol-question"; 273 | public const string MAR_c_simbolexclam = "c_simbol-exclam"; 274 | public const string MAR_c_simbolinfo = "c_simbol-info"; 275 | public const string MAR_c_simbolwrong = "c_simbol-wrong"; 276 | public const string MAR_c_simbolright = "c_simbol-right"; 277 | // Arrow Markers 278 | 279 | public const string MAR_arrowup = "arrow-up"; 280 | public const string MAR_arrowupright = "arrow-up-right"; 281 | public const string MAR_arrowright = "arrow-right"; 282 | public const string MAR_arrowdownright = "arrow-down-right"; 283 | public const string MAR_arrowdown = "arrow-down"; 284 | public const string MAR_arrowdownleft = "arrow-down-left"; 285 | public const string MAR_arrowleft = "arrow-left"; 286 | public const string MAR_arrowupleft = "arrow-up-left"; 287 | public const string MAR_arrowrefresh = "arrow-refresh"; 288 | 289 | //Other Markers 290 | 291 | public const string MAR_othercalendar = "other-calendar"; 292 | public const string MAR_otheremail = "other-email"; 293 | public const string MAR_otherphone = "other-phone"; 294 | public const string MAR_otherphone2 = "other-phone2"; 295 | public const string MAR_otherfax = "other-fax"; 296 | public const string MAR_otherpeople = "other-people"; 297 | public const string MAR_otherpeople2 = "other-people2"; 298 | public const string MAR_otherclock = "other-clock"; 299 | public const string MAR_othercoffee = "other-coffee"; 300 | public const string MAR_otherquestion = "other-question"; 301 | public const string MAR_otherexclam = "other-exclam"; 302 | public const string MAR_otherlightbulb = "other-lightbulb"; 303 | public const string MAR_otherbusinesscard = "other-businesscard"; 304 | public const string MAR_othersocial = "other-social"; 305 | public const string MAR_otherchat = "other-chat"; 306 | public const string MAR_othernote = "other-note"; 307 | public const string MAR_otherlock = "other-lock"; 308 | public const string MAR_otherunlock = "other-unlock"; 309 | public const string MAR_otheryes = "other-yes"; 310 | public const string MAR_otherno = "other-no"; 311 | public const string MAR_otherbomb = "other-bomb"; 312 | 313 | 314 | } 315 | } 316 | -------------------------------------------------------------------------------- /XMindAPI/Core/DOM/DOMUtils.cs: -------------------------------------------------------------------------------- 1 | 2 | #nullable disable 3 | using System.Xml.Linq; 4 | using System.Linq; 5 | using System.Collections.Generic; 6 | using static XMindAPI.Core.DOM.DOMConstants; 7 | 8 | namespace XMindAPI.Core.DOM 9 | { 10 | internal class DOMUtils 11 | { 12 | 13 | internal static XElement AddIdAttribute(XElement element) 14 | { 15 | if (!element.Attributes().Any()) 16 | { 17 | element.SetAttributeValue(ATTR_ID, Core.GetFactory().CreateId()); 18 | //TODO: setIdAttribute(tag, true) - investigate 19 | } 20 | return element; 21 | } 22 | 23 | internal static XElement GetElementById(XDocument document, string id) 24 | { 25 | var result = document.Descendants() 26 | .Where(el => el.Attributes("id").FirstOrDefault()?.Equals(id) ?? false) 27 | .FirstOrDefault(); 28 | if (result == null) 29 | { 30 | result = document.Descendants().FirstOrDefault(el => GetElementById(el, id) != null); 31 | } 32 | return result; 33 | } 34 | 35 | internal static XElement GetElementById(XElement element, string id) 36 | { 37 | XElement result = null; 38 | foreach (var item in element.Descendants()) 39 | { 40 | result = item.Attributes("id").FirstOrDefault()?.Equals(id) ?? false ? item : null; 41 | result = GetElementById(item, id); 42 | } 43 | return result; 44 | } 45 | 46 | 47 | internal static List GetChildList(XElement element, string childTag, NodeAdaptableRegistry registry) where T : IAdaptable 48 | { 49 | List result = new List(); 50 | foreach (var item in GetChildElementsByTag(element, childTag)) 51 | { 52 | IAdaptable adaptable = registry.GetAdaptable(item); 53 | if (adaptable != null) 54 | { 55 | result.Add((T)adaptable); 56 | } 57 | } 58 | return result; 59 | } 60 | 61 | internal static IEnumerable GetChildElementsByTag(XNode element, string tagName) 62 | { 63 | return ((XElement)element).Elements(tagName); 64 | } 65 | 66 | internal static XElement GetFirstElementByTagName(XNode element, string tagName) 67 | { 68 | return GetChildElementsByTag(element, tagName).FirstOrDefault(); 69 | } 70 | 71 | internal static string GetTextContentByTag(XElement element, string tagName) 72 | { 73 | XElement item = GetFirstElementByTagName(element, tagName); 74 | return item?.Value; 75 | } 76 | 77 | internal static void SetText(XNode node, string tagName, string textContent) 78 | { 79 | XNode textNode = GetFirstElementByTagName(node, tagName); 80 | if (textNode != null) 81 | { 82 | if (textContent == null) 83 | { 84 | textNode.Remove(); 85 | } 86 | else 87 | { 88 | var element = (XElement)textNode; 89 | element.Value = textContent; 90 | } 91 | } 92 | else 93 | { 94 | //TODO: strange behavior - investigate 95 | var element = (XElement)node; 96 | element.Add(new XElement(tagName, textContent)); 97 | } 98 | } 99 | internal static XElement CreateText(XNode parent, string tagName, string content) 100 | { 101 | XElement element = CreateElement(parent, tagName); 102 | element.Value = content; 103 | return element; 104 | } 105 | internal static XNode FindTextNode(XNode node) 106 | { 107 | return node.Ancestors() 108 | .Where(el => el.NodeType == System.Xml.XmlNodeType.Text) 109 | .FirstOrDefault(); 110 | } 111 | 112 | internal static XElement CreateElement(XNode node, string tagName) 113 | { 114 | XDocument doc = node.NodeType == System.Xml.XmlNodeType.Document ? 115 | node as XDocument : 116 | node.Document; 117 | //TODO: differs from Java implementation 118 | var innerElement = new XElement(tagName); 119 | (node as XElement)?.Add(innerElement); 120 | return innerElement; 121 | } 122 | 123 | internal static XElement EnsureChildElement(XNode parent, string tagName) 124 | { 125 | XElement element; 126 | if (parent.NodeType == System.Xml.XmlNodeType.Document) 127 | { 128 | element = parent.Parent; 129 | } 130 | else 131 | { 132 | element = GetFirstElementByTagName(parent, tagName); 133 | } 134 | if (element == null) 135 | { 136 | // Logger.Info($"EnsureChildElement.CreateElement: item {tagName}was created for {parent.NodeType}"); 137 | CreateElement(parent, tagName); 138 | } 139 | return element; 140 | } 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /XMindAPI/Core/DOM/IDKey.cs: -------------------------------------------------------------------------------- 1 | using System.Xml.Linq; 2 | 3 | namespace XMindAPI.Core.DOM 4 | { 5 | internal struct IDKey 6 | { 7 | internal IDKey(XDocument document, string id) 8 | { 9 | Document = document; 10 | Id = id; 11 | } 12 | public XDocument Document { get; } 13 | public string Id { get; } 14 | 15 | // public override int GetHashCode() 16 | // { 17 | // unchecked // Overflow is fine, just wrap 18 | // { 19 | // int hash = 17; 20 | // hash = hash * 23 + Id.GetHashCode(); 21 | // hash = hash * 23 + Document.GetHashCode(); 22 | // return hash; 23 | // } 24 | // } 25 | 26 | // public override bool Equals(object obj) 27 | // { 28 | // if (obj == this) 29 | // { 30 | // return true; 31 | // } 32 | // if (obj == null || !(obj is IDKey)) 33 | // { 34 | // return false; 35 | // } 36 | // IDKey? that = obj as IDKey; 37 | // var document = that?.Document; 38 | // return Document.Equals(document) && Id.Equals(that?.Id); 39 | // } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /XMindAPI/Core/DOM/NodeAdaptableRegistry.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Xml.Linq; 5 | using static XMindAPI.Core.DOM.DOMConstants; 6 | 7 | namespace XMindAPI.Core.DOM 8 | { 9 | internal class NodeAdaptableRegistry 10 | { 11 | private readonly XDocument _defaultDocument; 12 | private readonly INodeAdaptableFactory _factory; 13 | private readonly Dictionary _idMap = new Dictionary(); 14 | private readonly Dictionary _nodeMap = new Dictionary(); 15 | 16 | // private IDKey _key = new IDKey(); 17 | 18 | public NodeAdaptableRegistry(XDocument defaultDocument, INodeAdaptableFactory factory) 19 | { 20 | _defaultDocument = defaultDocument; 21 | _factory = factory; 22 | } 23 | 24 | public IAdaptable? GetAdaptableById(String id, XDocument document) 25 | { 26 | // Logger.Info($"NodeAdaptableRegistry.GetAdaptableById: Getting element by Id: {id}"); 27 | if (!_idMap.TryGetValue(CreateIDKey(id, document), out var result)) 28 | return null; 29 | return result; 30 | } 31 | public IAdaptable GetAdaptableByNode(XNode node) 32 | { 33 | // Logger.Info($"NodeAdaptableRegistry.GetAdaptableById: Getting element by Node: {node.NodeType}"); 34 | return _nodeMap[node]; 35 | } 36 | public IAdaptable? GetAdaptable(string id) => GetAdaptable(id, _defaultDocument); 37 | 38 | public IAdaptable? GetAdaptable(string id, XDocument document) 39 | { 40 | IAdaptable? a = GetAdaptableById(id, document); 41 | if (a == null) 42 | { 43 | XElement element = DOMUtils.GetElementById(document, id); 44 | if (element != null) 45 | { 46 | a = GetAdaptableByNode(element); 47 | if (a == null) 48 | { 49 | a = _factory.CreateAdaptable(element); 50 | } 51 | if (a != null) 52 | { 53 | RegisterByNode(a, element); 54 | RegisterById(a, id, document); 55 | } 56 | } 57 | } 58 | return a; 59 | } 60 | public IAdaptable? GetAdaptable(XNode node) 61 | { 62 | if (node is null) 63 | return null; 64 | if (!_nodeMap.TryGetValue(node, out IAdaptable? a)) 65 | { 66 | a = _factory.CreateAdaptable(node); 67 | if (a != null) 68 | { 69 | RegisterByNode(a, node); 70 | var id = GetId(node); 71 | if (id != null) 72 | { 73 | RegisterById(a, id, node.Document); 74 | } 75 | } 76 | } 77 | return a; 78 | } 79 | public void RegisterById(IAdaptable adaptable, string id, XDocument document) 80 | { 81 | // Logger.Info($"NodeAdaptableRegistry.RegisterById: item was registered {adaptable}"); 82 | _idMap.Add(CreateIDKey(id, document), adaptable); 83 | } 84 | 85 | public void UnregisterById(IAdaptable adaptable, string id, XDocument document) 86 | { 87 | IDKey key = CreateIDKey(id, document); 88 | if (_idMap.TryGetValue(key, out IAdaptable a) && a.Equals(adaptable)) 89 | { 90 | // Logger.Info($"NodeAdaptableRegistry.UnregisterById: item was unregistered {adaptable}"); 91 | _idMap.Remove(key); 92 | } 93 | } 94 | 95 | public void RegisterByNode(IAdaptable adaptable, XNode node) 96 | { 97 | // Logger.Info($"NodeAdaptableRegistry.RegisterByNode: item was registered {adaptable}"); 98 | _nodeMap.Add(node, adaptable); 99 | } 100 | public void UnregisterByNode(IAdaptable adaptable, XNode node) 101 | { 102 | IAdaptable a = _nodeMap[node]; 103 | if (a == adaptable || (a != null && a.Equals(adaptable))) 104 | { 105 | // Logger.Info($"NodeAdaptableRegistry.UnregisterByNode: item was unregistered {adaptable}"); 106 | _nodeMap.Remove(node); 107 | } 108 | } 109 | 110 | public void Register(IAdaptable adaptable, string id) 111 | { 112 | Register(adaptable, id, _defaultDocument); 113 | } 114 | public void Register(IAdaptable adaptable, string id, XDocument document) 115 | { 116 | RegisterById(adaptable, id, document); 117 | XElement element = DOMUtils.GetElementById(document, id); 118 | if (element != null) 119 | { 120 | RegisterByNode(adaptable, element); 121 | } 122 | } 123 | 124 | public void Register(IAdaptable adaptable, XNode node) 125 | { 126 | RegisterByNode(adaptable, node); 127 | var id = GetId(node); 128 | if (id != null) 129 | { 130 | RegisterById(adaptable, id, node.Document); 131 | } 132 | } 133 | 134 | 135 | public void Unregister(IAdaptable adaptable, string id) 136 | { 137 | Unregister(adaptable, id, _defaultDocument); 138 | } 139 | 140 | public void Unregister(IAdaptable adaptable, string id, XDocument document) 141 | { 142 | UnregisterById(adaptable, id, document); 143 | XElement element = DOMUtils.GetElementById(document, id); 144 | if (element != null) 145 | { 146 | UnregisterByNode(adaptable, element); 147 | } 148 | } 149 | 150 | public void Unregister(IAdaptable adaptable, XNode node) 151 | { 152 | UnregisterByNode(adaptable, node); 153 | var id = GetId(node); 154 | if (id != null) 155 | { 156 | UnregisterById(adaptable, id, node.Document); 157 | } 158 | } 159 | 160 | private IDKey CreateIDKey(string id, XDocument document) 161 | { 162 | return new IDKey(document, id); 163 | } 164 | 165 | private string? GetId(XNode node) 166 | { 167 | if (node.NodeType == System.Xml.XmlNodeType.Element) 168 | { 169 | XElement? xElement = node as XElement; 170 | return xElement?.Attributes(ATTR_ID) 171 | ?.FirstOrDefault()?.Value; 172 | } 173 | return null; 174 | } 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /XMindAPI/Core/IAdaptable.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace XMindAPI.Core 4 | { 5 | /// 6 | /// An interface for an adaptable object. Adaptable objects can be dynamically extended to provide different interfaces (or "adapters"). Workbooks and workbook components implement this interface to provide additional functionalities specific to their implementations. For example,IAdaptable a = [some adaptee]; 7 | /// IFoo x = a.getAdapter(IFoo.class); 8 | /// if (x != null) 9 | /// [do IFoo things with x] 10 | /// 11 | public interface IAdaptable 12 | { 13 | 14 | /// 15 | /// Returns an object which is an instance of the given class associated with this object. 16 | /// 17 | /// the adapter class to look up 18 | /// class 19 | /// a object of the given class, or null if this object does not have an adapter for the given class 20 | T GetAdapter(Type adapter); 21 | } 22 | } -------------------------------------------------------------------------------- /XMindAPI/Core/IHyperlinked.cs: -------------------------------------------------------------------------------- 1 | namespace XMindAPI.Core 2 | { 3 | public interface IHyperLinked 4 | { 5 | string? HyperLink {get; set;} 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /XMindAPI/Core/IIdFactory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace XMindAPI.Core 4 | { 5 | interface IIdFactory 6 | { 7 | string CreateId(); 8 | } 9 | } -------------------------------------------------------------------------------- /XMindAPI/Core/IIdentifiable.cs: -------------------------------------------------------------------------------- 1 | namespace XMindAPI.Core 2 | { 3 | /// 4 | /// Provides object Id 5 | /// 6 | public interface IIdentifiable 7 | { 8 | /// 9 | /// Provides access to Id of object 10 | /// 11 | /// 12 | string GetId(); 13 | } 14 | } -------------------------------------------------------------------------------- /XMindAPI/Core/ILabeled.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace XMindAPI.Core 4 | { 5 | public interface ILabeled 6 | { 7 | HashSet GetLabels(); 8 | 9 | void SetLabels(ICollection labels); 10 | 11 | void AddLabel(string label); 12 | void RemoveLabel(string label); 13 | 14 | void RemoveAllLabels(); 15 | } 16 | } -------------------------------------------------------------------------------- /XMindAPI/Core/INodeAdaptableFactory.cs: -------------------------------------------------------------------------------- 1 | using System.Xml.Linq; 2 | 3 | namespace XMindAPI.Core 4 | { 5 | interface INodeAdaptableFactory 6 | { 7 | IAdaptable? CreateAdaptable(XNode node); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /XMindAPI/Core/IPositioned.cs: -------------------------------------------------------------------------------- 1 | namespace XMindAPI.Core 2 | { 3 | public interface IPositioned 4 | { 5 | (int x, int y) Position { get; } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /XMindAPI/Core/IRelationship.cs: -------------------------------------------------------------------------------- 1 | namespace XMindAPI.Core 2 | { 3 | public interface IRelationship : IAdaptable, IIdentifiable, ITitled, ISheetComponent 4 | { 5 | IRelationshipEnd End1 { get; set; } 6 | IRelationshipEnd End2 { get; set; } 7 | 8 | ISheet GetParent(); 9 | 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /XMindAPI/Core/IRelationshipEnd.cs: -------------------------------------------------------------------------------- 1 | namespace XMindAPI.Core 2 | { 3 | public interface IRelationshipEnd : IIdentifiable, ISheetComponent 4 | { 5 | } 6 | } -------------------------------------------------------------------------------- /XMindAPI/Core/ISheet.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace XMindAPI.Core 4 | { 5 | /// 6 | /// Provides actions on the sheet 7 | /// 8 | public interface ISheet : IIdentifiable, ITitled, IWorkbookComponent, IAdaptable 9 | { 10 | /// 11 | /// Returns root topic of the sheet 12 | /// 13 | /// Root topic of sheet 14 | ITopic GetRootTopic(); 15 | 16 | /// 17 | /// Replaces root topic of the sheet 18 | /// 19 | void ReplaceRootTopic(ITopic newRootTopic); 20 | 21 | 22 | HashSet GetRelationships(); 23 | void AddRelationship(IRelationship relationship); 24 | 25 | void RemoveRelationship(IRelationship relationship); 26 | 27 | IWorkbook GetParent(); 28 | 29 | int GetIndex(); 30 | 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /XMindAPI/Core/ISheetComponent.cs: -------------------------------------------------------------------------------- 1 | namespace XMindAPI.Core 2 | { 3 | public interface ISheetComponent : IWorkbookComponent 4 | { 5 | ISheet? OwnedSheet {get; set;} 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /XMindAPI/Core/ITitled.cs: -------------------------------------------------------------------------------- 1 | namespace XMindAPI.Core 2 | { 3 | /// 4 | /// Provides access to object title 5 | /// 6 | public interface ITitled 7 | { 8 | /// 9 | /// Object title 10 | /// 11 | /// 12 | string GetTitle(); 13 | 14 | /// 15 | /// Object title 16 | /// 17 | /// 18 | void SetTitle(string value); 19 | 20 | 21 | 22 | /// 23 | /// Returns whether object has a valid text 24 | /// 25 | /// true if object has a valid title text, false otherwise 26 | bool HasTitle(); 27 | } 28 | } -------------------------------------------------------------------------------- /XMindAPI/Core/ITopic.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using XMindAPI.Models; 3 | 4 | namespace XMindAPI.Core 5 | { 6 | public interface ITopic : IAdaptable, ITitled, ILabeled, IIdentifiable, ITopicComponent, IRelationshipEnd, IHyperLinked 7 | { 8 | 9 | TopicType Type { get; set; } 10 | 11 | bool IsFolded { get; set; } 12 | 13 | IList Children { get; set; } 14 | 15 | void Add(ITopic child, int index = -1, TopicType type = TopicType.Attached); 16 | void AddMarker(string markerId); 17 | 18 | void RemoveMarker(string markerId); 19 | 20 | bool HasMarker(string markerId); 21 | 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /XMindAPI/Core/ITopicComponent.cs: -------------------------------------------------------------------------------- 1 | namespace XMindAPI.Core 2 | { 3 | public interface ITopicComponent : ISheetComponent 4 | { 5 | ITopic Parent { get; } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /XMindAPI/Core/IWorkbook.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Threading.Tasks; 3 | 4 | namespace XMindAPI.Core 5 | { 6 | /// 7 | /// Provides actions on the workbook 8 | /// 9 | public interface IWorkbook: IAdaptable 10 | { 11 | /// 12 | /// Returns ITopic 13 | /// 14 | /// 15 | ITopic CreateTopic(); 16 | 17 | /// 18 | /// Returns Sheet 19 | /// 20 | /// 21 | ISheet CreateSheet(); 22 | void AddSheet(ISheet sheet); 23 | void AddSheet(ISheet sheet, int index); 24 | IEnumerable GetSheets(); 25 | 26 | ISheet GetPrimarySheet(); 27 | 28 | void RemoveSheet(ISheet sheet); 29 | 30 | /// 31 | /// Gets an element with the given identifier string. 32 | /// 33 | /// The identifier string of the desired element 34 | /// The element with the given identifier string. 35 | /// TODO: consider to use IWorkbookComponent for return type 36 | object? GetElementById(string id); 37 | /// 38 | /// Finds an element with the given identifier string requested starting from the source object. 39 | /// 40 | /// 41 | /// 42 | /// 43 | object? FindElement(string id, IAdaptable source); 44 | 45 | /// 46 | /// Get a topic element with a given id. 47 | /// 48 | /// 49 | ITopic? FindTopic(string id); 50 | 51 | // IManifest GetManifest() 52 | // IMeta GetMate() 53 | 54 | /// 55 | /// Saves workbook based on 56 | /// 57 | /// 58 | Task Save(); 59 | IRelationship CreateRelationship(IRelationshipEnd rel1, IRelationshipEnd rel2); 60 | IRelationship CreateRelationship(); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /XMindAPI/Core/IWorkbookComponent.cs: -------------------------------------------------------------------------------- 1 | namespace XMindAPI.Core 2 | { 3 | /// 4 | /// Provides object capability to return parent workbook 5 | /// 6 | public interface IWorkbookComponent 7 | { 8 | /// 9 | /// Gets the workbook who owns this component. 10 | /// 11 | /// 12 | IWorkbook OwnedWorkbook { get; set; } 13 | } 14 | } -------------------------------------------------------------------------------- /XMindAPI/Core/IdFactory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace XMindAPI.Core 4 | { 5 | internal class IdFactory : IIdFactory 6 | { 7 | public string CreateId() 8 | { 9 | return Guid.NewGuid().ToString().Replace("-", ""); 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /XMindAPI/Core/TopicType.cs: -------------------------------------------------------------------------------- 1 | namespace XMindAPI.Models 2 | { 3 | public enum TopicType 4 | { 5 | Root, 6 | Attached, 7 | Detached 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /XMindAPI/Infrastructure/Logger.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics.Tracing; 3 | 4 | namespace XMindAPI.Infrastructure.Logging 5 | { 6 | [EventSource(Name = "XMind-XMindCsharpEventSource")] 7 | internal sealed class Logger : EventSource 8 | { 9 | [Event(1, Keywords = Keywords.Requests, Message = "Start processing request\n\t*** {0}", Task = Tasks.Request, Opcode = EventOpcode.Start)] 10 | public void RequestStart(string RequestID) { WriteEvent(1, RequestID); } 11 | 12 | [Event(2, Keywords = Keywords.Requests, Message = "Entering Phase {1} for request {0}", Task = Tasks.Request, Opcode = EventOpcode.Info, Level = EventLevel.Verbose)] 13 | public void RequestPhase(string RequestID, string PhaseName) { WriteEvent(2, RequestID, PhaseName); } 14 | 15 | [Event(3, Keywords = Keywords.Requests, Message = "Stop processing request\n\t*** {0} ***", Task = Tasks.Request, Opcode = EventOpcode.Stop)] 16 | public void RequestStop(string RequestID) { WriteEvent(3, RequestID); } 17 | 18 | [Event(4, Keywords = Keywords.Debug, Message = "DebugMessage: {0}", Channel = EventChannel.Debug)] 19 | public void DebugTrace(string Message) { WriteEvent(4, Message); } 20 | 21 | [Event(5, Keywords = Keywords.Error, Message = "ErrorMessage: {0}", Channel = EventChannel.Debug, Level = EventLevel.Error)] 22 | public void Error(string Message) 23 | { 24 | // TODO: add exception in SourceEvent 25 | WriteEvent(5, Message); 26 | } 27 | 28 | [Event(6, Keywords = Keywords.Error, Message = "ErrorMessage: {0}", Channel = EventChannel.Debug, Level = EventLevel.Warning)] 29 | public void Warning(string Message) { WriteEvent(6, Message); } 30 | 31 | public class Keywords // This is a bitvector 32 | { 33 | public const EventKeywords Requests = (EventKeywords)0x0001; 34 | public const EventKeywords Debug = (EventKeywords)0x0002; 35 | public const EventKeywords Warning = (EventKeywords)0x0008; 36 | public const EventKeywords Error = (EventKeywords)0x0004; 37 | } 38 | 39 | public class Tasks 40 | { 41 | public const EventTask Request = (EventTask)0x1; 42 | } 43 | public static Logger Log = new Logger(); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /XMindAPI/Infrastructure/SmallGuidGenerator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace XMindAPI.Infrastructure 4 | { 5 | internal class SmallGuidGenerator 6 | { 7 | public static string NewGuid() => 8 | Convert.ToBase64String(Guid.NewGuid().ToByteArray()).TrimEnd('='); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /XMindAPI/Models/XMindMarkers.cs: -------------------------------------------------------------------------------- 1 | 2 | using System; 3 | using System.ComponentModel; 4 | 5 | namespace XMindAPI.Models 6 | { 7 | /// 8 | /// XMindMarkers define optional markers that can be added to topics. Markers are displayed right in front of the topic title. 9 | /// Refer AddMarker() method. 10 | /// 11 | public enum XMindMarkers 12 | { 13 | // Task markers: 14 | [Description("task-start")] 15 | TaskStart, 16 | [Description("task-quarter")] 17 | TaskQuarter, 18 | [Description("task-half")] 19 | TaskHalf, 20 | [Description("task-3quar")] 21 | Task3Quarter, 22 | [Description("task-done")] 23 | TaskDone, 24 | [Description("task-paused")] 25 | TaskPaused, 26 | // Priority markers: 27 | [Description("priority-1")] 28 | Priority1, 29 | [Description("priority-2")] 30 | Priority2, 31 | [Description("priority-3")] 32 | Priority3, 33 | [Description("priority-4")] 34 | Priority4, 35 | [Description("priority-5")] 36 | Priority5, 37 | [Description("priority-6")] 38 | Priority6, 39 | // Smiley markers: 40 | [Description("smiley-smile")] 41 | SmieySmile, 42 | [Description("smiley-laugh")] 43 | SmileyLaugh, 44 | [Description("smiley-angry")] 45 | SmileyAngry, 46 | [Description("smiley-cry")] 47 | SmileyCry, 48 | [Description("smiley-surprise")] 49 | SmileySurprise, 50 | [Description("smiley-boring")] 51 | SmileyBoring, 52 | // Flag markers: 53 | [Description("flag-green")] 54 | FlagGreen, 55 | [Description("flag-red")] 56 | FlagRed, 57 | [Description("flag-orange")] 58 | FlagOrange, 59 | [Description("flag-purple")] 60 | FlagPurple, 61 | [Description("flag-blue")] 62 | FlagBlue, 63 | [Description("flag-black")] 64 | FlagBlack, 65 | // Star markers: 66 | [Description("star-green")] 67 | StarGreen, 68 | [Description("star-red")] 69 | StarRed, 70 | [Description("star-yellow")] 71 | StarYellow, 72 | [Description("star-purple")] 73 | StarPurple, 74 | [Description("star-blue")] 75 | StarBlue, 76 | [Description("star-gray")] 77 | StarGray, 78 | // Half Star markers: 79 | [Description("half-star-green")] 80 | HalfStarGreen, 81 | [Description("half-star-red")] 82 | HalfStarRed, 83 | [Description("half-star-yellow")] 84 | HalfStarYellow, 85 | [Description("half-star-purple")] 86 | HalfStarPurple, 87 | [Description("half-star-blue")] 88 | HalfStarBlue, 89 | [Description("half-star-gray")] 90 | HalfStarGray, 91 | // Other markers: 92 | [Description("other-calendar")] 93 | Caledar, 94 | [Description("other-email")] 95 | Email, 96 | [Description("other-phone")] 97 | Phone, 98 | [Description("other-phone")] 99 | Phone2, 100 | [Description("other-fax")] 101 | Fax, 102 | [Description("other-people")] 103 | People, 104 | [Description("other-people2")] 105 | People2, 106 | [Description("other-clock")] 107 | Clock, 108 | [Description("other-coffee-cup")] 109 | CoffeeCup, 110 | [Description("other-question")] 111 | Question, 112 | [Description("other-exclam")] 113 | ExclamationMark, 114 | [Description("other-lightbulb")] 115 | LightBulb, 116 | [Description("other-businesscard")] 117 | BusinessCard, 118 | [Description("other-social")] 119 | Social, 120 | [Description("other-chat")] 121 | Chat, 122 | [Description("other-note")] 123 | Note, 124 | [Description("other-lock")] 125 | Lock, 126 | [Description("other-unlock")] 127 | Unlock, 128 | [Description("other-yes")] 129 | Yes, 130 | [Description("other-no")] 131 | No, 132 | [Description("other-bomb")] 133 | Bomb 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /XMindAPI/Models/XMindRelationship.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Xml.Linq; 3 | using XMindAPI.Core; 4 | using XMindAPI.Core.DOM; 5 | 6 | namespace XMindAPI.Models 7 | { 8 | internal class XMindRelationship : IRelationship 9 | { 10 | public XMindRelationship(XElement implementation, XMindWorkBook book) 11 | { 12 | OwnedWorkbook = book; 13 | Implementation = DOMUtils.AddIdAttribute(implementation); 14 | } 15 | 16 | public XElement Implementation { get; } 17 | public IRelationshipEnd End1 { get; set; } 18 | public IRelationshipEnd End2 { get; set ; } 19 | public ISheet OwnedSheet { get; set ; } 20 | public IWorkbook OwnedWorkbook { get; set; } 21 | 22 | public T GetAdapter(Type adapter) 23 | { 24 | throw new NotImplementedException(); 25 | } 26 | 27 | public string GetId() 28 | { 29 | throw new System.NotImplementedException(); 30 | } 31 | 32 | public ISheet GetParent() 33 | { 34 | throw new NotImplementedException(); 35 | } 36 | 37 | public string GetTitle() 38 | { 39 | throw new NotImplementedException(); 40 | } 41 | 42 | public bool HasTitle() 43 | { 44 | throw new NotImplementedException(); 45 | } 46 | 47 | public void SetTitle(string value) 48 | { 49 | throw new NotImplementedException(); 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /XMindAPI/Models/XMindSheet.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Xml.Linq; 5 | using XMindAPI.Core; 6 | using XMindAPI.Core.DOM; 7 | using XMindAPI.Infrastructure.Logging; 8 | using static XMindAPI.Core.DOM.DOMConstants; 9 | 10 | 11 | namespace XMindAPI.Models 12 | { 13 | public class XMindSheet : ISheet 14 | { 15 | private XMindWorkBook _ownedWorkbook; 16 | 17 | public string GetId() 18 | { 19 | return Implementation.Attribute(ATTR_ID).Value; 20 | } 21 | 22 | public string GetTitle() 23 | { 24 | return DOMUtils.GetTextContentByTag(Implementation, TAG_TITLE); 25 | } 26 | 27 | public void SetTitle(string value) 28 | { 29 | DOMUtils.SetText(Implementation, TAG_TITLE, value); 30 | } 31 | 32 | public IWorkbook OwnedWorkbook 33 | { 34 | get => _ownedWorkbook; 35 | set => _ownedWorkbook = (XMindWorkBook)value; 36 | } 37 | 38 | public XElement Implementation { get; } 39 | 40 | public XMindSheet(XElement implementation, XMindWorkBook book) 41 | { 42 | _ownedWorkbook = book; 43 | Implementation = DOMUtils.AddIdAttribute(implementation); 44 | // implementation.Attributes().Where(x => x.IsNamespaceDeclaration).Remove(); 45 | //creates default topic if needed 46 | DOMUtils.EnsureChildElement(implementation, TAG_TOPIC); 47 | 48 | } 49 | public void AddRelationship(IRelationship relationship) 50 | { 51 | var container = DOMUtils.EnsureChildElement(Implementation, TAG_RELATIONSHIPS); 52 | if (!(relationship is XMindRelationship rel)) 53 | { 54 | var errorMessage = "AddRelationship: Not valid relationship"; 55 | Logger.Log.Error(errorMessage); 56 | throw new ArgumentException(errorMessage); 57 | } 58 | container.Add(rel); 59 | } 60 | 61 | public T GetAdapter(Type adapter) 62 | { 63 | throw new NotImplementedException(); 64 | } 65 | 66 | public HashSet GetRelationships() 67 | { 68 | throw new NotImplementedException(); 69 | } 70 | 71 | public ITopic GetRootTopic() 72 | { 73 | XElement rootTopic = DOMUtils.GetFirstElementByTagName(Implementation, TAG_TOPIC); 74 | return (ITopic)(OwnedWorkbook as XMindWorkBook) 75 | ?.GetAdaptableRegistry()?.GetAdaptable(rootTopic)!; 76 | } 77 | 78 | public bool HasTitle() 79 | { 80 | return !string.IsNullOrEmpty(GetTitle()); 81 | } 82 | 83 | public void RemoveRelationship(IRelationship relationship) 84 | { 85 | throw new NotImplementedException(); 86 | } 87 | 88 | public void ReplaceRootTopic(ITopic newRootTopic) 89 | { 90 | XElement? rootTopic = (GetRootTopic() as XMindTopic)?.Implementation; 91 | rootTopic?.AddAfterSelf((newRootTopic as XMindTopic)?.Implementation); 92 | rootTopic?.Remove(); 93 | } 94 | 95 | public override string ToString() 96 | { 97 | return $"SHT# Id:{GetId()} ({GetTitle()})"; 98 | } 99 | 100 | public IWorkbook GetParent() 101 | { 102 | XNode node = Implementation.Parent; 103 | if (node != ((OwnedWorkbook as XMindWorkBook)?.GetWorkbookElement())) 104 | { 105 | throw new InvalidOperationException("GetParent: Parent WorkBook is not set"); 106 | } 107 | return OwnedWorkbook; 108 | } 109 | 110 | public int GetIndex() 111 | { 112 | return GetParent().GetSheets().ToList().IndexOf(this); 113 | } 114 | 115 | public override int GetHashCode() 116 | { 117 | return Implementation.GetHashCode(); 118 | } 119 | } 120 | 121 | } 122 | -------------------------------------------------------------------------------- /XMindAPI/Models/XMindStructure.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel; 3 | 4 | namespace XMindAPI.Models 5 | { 6 | /// 7 | /// XMindStructure defines the different types of diagrams that can be drawn. Its implemented through the AddCentralTopic() method. 8 | /// 9 | public enum XMindStructure 10 | { 11 | [Description("org.xmind.ui.fishbone.rightHeaded")] 12 | FishboneRightHeaded, 13 | [Description("org.xmind.ui.fishbone.leftHeaded")] 14 | FishboneLeftHeaded, 15 | [Description("org.xmind.ui.spreadsheet")] 16 | SpreadSheet, 17 | [Description("org.xmind.ui.map")] 18 | Map, 19 | [Description("org.xmind.ui.map.clockwise")] 20 | MapClockwise, 21 | [Description("org.xmind.ui.map.anticlockwise")] 22 | MapAntiClockwise, 23 | [Description("org.xmind.ui.org-chart.down")] 24 | OrgChartDown, 25 | [Description("org.xmind.ui.org-chart.up")] 26 | OrgChartUp, 27 | [Description("org.xmind.ui.tree.left")] 28 | TreeLeft, 29 | [Description("org.xmind.ui.tree.right")] 30 | TreeRight, 31 | [Description("org.xmind.ui.logic.right")] 32 | LogicRight, 33 | [Description("org.xmind.ui.logic.left")] 34 | LogicLeft 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /XMindAPI/Models/XMindTopic.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Xml.Linq; 5 | using System.Xml.XPath; 6 | using XMindAPI.Core; 7 | using XMindAPI.Core.DOM; 8 | using XMindAPI.Infrastructure.Logging; 9 | 10 | using static XMindAPI.Core.DOM.DOMConstants; 11 | 12 | namespace XMindAPI.Models 13 | { 14 | 15 | /// 16 | /// Base element of build XMind maps, topics are added to 17 | /// 18 | public class XMindTopic : ITopic 19 | { 20 | public XMindTopic(XElement implementation, XMindWorkBook book) 21 | { 22 | OwnedWorkbook = book; 23 | Implementation = DOMUtils.AddIdAttribute(implementation); 24 | } 25 | 26 | public IWorkbook OwnedWorkbook { get; set; } 27 | public XElement Implementation { get; } 28 | 29 | public ITopic Parent => throw new NotImplementedException(); 30 | 31 | public ISheet? OwnedSheet { get; set; } 32 | 33 | private readonly TopicType _type = TopicType.Root; 34 | public TopicType Type { get => _type; set => throw new NotImplementedException(); } 35 | public bool IsFolded 36 | { 37 | get => Implementation.Attribute("branch")?.Value == "folded"; 38 | set 39 | { 40 | Implementation.SetAttributeValue("branch", value ? "folded" : null); 41 | } 42 | } 43 | public IList Children { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } 44 | 45 | // TODO: Add possibility to link topics 46 | public string? HyperLink 47 | { 48 | get => Implementation.Attribute(XName.Get("href"))?.Value; 49 | set => Implementation.SetAttributeValue(XName.Get("href"), value); 50 | } 51 | 52 | public void AddLabel(string label) 53 | { 54 | DOMUtils.EnsureChildElement(Implementation, TAG_LABELS); 55 | var labelsTag = Implementation.Element(TAG_LABELS); 56 | labelsTag.Add(new XElement(TAG_LABEL) { Value = label }); 57 | } 58 | public void RemoveAllLabels() 59 | { 60 | var labelsTag = Implementation.Element(TAG_LABELS); 61 | labelsTag.RemoveNodes(); 62 | } 63 | 64 | public void RemoveLabel(string label) 65 | { 66 | Implementation.Element(TAG_LABELS) 67 | .Elements(TAG_LABEL) 68 | .Where(elem => elem.Value 69 | .Equals(label, StringComparison.InvariantCultureIgnoreCase)) 70 | .Remove(); 71 | } 72 | 73 | public void SetLabels(ICollection labels) 74 | { 75 | DOMUtils.EnsureChildElement(Implementation, TAG_LABELS); 76 | Implementation.Element(TAG_LABELS) 77 | .ReplaceNodes(labels.Select(label => new XElement(TAG_LABEL) { Value = label })); 78 | } 79 | 80 | public HashSet GetLabels() => 81 | new HashSet(Implementation.Element(TAG_LABELS) 82 | .Elements().Select(elem => elem.Value)); 83 | 84 | 85 | public void AddMarker(string markerId) 86 | { 87 | DOMUtils.EnsureChildElement(Implementation, TAG_MARKER_REFS); 88 | var markersTag = Implementation.Element(TAG_MARKER_REFS); 89 | markersTag.Add(new XElement( 90 | TAG_MARKER_REF, new XAttribute(ATTR_MARKER_ID, markerId))); 91 | } 92 | 93 | public void RemoveMarker(string markerId) 94 | { 95 | Implementation.Element(TAG_MARKER_REFS) 96 | ?.Elements() 97 | .Where(elem => elem.Attribute(ATTR_MARKER_ID).Value?.Equals(markerId) ?? false) 98 | .Remove(); 99 | } 100 | 101 | public bool HasMarker(string markerId) => Implementation.Element(TAG_MARKER_REFS) 102 | ?.Elements() 103 | ?.Any(elem => elem.Attribute(ATTR_MARKER_ID).Value?.Equals(markerId) ?? false) ?? false; 104 | 105 | public string GetId() 106 | { 107 | return Implementation.Attribute(ATTR_ID).Value; 108 | } 109 | 110 | public string GetTitle() 111 | { 112 | return DOMUtils.GetTextContentByTag(Implementation, TAG_TITLE); 113 | } 114 | 115 | public bool HasTitle() => !string.IsNullOrWhiteSpace(GetTitle()); 116 | 117 | public void SetTitle(string value) 118 | { 119 | DOMUtils.SetText(Implementation, TAG_TITLE, value); 120 | } 121 | 122 | 123 | public override int GetHashCode() 124 | { 125 | // TODO: confirm behavior 126 | return Implementation.GetHashCode(); 127 | } 128 | 129 | public override string ToString() 130 | { 131 | return $"TPC# Id:{GetId()} ({GetTitle()})"; 132 | } 133 | 134 | public void Add(ITopic child, int index = -1, TopicType type = TopicType.Attached) 135 | { 136 | if (!(child is XMindTopic childTopic)) 137 | { 138 | var errorMessage = $"XMindTopic.Add: {nameof(child)} is not valid XMindTopic"; 139 | Logger.Log.Error(errorMessage); 140 | throw new ArgumentException(errorMessage); 141 | } 142 | var typeName = Enum.GetName(type.GetType(), type).ToLower(); 143 | // Override topic type 144 | // child.Type = type; 145 | // Add children tag 146 | DOMUtils.EnsureChildElement(Implementation, TAG_CHILDREN); 147 | var childrenTag = Implementation.Elements(TAG_CHILDREN).Single(); 148 | XElement? tagTopics = childrenTag.Elements(TAG_TOPICS) 149 | ?.FirstOrDefault(elem => elem.Attribute(ATTR_TYPE)?.Value == typeName); 150 | if (tagTopics is null) 151 | { 152 | tagTopics = DOMUtils.CreateElement(childrenTag, TAG_TOPICS); 153 | tagTopics.SetAttributeValue(ATTR_TYPE, typeName); 154 | } 155 | var es = DOMUtils.GetChildElementsByTag(tagTopics, TAG_TOPIC).ToList(); 156 | if (index >= 0 && index < es.Count) 157 | { 158 | es[index].AddBeforeSelf(childTopic.Implementation); 159 | } 160 | else 161 | { 162 | tagTopics.Add(childTopic.Implementation); 163 | } 164 | } 165 | public T GetAdapter(Type adapter) 166 | { 167 | throw new NotImplementedException(); 168 | } 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /XMindAPI/Models/XMindWorkBook.cs: -------------------------------------------------------------------------------- 1 | //TODO: cyclic dependency with XMindAPI.Configuration and XMindAPI.Writers.Configuration; 2 | using Ardalis.GuardClauses; 3 | using Microsoft.Extensions.Configuration; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Threading.Tasks; 8 | using System.Xml.Linq; 9 | using XMindAPI.Configuration; 10 | using XMindAPI.Core; 11 | using XMindAPI.Core.Builders; 12 | using XMindAPI.Core.DOM; 13 | using XMindAPI.Infrastructure; 14 | using XMindAPI.Infrastructure.Logging; 15 | using XMindAPI.Writers; 16 | 17 | using static XMindAPI.Core.DOM.DOMConstants; 18 | 19 | namespace XMindAPI.Models 20 | { 21 | /// 22 | /// XMindWorkBook encapsulates an XMind workbook and methods for performing actions on workbook content. 23 | /// 24 | public class XMindWorkBook : AbstractWorkbook, INodeAdaptableFactory//IWorkbook 25 | { 26 | // https://github.com/xmindltd/xmind/wiki/UsingXmindAPI 27 | // TODO: add IO 28 | // void save(OutputStream output); // save the workbook to the specified OutputStream 29 | public string Name { get; set; } 30 | private readonly XMindConfiguration _bookConfiguration; 31 | private readonly IXMindDocumentBuilder _documentBuilder; 32 | 33 | private readonly NodeAdaptableRegistry _adaptableRegistry; 34 | internal readonly IConfiguration _xMindSettings; 35 | 36 | private readonly XElement _implementation; 37 | 38 | // private string _fileName = null; 39 | 40 | /// 41 | /// Creates a new if loadContent is false, otherwise the file content will be loaded. 42 | /// 43 | /// Name of the book 44 | /// Book configuration 45 | /// Builder to write based on 46 | internal XMindWorkBook(string name, XMindConfiguration bookConfiguration, IXMindDocumentBuilder builder) 47 | { 48 | Guard.Against.Null(XMindConfigurationLoader.Configuration.XMindConfigCollection, "XMindConfigCollection"); 49 | 50 | Name = name; 51 | _xMindSettings = XMindConfigurationLoader.Configuration.XMindConfigCollection; 52 | _bookConfiguration = bookConfiguration; 53 | _documentBuilder = builder; 54 | 55 | _documentBuilder.CreateMetaFile(); 56 | _documentBuilder.CreateManifestFile(); 57 | _documentBuilder.CreateContentFile(); 58 | 59 | _implementation = _documentBuilder.ContentFile.Descendants().First(); 60 | _adaptableRegistry = new NodeAdaptableRegistry(_documentBuilder.ContentFile, this); 61 | //Create default sheet if needed 62 | //TODO: 63 | if (DOMUtils.GetFirstElementByTagName(_implementation, TAG_SHEET) == null) 64 | { 65 | AddSheet(CreateSheet()); 66 | } 67 | } 68 | 69 | public override T GetAdapter(Type adapter) 70 | { 71 | //TODO: this is point of extension for all adaptees 72 | // if (IStorage.class.equals(adapter)) 73 | // return adapter.cast(getStorage()); 74 | // if (IEntryStreamNormalizer.class.equals(adapter)) 75 | // return adapter.cast(manifest.getStreamNormalizer()); 76 | // if (ICoreEventSource.class.equals(adapter)) 77 | // return adapter.cast(this); 78 | // if (adapter.isAssignableFrom(Document.class)) 79 | // return adapter.cast(implementation); 80 | // if (adapter.isAssignableFrom(Element.class)) 81 | // return adapter.cast(getWorkbookElement()); 82 | // if (IMarkerSheet.class.equals(adapter)) 83 | // return adapter.cast(getMarkerSheet()); 84 | // if (IManifest.class.equals(adapter)) 85 | // return adapter.cast(getManifest()); 86 | // if (ICoreEventSupport.class.equals(adapter)) 87 | // return adapter.cast(getCoreEventSupport()); 88 | // if (INodeAdaptableFactory.class.equals(adapter)) 89 | // return adapter.cast(this); 90 | // if (INodeAdaptableProvider.class.equals(adapter)) 91 | // return adapter.cast(getAdaptableRegistry()); 92 | // if (IMarkerRefCounter.class.equals(adapter)) 93 | // return adapter.cast(getMarkerRefCounter()); 94 | // if (IStyleRefCounter.class.equals(adapter)) 95 | // return adapter.cast(getStyleRefCounter()); 96 | // if (IWorkbookComponentRefManager.class.equals(adapter)) 97 | // return adapter.cast(getElementRefCounter()); 98 | // if (IRevisionRepository.class.equals(adapter)) 99 | // return adapter.cast(getRevisionRepository()); 100 | // if (IWorkbookExtensionManager.class.equals(adapter)) 101 | // return adapter.cast(getWorkbookExtensionManager()); 102 | return base.GetAdapter(adapter); 103 | } 104 | 105 | /// 106 | /// Save the current XMind workbook file to disk. 107 | /// 108 | public override async Task Save() 109 | { 110 | var requestId = $"SaveWorkBook-{SmallGuidGenerator.NewGuid()}"; 111 | Logger.Log.RequestStart(requestId); 112 | var manifestFileName = _xMindSettings[XMindConfiguration.ManifestLabel]; 113 | var metaFileName = _xMindSettings[XMindConfiguration.MetaLabel]; 114 | var contentFileName = _xMindSettings[XMindConfiguration.ContentLabel]; 115 | 116 | var files = new Dictionary(3) 117 | { 118 | [metaFileName] = _documentBuilder.MetaFile, 119 | [manifestFileName] = _documentBuilder.ManifestFile, 120 | [contentFileName] = _documentBuilder.ContentFile 121 | }; 122 | var writerJobs = new List(3); 123 | var writerContexts = new List(); 124 | foreach (var kvp in files) 125 | { 126 | var currentWriterContext = new XMindWriterContext() 127 | { 128 | FileName = kvp.Key, 129 | FileEntries = new XDocument[1] { kvp.Value } 130 | }; 131 | var selectedWriters = _bookConfiguration 132 | .WriteTo 133 | .ResolveWriters(currentWriterContext); 134 | if (selectedWriters == null) 135 | { 136 | var errorMessage = "XMindBook.Save: Writer is not selected"; 137 | Logger.Log.Error(errorMessage); 138 | throw new InvalidOperationException(errorMessage); 139 | } 140 | foreach (var writer in selectedWriters) 141 | { 142 | writerJobs.Add(writer.WriteToStorage(kvp.Value, kvp.Key)); 143 | } 144 | writerContexts.Add(currentWriterContext); 145 | } 146 | try 147 | { 148 | await Task.WhenAll(writerJobs); 149 | Logger.Log.RequestPhase(requestId, "WritersCompleted"); 150 | _bookConfiguration.WriteTo.FinalizeAction?.Invoke(writerContexts, this); 151 | Logger.Log.RequestPhase(requestId, "FinalizerExecuted"); 152 | } 153 | catch (Exception e) 154 | { 155 | Logger.Log.Error(e.Message); 156 | throw; 157 | } 158 | finally 159 | { 160 | Logger.Log.RequestStop(requestId); 161 | } 162 | } 163 | 164 | public override IRelationship CreateRelationship( 165 | IRelationshipEnd rel1, IRelationshipEnd rel2) 166 | { 167 | ISheet sheet = rel1.OwnedSheet; 168 | IRelationship rel = CreateRelationship(); 169 | rel.End1 = rel1; 170 | rel.End2 = rel2; 171 | sheet.AddRelationship(rel); 172 | return rel; 173 | } 174 | 175 | public override IRelationship CreateRelationship() 176 | { 177 | var relationshipElement = new XElement(TAG_RELATIONSHIP); 178 | var relationship = new XMindRelationship(relationshipElement, this); 179 | _adaptableRegistry.RegisterByNode(relationship, relationship.Implementation); 180 | return relationship; 181 | } 182 | 183 | public override ISheet CreateSheet() 184 | { 185 | var sheetElement = new XElement(TAG_SHEET); 186 | var sheet = new XMindSheet(sheetElement, this); 187 | _adaptableRegistry.RegisterByNode(sheet, sheet.Implementation); 188 | return sheet; 189 | } 190 | 191 | public override void AddSheet(ISheet sheet, int index) 192 | { 193 | Logger.Log.DebugTrace($"Add sheet {sheet.GetId()} to {Name}"); 194 | if (!(sheet is XMindSheet impl) || impl.Implementation is null) 195 | { 196 | const string errorMessage = "XMindWorkbook.AddSheet: sheet is not correct"; 197 | Logger.Log.Error(errorMessage); 198 | throw new ArgumentException(errorMessage); 199 | } 200 | XElement elementImplementation = impl.Implementation; 201 | var bookImplementation = GetWorkbookElement(); 202 | if (elementImplementation.Parent is object 203 | && elementImplementation.Parent != bookImplementation) 204 | { 205 | const string errorMessage = "XMindWorkbook.AddSheet: sheet must belong to same document"; 206 | Logger.Log.Error(errorMessage); 207 | throw new ArgumentException(errorMessage); 208 | } 209 | var childElements = DOMUtils.GetChildElementsByTag(bookImplementation, TAG_SHEET); 210 | if (index >= 0 && index < childElements.Count()) 211 | { 212 | childElements.Where((e, i) => i == index) 213 | .First() 214 | .AddBeforeSelf(elementImplementation); 215 | } 216 | else 217 | { 218 | bookImplementation.Add(elementImplementation); 219 | } 220 | } 221 | 222 | /// 223 | /// Register topic. Note is not included in DOM of 224 | /// 225 | /// Registered XMindTopic 226 | public override ITopic CreateTopic() 227 | { 228 | var topicElement = new XElement(TAG_TOPIC); 229 | XMindTopic topic = new XMindTopic(topicElement, this) 230 | { 231 | OwnedSheet = GetPrimarySheet() 232 | }; 233 | Logger.Log.DebugTrace($"Register topic {topic.GetId()} for {Name}"); 234 | _adaptableRegistry.RegisterByNode(topic, topic.Implementation); 235 | return topic; 236 | } 237 | /// 238 | /// Register topic. Note is not included in DOM of 239 | 240 | /// Title to set 241 | /// Registered XMindTopic 242 | public ITopic CreateTopic(string title) 243 | { 244 | var topic = CreateTopic(); 245 | topic.SetTitle(title); 246 | return topic; 247 | } 248 | 249 | /// 250 | /// Finds elements in WorkBook based on registry 251 | /// 252 | /// Unique Id of element 253 | /// 254 | /// 255 | public override object? FindElement(string id, IAdaptable source) 256 | { 257 | XNode node = source.GetAdapter(typeof(XNode)); 258 | if (node == null) 259 | { 260 | node = GetWorkbookElement(); 261 | } 262 | return GetAdaptableRegistry() 263 | ?.GetAdaptable(id, node.Document); 264 | } 265 | 266 | /// 267 | /// Gets primary sheet 268 | /// 269 | /// 270 | public override ISheet GetPrimarySheet() 271 | { 272 | XElement primarySheet = DOMUtils.GetFirstElementByTagName(GetWorkbookElement(), TAG_SHEET); 273 | if (primarySheet == null) 274 | { 275 | const string errorMessage = "Primary sheet was not found"; 276 | Logger.Log.Error(errorMessage); 277 | throw new InvalidOperationException(errorMessage); 278 | } 279 | return (ISheet) GetAdaptableRegistry().GetAdaptable(primarySheet)!; 280 | } 281 | 282 | /// 283 | /// Sheets enumerator 284 | /// 285 | /// 286 | public override IEnumerable GetSheets() 287 | { 288 | return DOMUtils.GetChildList(GetWorkbookElement(), TAG_SHEET, GetAdaptableRegistry()); 289 | } 290 | public override void RemoveSheet(ISheet sheet) 291 | { 292 | if (!(sheet is XMindSheet xmindSheet) || xmindSheet.Implementation is null) 293 | { 294 | const string errorMessage = "Implementation was not found"; 295 | Logger.Log.Error(errorMessage); 296 | throw new ArgumentException(errorMessage); 297 | } 298 | XElement elementImplementation = xmindSheet.Implementation; 299 | var bookImplementation = GetWorkbookElement(); 300 | if (elementImplementation.Parent != bookImplementation) 301 | { 302 | // Logger.Warn("XMindWorkbook.RemoveSheet: sheet must belong to same document"); 303 | } 304 | var childElements = DOMUtils 305 | .GetChildElementsByTag(bookImplementation, TAG_SHEET).ToList(); 306 | childElements 307 | .FirstOrDefault(el => el == elementImplementation)? 308 | .Remove(); 309 | } 310 | public IAdaptable? CreateAdaptable(XNode node) 311 | { 312 | IAdaptable? adaptable = null; 313 | if (node is XElement e) 314 | { 315 | XName nodeName = e.Name; 316 | switch (nodeName.ToString()) 317 | { 318 | case TAG_SHEET: 319 | adaptable = new XMindSheet(e, this); 320 | break; 321 | case TAG_TOPIC: 322 | adaptable = new XMindTopic(e, this); 323 | break; 324 | } 325 | } 326 | if (adaptable is null) 327 | { 328 | Logger.Log.Warning($"XMindWorkbook.CreateAdaptable: adaptable was is not created - {adaptable}"); 329 | } 330 | return adaptable; 331 | } 332 | 333 | // public override string ToString() 334 | // { 335 | // return $"Workbook# {_globalConfiguration.WorkbookName}"; 336 | // } 337 | 338 | internal NodeAdaptableRegistry GetAdaptableRegistry() 339 | { 340 | return _adaptableRegistry; 341 | } 342 | 343 | internal XElement GetWorkbookElement() 344 | { 345 | return _implementation; 346 | } 347 | } 348 | } 349 | -------------------------------------------------------------------------------- /XMindAPI/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | 3 | [assembly:InternalsVisibleTo("XMindAPI.Tests")] 4 | -------------------------------------------------------------------------------- /XMindAPI/Utils/XMindUtils.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Xml.Linq; 4 | 5 | 6 | namespace XMindAPI.Utils 7 | { 8 | // USE DOM Utils instead 9 | internal static class XMindUtils 10 | { 11 | public static string NewId() 12 | { 13 | return Guid.NewGuid().ToString().Replace("-", ""); 14 | } 15 | 16 | public static string GetTimeStamp() 17 | { 18 | return DateTime.UtcNow.Ticks.ToString(); 19 | } 20 | public static string GetAttribValue(XElement el, string attributeName) 21 | { 22 | return el.Attribute(attributeName).Value; 23 | } 24 | 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /XMindAPI/XMindAPI.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | netstandard2.0;netstandard2.1 4 | XMindAPI 5 | 8.0 6 | enable 7 | 8 | 9 | 10 | 11 | 15 | 16 | 17 | 18 | 20 | 21 | 25 | 26 | 30 | 31 | true 32 | 33 | 34 | 35 | 36 | 37 | XMind API that allows to build .xmind files programmatically 38 | false 39 | bin\XMindAPI.xml 40 | 41 | 42 | 43 | 44 | XMindCSharp 45 | 46 | icon.png 47 | Alexey Nikiforov 48 | © Alexey Nikiforov 49 | HYS Enterprise 50 | xmind;tools;mindmaps;productivity 51 | https://github.com/NikiforovAll/xmindcsharp/ 52 | https://github.com/NikiforovAll/xmindcsharp/blob/master/LICENSE 53 | git 54 | 55 | 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /XMindAPI/XMindWriters/FileWriter/FileWriter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Xml.Linq; 4 | using System.Threading.Tasks; 5 | using Ardalis.GuardClauses; 6 | 7 | namespace XMindAPI.Writers 8 | { 9 | public class FileWriter : IXMindWriter 10 | { 11 | // private static readonly ILog Logger = LogProvider.GetCurrentClassLogger(); 12 | 13 | // private IConfiguration _xMindSettings; 14 | internal FileWriterOutputConfig? OutputConfig { get; private set; } 15 | 16 | internal readonly bool _isAutoAddedResolver = false; 17 | internal readonly FileWriterStandardOutput _fileWriterStandardOutput; 18 | public FileWriter(FileWriterStandardOutput standardOutput) 19 | { 20 | _isAutoAddedResolver = true; 21 | _fileWriterStandardOutput = standardOutput; 22 | } 23 | public FileWriter() : this(new FileWriterOutputConfig("root")) 24 | { 25 | } 26 | 27 | public FileWriter(FileWriterOutputConfig output) 28 | { 29 | SetOutput(output); 30 | // _xMindSettings = XMindConfigurationCache.Configuration.XMindConfigCollection; 31 | } 32 | 33 | public async Task WriteToStorage(XDocument xmlDocument, string file) 34 | { 35 | Guard.Against.Null(OutputConfig, nameof(OutputConfig)); 36 | var basePath = OutputConfig.Path; 37 | var fileFullName = Path.Combine(basePath, file); 38 | Directory.CreateDirectory(basePath); 39 | // Logger.Info($"FileWriter.WriteToStorage: writing content to {fileFullName}"); 40 | using var memoryStream = new MemoryStream(); 41 | using var fileStream = File.Create(fileFullName); 42 | xmlDocument.Save(memoryStream); 43 | memoryStream.Position = 0; 44 | await memoryStream.CopyToAsync(fileStream); 45 | fileStream.Flush(true); 46 | } 47 | 48 | public IXMindWriter SetOutput(IXMindWriterOutputConfig output) 49 | { 50 | if (!(output is FileWriterOutputConfig fileConfig)) 51 | { 52 | throw new ArgumentException("Please specify correct ${nameof(output)}"); 53 | } 54 | OutputConfig = fileConfig; 55 | return this; 56 | } 57 | 58 | FileWriterOutputConfig IXMindWriter.GetOutputConfig() 59 | { 60 | Guard.Against.Null(OutputConfig, nameof(OutputConfig)); 61 | Guard.Against.NullOrWhiteSpace(OutputConfig.OutputName, "OutputName"); 62 | return OutputConfig; 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /XMindAPI/XMindWriters/FileWriter/FileWriterFactory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.IO; 5 | 6 | using static XMindAPI.XMindConfiguration; 7 | using XMindAPI.Configuration; 8 | using XMindAPI.Infrastructure.Logging; 9 | using Microsoft.Extensions.Configuration; 10 | 11 | namespace XMindAPI.Writers 12 | { 13 | public class FileWriterFactory 14 | { 15 | 16 | public static List> CreateStandardWriters(string? basePath) 17 | { 18 | var standardOutputs = new List{ 19 | FileWriterStandardOutput.Manifest, 20 | FileWriterStandardOutput.Meta, 21 | FileWriterStandardOutput.Content 22 | }; 23 | return standardOutputs.Select(o => CreateStandardWriterFactoryMethod(o, basePath)).ToList(); 24 | } 25 | public static List>, IXMindWriter>> CreateStandardResolvers() 26 | { 27 | var standardOutputs = new List{ 28 | FileWriterStandardOutput.Manifest, 29 | FileWriterStandardOutput.Meta, 30 | FileWriterStandardOutput.Content 31 | }; 32 | return standardOutputs.Select(o => CreateResolverFactoryMethod(o)).ToList(); 33 | } 34 | public static IXMindWriter CreateStandardWriterFactoryMethod( 35 | FileWriterStandardOutput standardOutputType, string? basePath) 36 | { 37 | IConfiguration xMindSettings = EnsureXMindSettings(); 38 | string fileName = standardOutputType switch 39 | { 40 | FileWriterStandardOutput.Manifest => xMindSettings[ManifestLabel], 41 | FileWriterStandardOutput.Meta => xMindSettings[MetaLabel], 42 | FileWriterStandardOutput.Content => xMindSettings[ContentLabel], 43 | _ => throw new InvalidOperationException( 44 | "CreateWriterFactoryMethod haven't assigned writer") 45 | }; 46 | bool useDefaultPath = basePath is null; 47 | IXMindWriter result; 48 | 49 | var writerConfig = new FileWriterOutputConfig(fileName, useDefaultPath); 50 | if (!useDefaultPath) 51 | { 52 | var xmindDefaultFileLocation = XMindConfigurationLoader.Configuration 53 | .GetOutputFilesLocations()[fileName]; 54 | writerConfig.SetBasePath(Path.Combine(basePath, xmindDefaultFileLocation)); 55 | } 56 | result = new FileWriter().SetOutput(writerConfig); 57 | return result; 58 | } 59 | 60 | private static IConfiguration EnsureXMindSettings() 61 | { 62 | var xMindSettings = XMindConfigurationLoader.Configuration.XMindConfigCollection; 63 | if (xMindSettings is null) 64 | { 65 | const string errorMessage = "XMindSettings are not provided"; 66 | Logger.Log.Error(errorMessage); 67 | throw new InvalidOperationException(errorMessage); 68 | } 69 | return xMindSettings; 70 | } 71 | 72 | public static Func>, IXMindWriter> CreateResolverFactoryMethod(FileWriterStandardOutput standardOutputType) => standardOutputType switch 73 | { 74 | FileWriterStandardOutput.Manifest => 75 | (ctx, writers) => ResolveWriterByOutputName(ctx, writers, ManifestLabel), 76 | FileWriterStandardOutput.Meta => 77 | (ctx, writers) => ResolveWriterByOutputName(ctx, writers, MetaLabel), 78 | FileWriterStandardOutput.Content => 79 | (ctx, writers) => ResolveWriterByOutputName(ctx, writers, ContentLabel), 80 | _ => throw new InvalidOperationException( 81 | "CreateResolverFactoryMethod haven't assigned binding") 82 | }; 83 | 84 | private static IXMindWriter ResolveWriterByOutputName( 85 | XMindWriterContext context, 86 | List> writers, 87 | string fileLabel) 88 | { 89 | IConfiguration xMindSettings = EnsureXMindSettings(); 90 | var file = xMindSettings[fileLabel]; 91 | var fileName = context.FileName; 92 | var writerFound = writers.FirstOrDefault( 93 | w => !string.IsNullOrWhiteSpace(fileName) 94 | && fileName!.Equals(file) // TODO: fix 95 | && w.GetOutputConfig().OutputName.Equals(file)); 96 | return writerFound; 97 | } 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /XMindAPI/XMindWriters/FileWriter/FileWriterOutputConfig.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using Microsoft.Extensions.Configuration; 5 | 6 | using XMindAPI.Configuration; 7 | 8 | namespace XMindAPI.Writers 9 | { 10 | public class FileWriterOutputConfig : IXMindWriterOutputConfig 11 | { 12 | private readonly bool _useDefaultPath; 13 | 14 | public string? Path { get; private set; } 15 | public string OutputName { get; set; } 16 | 17 | public FileWriterOutputConfig(string outputName) 18 | { 19 | OutputName = outputName; 20 | } 21 | 22 | /// 23 | /// 24 | /// 25 | /// name of the file 26 | /// build Path based on xmindsettings.json file, basePath (output:base) and file location (output:files:[outputname]) is used 27 | public FileWriterOutputConfig(string outputName, bool useDefaultPath) : this(outputName) 28 | { 29 | var xMindSettings = XMindConfigurationLoader.Configuration.XMindConfigCollection; 30 | if (useDefaultPath && xMindSettings is object) 31 | { 32 | var basePath = xMindSettings["output:base"]; 33 | Dictionary locations = XMindConfigurationLoader.Configuration.GetOutputFilesLocations(); 34 | var path = locations[outputName]; 35 | if (path != null) 36 | { 37 | Path = System.IO.Path.Combine(basePath, path); 38 | } 39 | } 40 | _useDefaultPath = useDefaultPath; 41 | } 42 | 43 | public IXMindWriterOutputConfig SetBasePath(string path) 44 | { 45 | if (_useDefaultPath) 46 | { 47 | throw new InvalidOperationException( 48 | "Not possible to assign new path, default path in use because of FileWriterOutputConfig.useDefaultPath"); 49 | } 50 | Path = path; 51 | return this; 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /XMindAPI/XMindWriters/FileWriter/FileWriterStandardOutput.cs: -------------------------------------------------------------------------------- 1 | namespace XMindAPI.Writers 2 | { 3 | public enum FileWriterStandardOutput 4 | { 5 | Manifest, 6 | Meta, 7 | Content 8 | } 9 | } -------------------------------------------------------------------------------- /XMindAPI/XMindWriters/IXMindWriter.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using System.Xml.Linq; 3 | 4 | namespace XMindAPI.Writers 5 | { 6 | public interface IXMindWriter where TConfig : IXMindWriterOutputConfig 7 | { 8 | Task WriteToStorage(XDocument document, string fileName); 9 | IXMindWriter SetOutput(IXMindWriterOutputConfig output); 10 | TConfig GetOutputConfig(); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /XMindAPI/XMindWriters/IXMindWriterOutputConfig.cs: -------------------------------------------------------------------------------- 1 | namespace XMindAPI.Writers 2 | { 3 | public interface IXMindWriterOutputConfig 4 | { 5 | string OutputName { get; set;} 6 | } 7 | } -------------------------------------------------------------------------------- /XMindAPI/XMindWriters/InMemoryWriter/InMemoryWriter.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Xml.Linq; 3 | using XMindAPI; 4 | using System.Collections.Generic; 5 | using System.Threading.Tasks; 6 | 7 | namespace XMindAPI.Writers 8 | { 9 | public class InMemoryWriter : IXMindWriter 10 | { 11 | private IXMindWriterOutputConfig _output; 12 | 13 | public Dictionary DocumentStorage { get; } = new Dictionary(); 14 | 15 | public InMemoryWriter() : this(new FileWriterOutputConfig("root")) 16 | { 17 | } 18 | public InMemoryWriter(IXMindWriterOutputConfig output) => _output = output; 19 | 20 | public Task WriteToStorage(XDocument document, string file) 21 | { 22 | DocumentStorage.Add(file, document); 23 | return Task.CompletedTask; 24 | } 25 | 26 | public IXMindWriterOutputConfig GetOutputConfig() 27 | { 28 | return _output; 29 | } 30 | 31 | public IXMindWriter SetOutput(IXMindWriterOutputConfig output) 32 | { 33 | _output = output; 34 | return this; 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /XMindAPI/XMindWriters/InMemoryWriter/InMemoryWriterOutputConfig.cs: -------------------------------------------------------------------------------- 1 | namespace XMindAPI.Writers 2 | { 3 | public class InMemoryWriterOutputConfig : IXMindWriterOutputConfig 4 | { 5 | 6 | public string OutputName { get; set; } 7 | 8 | public InMemoryWriterOutputConfig(string outputName) 9 | { 10 | OutputName = outputName; 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /XMindAPI/XMindWriters/LoggerWriter/LoggerWriter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using System.Xml.Linq; 4 | using XMindAPI.Infrastructure.Logging; 5 | 6 | namespace XMindAPI.Writers 7 | { 8 | public class LoggerWriter : IXMindWriter 9 | { 10 | public LoggerWriter() { } 11 | 12 | public IXMindWriterOutputConfig? Output { get; set; } 13 | 14 | public Task WriteToStorage(XDocument document, string file) 15 | { 16 | throw new NotImplementedException("Logger output is not implemented"); 17 | // Logger.Info( 18 | // $"IXMindWriter, OutputName: {Output.OutputName}{System.Environment.NewLine} fileName {file} {System.Environment.NewLine}{document.ToString()}"); 19 | } 20 | 21 | public IXMindWriterOutputConfig GetOutputConfig() 22 | { 23 | if (Output is null) 24 | { 25 | const string errorMessage = "Output config is not specified"; 26 | Logger.Log.Error(errorMessage); 27 | throw new InvalidOperationException(errorMessage); 28 | } 29 | return Output; 30 | } 31 | 32 | public IXMindWriter SetOutput(IXMindWriterOutputConfig output) 33 | { 34 | Output = output; 35 | return this; 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /XMindAPI/XMindWriters/LoggerWriter/LoggerWriterOutputConfig.cs: -------------------------------------------------------------------------------- 1 | namespace XMindAPI.Writers 2 | { 3 | public class LoggerWriterOutputConfig : IXMindWriterOutputConfig 4 | { 5 | 6 | public string OutputName { get; set; } 7 | 8 | public LoggerWriterOutputConfig(string outputName) 9 | { 10 | OutputName = outputName; 11 | } 12 | } 13 | } -------------------------------------------------------------------------------- /XMindAPI/XMindWriters/XMindWriterConfiguration.cs: -------------------------------------------------------------------------------- 1 | // using XMindAPI.Writers; 2 | using XMindAPI.Configuration; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using XMindAPI.Models; 7 | using XMindAPI.Infrastructure.Logging; 8 | 9 | namespace XMindAPI.Writers.Configuration 10 | { 11 | /// 12 | /// Controls writer configuration. 13 | /// 14 | public class XMindWriterConfiguration 15 | { 16 | internal Action, XMindWorkBook>? FinalizeAction { get; private set; } 17 | private readonly XMindConfiguration _xMindConfiguration; 18 | 19 | private List>? _writers; 20 | 21 | private List>, IXMindWriter>>? _criteria; 22 | 23 | public XMindWriterConfiguration(XMindConfiguration xMindConfiguration) 24 | { 25 | _xMindConfiguration = xMindConfiguration; 26 | // _criteria = new List>, IXMindWriter>>(); 27 | // _writers = new List>(); 28 | } 29 | 30 | public XMindConfiguration Writer(IXMindWriter writer) 31 | { 32 | _writers = new List> { writer }; 33 | return _xMindConfiguration; 34 | } 35 | 36 | public XMindConfiguration Writers(List> writers) 37 | { 38 | _writers = writers; 39 | // foreach (var writer in writers) 40 | // { 41 | // //Accept resolver 42 | // } 43 | return _xMindConfiguration; 44 | } 45 | 46 | public XMindConfiguration SetWriterBinding(List>, IXMindWriter>> criteria) 47 | { 48 | _criteria = criteria; 49 | return _xMindConfiguration; 50 | } 51 | 52 | public XMindConfiguration SetFinalizeAction(Action, XMindWorkBook> action) 53 | { 54 | FinalizeAction = action; 55 | return _xMindConfiguration; 56 | } 57 | 58 | internal IList> ResolveWriters(XMindWriterContext context) 59 | { 60 | if (_writers == null || !_writers.Any()) 61 | { 62 | throw new InvalidOperationException( 63 | "XMindConfiguration.ResolveWriter: Writer is not specified"); 64 | } 65 | if (_criteria == null) 66 | { 67 | Logger.Log.Warning( 68 | "XMindConfiguration.ResolveWriter: default writer is assigned"); 69 | return _writers.Take(1).ToList(); 70 | } 71 | var writersFound = _criteria.Select(w => w.Invoke(context, _writers)).Where(w => w != null); 72 | Logger.Log.DebugTrace( 73 | $"For context.FileName: {context.FileName} ResolveWriters.writersFound: {writersFound.Count()}"); 74 | return writersFound.ToList(); 75 | } 76 | 77 | internal void AddResolver(Func>, IXMindWriter> criteria) 78 | { 79 | _criteria?.Add(criteria); 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /XMindAPI/XMindWriters/XMindWriterContext.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Xml.Linq; 3 | 4 | namespace XMindAPI.Writers 5 | { 6 | public class XMindWriterContext 7 | { 8 | /// 9 | /// Provided collection 10 | /// 11 | /// 12 | public IEnumerable? FileEntries { get; set; } 13 | /// 14 | /// Destination file name 15 | /// 16 | /// 17 | public string? FileName { get; set; } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /XMindAPI/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NikiforovAll/xmindcsharp/e039f9dcd7efe82cf991f9dee5ba598e66cfadc6/XMindAPI/icon.png -------------------------------------------------------------------------------- /XMindAPI/xmindsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "namespace": "urn:xmind:xmap:xmlns:meta:2.0", 3 | "manifestNamespace": "urn:xmind:xmap:xmlns:manifest:1.0", 4 | "contentNamespace": "urn:xmind:xmap:xmlns:content:2.0", 5 | "metaNamespace": "urn:xmind:xmap:xmlns:meta:2.0", 6 | "xlinkNamespace": "http://www.w3.org/1999/xlink", 7 | "standardContentNamespaces": { 8 | "xsl": "http://www.w3.org/1999/XSL/Format", 9 | "svg": "http://www.w3.org/2000/svg", 10 | "xhtml": "http://www.w3.org/1999/xhtml" 11 | }, 12 | "output": { 13 | "definition":{ 14 | "manifest": "manifest.xml", 15 | "meta": "meta.xml", 16 | "content": "content.xml", 17 | "default": "default" 18 | }, 19 | "base": "xmind-output", 20 | "files": [ 21 | { 22 | "name": "manifest.xml", 23 | "location": "META-INF" 24 | }, 25 | { 26 | "name": "meta.xml", 27 | "location": "" 28 | }, 29 | { 30 | "name": "content.xml", 31 | "location": "" 32 | }, 33 | { 34 | "name": "default", 35 | "location": "" 36 | } 37 | ] 38 | } 39 | } -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-tactile -------------------------------------------------------------------------------- /docs/example_output1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NikiforovAll/xmindcsharp/e039f9dcd7efe82cf991f9dee5ba598e66cfadc6/docs/example_output1.png -------------------------------------------------------------------------------- /examples/.gitignore: -------------------------------------------------------------------------------- 1 | output 2 | ## Ignore Visual Studio temporary files, build results, and 3 | ## files generated by popular Visual Studio add-ons. 4 | ## 5 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 6 | 7 | # User-specific files 8 | *.rsuser 9 | *.suo 10 | *.user 11 | *.userosscache 12 | *.sln.docstates 13 | 14 | # User-specific files (MonoDevelop/Xamarin Studio) 15 | *.userprefs 16 | 17 | # Mono auto generated files 18 | mono_crash.* 19 | 20 | # Build results 21 | [Dd]ebug/ 22 | [Dd]ebugPublic/ 23 | [Rr]elease/ 24 | [Rr]eleases/ 25 | x64/ 26 | x86/ 27 | [Aa][Rr][Mm]/ 28 | [Aa][Rr][Mm]64/ 29 | bld/ 30 | [Bb]in/ 31 | [Oo]bj/ 32 | [Ll]og/ 33 | 34 | # Visual Studio 2015/2017 cache/options directory 35 | .vs/ 36 | # Uncomment if you have tasks that create the project's static files in wwwroot 37 | #wwwroot/ 38 | 39 | # Visual Studio 2017 auto generated files 40 | Generated\ Files/ 41 | 42 | # MSTest test Results 43 | [Tt]est[Rr]esult*/ 44 | [Bb]uild[Ll]og.* 45 | 46 | # NUNIT 47 | *.VisualState.xml 48 | TestResult.xml 49 | 50 | # Build Results of an ATL Project 51 | [Dd]ebugPS/ 52 | [Rr]eleasePS/ 53 | dlldata.c 54 | 55 | # Benchmark Results 56 | BenchmarkDotNet.Artifacts/ 57 | 58 | # .NET Core 59 | project.lock.json 60 | project.fragment.lock.json 61 | artifacts/ 62 | 63 | # StyleCop 64 | StyleCopReport.xml 65 | 66 | # Files built by Visual Studio 67 | *_i.c 68 | *_p.c 69 | *_h.h 70 | *.ilk 71 | *.meta 72 | *.obj 73 | *.iobj 74 | *.pch 75 | *.pdb 76 | *.ipdb 77 | *.pgc 78 | *.pgd 79 | *.rsp 80 | *.sbr 81 | *.tlb 82 | *.tli 83 | *.tlh 84 | *.tmp 85 | *.tmp_proj 86 | *_wpftmp.csproj 87 | *.log 88 | *.vspscc 89 | *.vssscc 90 | .builds 91 | *.pidb 92 | *.svclog 93 | *.scc 94 | 95 | # Chutzpah Test files 96 | _Chutzpah* 97 | 98 | # Visual C++ cache files 99 | ipch/ 100 | *.aps 101 | *.ncb 102 | *.opendb 103 | *.opensdf 104 | *.sdf 105 | *.cachefile 106 | *.VC.db 107 | *.VC.VC.opendb 108 | 109 | # Visual Studio profiler 110 | *.psess 111 | *.vsp 112 | *.vspx 113 | *.sap 114 | 115 | # Visual Studio Trace Files 116 | *.e2e 117 | 118 | # TFS 2012 Local Workspace 119 | $tf/ 120 | 121 | # Guidance Automation Toolkit 122 | *.gpState 123 | 124 | # ReSharper is a .NET coding add-in 125 | _ReSharper*/ 126 | *.[Rr]e[Ss]harper 127 | *.DotSettings.user 128 | 129 | # JustCode is a .NET coding add-in 130 | .JustCode 131 | 132 | # TeamCity is a build add-in 133 | _TeamCity* 134 | 135 | # DotCover is a Code Coverage Tool 136 | *.dotCover 137 | 138 | # AxoCover is a Code Coverage Tool 139 | .axoCover/* 140 | !.axoCover/settings.json 141 | 142 | # Visual Studio code coverage results 143 | *.coverage 144 | *.coveragexml 145 | 146 | # NCrunch 147 | _NCrunch_* 148 | .*crunch*.local.xml 149 | nCrunchTemp_* 150 | 151 | # MightyMoose 152 | *.mm.* 153 | AutoTest.Net/ 154 | 155 | # Web workbench (sass) 156 | .sass-cache/ 157 | 158 | # Installshield output folder 159 | [Ee]xpress/ 160 | 161 | # DocProject is a documentation generator add-in 162 | DocProject/buildhelp/ 163 | DocProject/Help/*.HxT 164 | DocProject/Help/*.HxC 165 | DocProject/Help/*.hhc 166 | DocProject/Help/*.hhk 167 | DocProject/Help/*.hhp 168 | DocProject/Help/Html2 169 | DocProject/Help/html 170 | 171 | # Click-Once directory 172 | publish/ 173 | 174 | # Publish Web Output 175 | *.[Pp]ublish.xml 176 | *.azurePubxml 177 | # Note: Comment the next line if you want to checkin your web deploy settings, 178 | # but database connection strings (with potential passwords) will be unencrypted 179 | *.pubxml 180 | *.publishproj 181 | 182 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 183 | # checkin your Azure Web App publish settings, but sensitive information contained 184 | # in these scripts will be unencrypted 185 | PublishScripts/ 186 | 187 | # NuGet Packages 188 | *.nupkg 189 | # The packages folder can be ignored because of Package Restore 190 | **/[Pp]ackages/* 191 | # except build/, which is used as an MSBuild target. 192 | !**/[Pp]ackages/build/ 193 | # Uncomment if necessary however generally it will be regenerated when needed 194 | #!**/[Pp]ackages/repositories.config 195 | # NuGet v3's project.json files produces more ignorable files 196 | *.nuget.props 197 | *.nuget.targets 198 | 199 | # Microsoft Azure Build Output 200 | csx/ 201 | *.build.csdef 202 | 203 | # Microsoft Azure Emulator 204 | ecf/ 205 | rcf/ 206 | 207 | # Windows Store app package directories and files 208 | AppPackages/ 209 | BundleArtifacts/ 210 | Package.StoreAssociation.xml 211 | _pkginfo.txt 212 | *.appx 213 | *.appxbundle 214 | *.appxupload 215 | 216 | # Visual Studio cache files 217 | # files ending in .cache can be ignored 218 | *.[Cc]ache 219 | # but keep track of directories ending in .cache 220 | !?*.[Cc]ache/ 221 | 222 | # Others 223 | ClientBin/ 224 | ~$* 225 | *~ 226 | *.dbmdl 227 | *.dbproj.schemaview 228 | *.jfm 229 | *.pfx 230 | *.publishsettings 231 | orleans.codegen.cs 232 | 233 | # Including strong name files can present a security risk 234 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 235 | #*.snk 236 | 237 | # Since there are multiple workflows, uncomment next line to ignore bower_components 238 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 239 | #bower_components/ 240 | 241 | # RIA/Silverlight projects 242 | Generated_Code/ 243 | 244 | # Backup & report files from converting an old project file 245 | # to a newer Visual Studio version. Backup files are not needed, 246 | # because we have git ;-) 247 | _UpgradeReport_Files/ 248 | Backup*/ 249 | UpgradeLog*.XML 250 | UpgradeLog*.htm 251 | ServiceFabricBackup/ 252 | *.rptproj.bak 253 | 254 | # SQL Server files 255 | *.mdf 256 | *.ldf 257 | *.ndf 258 | 259 | # Business Intelligence projects 260 | *.rdl.data 261 | *.bim.layout 262 | *.bim_*.settings 263 | *.rptproj.rsuser 264 | *- Backup*.rdl 265 | 266 | # Microsoft Fakes 267 | FakesAssemblies/ 268 | 269 | # GhostDoc plugin setting file 270 | *.GhostDoc.xml 271 | 272 | # Node.js Tools for Visual Studio 273 | .ntvs_analysis.dat 274 | node_modules/ 275 | 276 | # Visual Studio 6 build log 277 | *.plg 278 | 279 | # Visual Studio 6 workspace options file 280 | *.opt 281 | 282 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 283 | *.vbw 284 | 285 | # Visual Studio LightSwitch build output 286 | **/*.HTMLClient/GeneratedArtifacts 287 | **/*.DesktopClient/GeneratedArtifacts 288 | **/*.DesktopClient/ModelManifest.xml 289 | **/*.Server/GeneratedArtifacts 290 | **/*.Server/ModelManifest.xml 291 | _Pvt_Extensions 292 | 293 | # Paket dependency manager 294 | .paket/paket.exe 295 | paket-files/ 296 | 297 | # FAKE - F# Make 298 | .fake/ 299 | 300 | # CodeRush personal settings 301 | .cr/personal 302 | 303 | # Python Tools for Visual Studio (PTVS) 304 | __pycache__/ 305 | *.pyc 306 | 307 | # Cake - Uncomment if you are using it 308 | # tools/** 309 | # !tools/packages.config 310 | 311 | # Tabs Studio 312 | *.tss 313 | 314 | # Telerik's JustMock configuration file 315 | *.jmconfig 316 | 317 | # BizTalk build output 318 | *.btp.cs 319 | *.btm.cs 320 | *.odx.cs 321 | *.xsd.cs 322 | 323 | # OpenCover UI analysis results 324 | OpenCover/ 325 | 326 | # Azure Stream Analytics local run output 327 | ASALocalRun/ 328 | 329 | # MSBuild Binary and Structured Log 330 | *.binlog 331 | 332 | # NVidia Nsight GPU debugger configuration file 333 | *.nvuser 334 | 335 | # MFractors (Xamarin productivity tool) working folder 336 | .mfractor/ 337 | 338 | # Local History for Visual Studio 339 | .localhistory/ 340 | 341 | # BeatPulse healthcheck temp database 342 | healthchecksdb 343 | 344 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 345 | MigrationBackup/ 346 | 347 | ## 348 | ## Visual studio for Mac 349 | ## 350 | 351 | 352 | # globs 353 | Makefile.in 354 | *.userprefs 355 | *.usertasks 356 | config.make 357 | config.status 358 | aclocal.m4 359 | install-sh 360 | autom4te.cache/ 361 | *.tar.gz 362 | tarballs/ 363 | test-results/ 364 | 365 | # Mac bundle stuff 366 | *.dmg 367 | *.app 368 | 369 | # content below from: https://github.com/github/gitignore/blob/master/Global/macOS.gitignore 370 | # General 371 | .DS_Store 372 | .AppleDouble 373 | .LSOverride 374 | 375 | # Icon must end with two \r 376 | Icon 377 | 378 | 379 | # Thumbnails 380 | ._* 381 | 382 | # Files that might appear in the root of a volume 383 | .DocumentRevisions-V100 384 | .fseventsd 385 | .Spotlight-V100 386 | .TemporaryItems 387 | .Trashes 388 | .VolumeIcon.icns 389 | .com.apple.timemachine.donotpresent 390 | 391 | # Directories potentially created on remote AFP share 392 | .AppleDB 393 | .AppleDesktop 394 | Network Trash Folder 395 | Temporary Items 396 | .apdisk 397 | 398 | # content below from: https://github.com/github/gitignore/blob/master/Global/Windows.gitignore 399 | # Windows thumbnail cache files 400 | Thumbs.db 401 | ehthumbs.db 402 | ehthumbs_vista.db 403 | 404 | # Dump file 405 | *.stackdump 406 | 407 | # Folder config file 408 | [Dd]esktop.ini 409 | 410 | # Recycle Bin used on file shares 411 | $RECYCLE.BIN/ 412 | 413 | # Windows Installer files 414 | *.cab 415 | *.msi 416 | *.msix 417 | *.msm 418 | *.msp 419 | 420 | # Windows shortcuts 421 | *.lnk 422 | 423 | # JetBrains Rider 424 | .idea/ 425 | *.sln.iml 426 | 427 | ## 428 | ## Visual Studio Code 429 | ## 430 | .vscode/* 431 | !.vscode/settings.json 432 | !.vscode/tasks.json 433 | !.vscode/launch.json 434 | !.vscode/extensions.json 435 | -------------------------------------------------------------------------------- /examples/simple/Program.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System; 3 | using Microsoft.Extensions.Logging; 4 | using Microsoft.Extensions.DependencyInjection; 5 | using System.Threading.Tasks; 6 | using System.Diagnostics.Tracing; 7 | using System.Text; 8 | using XMindAPI; 9 | 10 | namespace simple 11 | { 12 | class Program 13 | { 14 | public static ILogger Logger { get; private set; } 15 | 16 | public static TextWriter Out { get; set; } = Console.Out; 17 | 18 | static async Task Main(string demo = "file") 19 | { 20 | var serviceProvider = new ServiceCollection() 21 | .AddLogging(configure => configure.AddConsole()) 22 | .BuildServiceProvider(); 23 | 24 | Logger = serviceProvider.GetService() 25 | .CreateLogger(); 26 | 27 | using var eventSourceListener = new EventSourceListener("XMind-XMindCsharpEventSource"); 28 | switch (demo) 29 | { 30 | case "file": 31 | await SaveWorkBookToFileSystem(); 32 | break; 33 | case "file-2": 34 | await SaveWorkBookToFileSystem_Example2(); 35 | break; 36 | case "memory": 37 | await InMemoryWorkBook(); 38 | break; 39 | } 40 | } 41 | 42 | private async static Task InMemoryWorkBook() 43 | { 44 | var book = new XMindConfiguration() 45 | .WithInMemoryWriter() 46 | .CreateWorkBook("test.xmind"); 47 | await book.Save(); 48 | } 49 | 50 | private static async Task SaveWorkBookToFileSystem() 51 | { 52 | // string basePath = Path.Combine(Path.GetTempPath(), "xmind-test"); 53 | string basePath = Path.Combine("xmind-test"); 54 | var bookName = "test.xmind"; 55 | Logger.LogInformation(default(EventId), $"Base path: ${Path.Combine(basePath, bookName)}"); 56 | var book = new XMindConfiguration() 57 | .WithFileWriter(basePath, zip: true) 58 | .CreateWorkBook(bookName); 59 | var sheet = book.GetPrimarySheet(); 60 | var rootTopic = sheet.GetRootTopic(); 61 | rootTopic.SetTitle("RootTopic"); 62 | await book.Save(); 63 | } 64 | private static async Task SaveWorkBookToFileSystem_Example2() 65 | { 66 | // string basePath = Path.Combine(Path.GetTempPath(), "xmind-test"); 67 | string basePath = Path.Combine("xmind-test"); 68 | var bookName = "test.xmind"; 69 | Logger.LogInformation(default(EventId), $"Base path: ${Path.Combine(basePath, bookName)}"); 70 | var book = new XMindConfiguration() 71 | .WithFileWriter(basePath, zip: true) 72 | .CreateWorkBook(bookName); 73 | var sheet = book.GetPrimarySheet(); 74 | var rootTopic = sheet.GetRootTopic(); 75 | rootTopic.SetTitle("RootTopic"); 76 | var newTopic = book.CreateTopic("ChildTopic"); 77 | rootTopic.Add(newTopic); 78 | newTopic.IsFolded = true; 79 | newTopic.HyperLink ="http://google.com"; 80 | newTopic.AddMarker("priority-1"); 81 | newTopic.AddMarker("task-half"); 82 | var foldedTopic = book.CreateTopic("Folded"); 83 | newTopic.Add(foldedTopic); 84 | newTopic.Add(foldedTopic); 85 | await book.Save(); 86 | } 87 | } 88 | sealed class EventSourceListener : EventListener 89 | { 90 | private readonly string _eventSourceName; 91 | private readonly StringBuilder _messageBuilder = new StringBuilder(); 92 | 93 | public EventSourceListener(string name) 94 | { 95 | _eventSourceName = name; 96 | } 97 | 98 | protected override void OnEventSourceCreated(EventSource eventSource) 99 | { 100 | base.OnEventSourceCreated(eventSource); 101 | 102 | if (eventSource.Name == _eventSourceName) 103 | { 104 | EnableEvents(eventSource, EventLevel.LogAlways, EventKeywords.All); 105 | } 106 | } 107 | protected override void OnEventWritten(EventWrittenEventArgs eventData) 108 | { 109 | base.OnEventWritten(eventData); 110 | 111 | string message; 112 | lock (_messageBuilder) 113 | { 114 | // _messageBuilder.Append("Event "); 115 | // _messageBuilder.Append(eventData.EventSource.Name); 116 | _messageBuilder.Append("\t"); 117 | _messageBuilder.Append(eventData.EventName); 118 | _messageBuilder.Append(" : "); 119 | _messageBuilder.AppendJoin(',', eventData.Payload); 120 | message = _messageBuilder.ToString(); 121 | _messageBuilder.Clear(); 122 | } 123 | Console.WriteLine(message); 124 | } 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /examples/simple/markers.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 118 | 141 | 142 | -------------------------------------------------------------------------------- /examples/simple/scripts/unzip-xmind.ps1: -------------------------------------------------------------------------------- 1 | Get-ChildItem ./xmind-test/tmp -Recurse | Remove-Item -Recurse 2 | Copy-Item "xmind-test\\test.xmind" -Destination "xmind-test\\tmp\\test.zip" 3 | Expand-Archive ./xmind-test/tmp/test.zip -DestinationPath ./xmind-test/tmp 4 | -------------------------------------------------------------------------------- /examples/simple/simple.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | Exe 16 | netcoreapp5.0 17 | latest 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /examples/simple/xmind-test/test.xmind: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NikiforovAll/xmindcsharp/e039f9dcd7efe82cf991f9dee5ba598e66cfadc6/examples/simple/xmind-test/test.xmind -------------------------------------------------------------------------------- /examples/simple/xmind-test/tmp/content.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | Patterns 9 | 10 | 11 | Software Craftsmanship 12 | 13 | 14 | .NET 15 | 16 | 17 | .NET Core 18 | 19 | 20 | System Design 21 | 22 | 23 | ASP.NET Core 24 | 25 | 26 | Unit Testing 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /examples/simple/xmind-test/tmp/manifest.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /examples/simple/xmind-test/tmp/meta.xml: -------------------------------------------------------------------------------- 1 |  2 | -------------------------------------------------------------------------------- /examples/simple/xmind-test/tmp/test.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NikiforovAll/xmindcsharp/e039f9dcd7efe82cf991f9dee5ba598e66cfadc6/examples/simple/xmind-test/tmp/test.zip -------------------------------------------------------------------------------- /xmindapicsharp.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.26124.0 5 | MinimumVisualStudioVersion = 15.0.26124.0 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "XMindAPI", "XMindAPI\XMindAPI.csproj", "{3B7F12AF-F1AA-442A-AB71-46205D7C9C59}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "XMindAPI.Tests", "XMindAPI.Tests\XMindAPI.Tests.csproj", "{B260573F-5688-40F9-9CAE-0069781BB596}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|Any CPU = Debug|Any CPU 13 | Debug|x64 = Debug|x64 14 | Debug|x86 = Debug|x86 15 | Release|Any CPU = Release|Any CPU 16 | Release|x64 = Release|x64 17 | Release|x86 = Release|x86 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 23 | {3B7F12AF-F1AA-442A-AB71-46205D7C9C59}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 24 | {3B7F12AF-F1AA-442A-AB71-46205D7C9C59}.Debug|Any CPU.Build.0 = Debug|Any CPU 25 | {3B7F12AF-F1AA-442A-AB71-46205D7C9C59}.Debug|x64.ActiveCfg = Debug|Any CPU 26 | {3B7F12AF-F1AA-442A-AB71-46205D7C9C59}.Debug|x64.Build.0 = Debug|Any CPU 27 | {3B7F12AF-F1AA-442A-AB71-46205D7C9C59}.Debug|x86.ActiveCfg = Debug|Any CPU 28 | {3B7F12AF-F1AA-442A-AB71-46205D7C9C59}.Debug|x86.Build.0 = Debug|Any CPU 29 | {3B7F12AF-F1AA-442A-AB71-46205D7C9C59}.Release|Any CPU.ActiveCfg = Release|Any CPU 30 | {3B7F12AF-F1AA-442A-AB71-46205D7C9C59}.Release|Any CPU.Build.0 = Release|Any CPU 31 | {3B7F12AF-F1AA-442A-AB71-46205D7C9C59}.Release|x64.ActiveCfg = Release|Any CPU 32 | {3B7F12AF-F1AA-442A-AB71-46205D7C9C59}.Release|x64.Build.0 = Release|Any CPU 33 | {3B7F12AF-F1AA-442A-AB71-46205D7C9C59}.Release|x86.ActiveCfg = Release|Any CPU 34 | {3B7F12AF-F1AA-442A-AB71-46205D7C9C59}.Release|x86.Build.0 = Release|Any CPU 35 | {B260573F-5688-40F9-9CAE-0069781BB596}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 36 | {B260573F-5688-40F9-9CAE-0069781BB596}.Debug|Any CPU.Build.0 = Debug|Any CPU 37 | {B260573F-5688-40F9-9CAE-0069781BB596}.Debug|x64.ActiveCfg = Debug|Any CPU 38 | {B260573F-5688-40F9-9CAE-0069781BB596}.Debug|x64.Build.0 = Debug|Any CPU 39 | {B260573F-5688-40F9-9CAE-0069781BB596}.Debug|x86.ActiveCfg = Debug|Any CPU 40 | {B260573F-5688-40F9-9CAE-0069781BB596}.Debug|x86.Build.0 = Debug|Any CPU 41 | {B260573F-5688-40F9-9CAE-0069781BB596}.Release|Any CPU.ActiveCfg = Release|Any CPU 42 | {B260573F-5688-40F9-9CAE-0069781BB596}.Release|Any CPU.Build.0 = Release|Any CPU 43 | {B260573F-5688-40F9-9CAE-0069781BB596}.Release|x64.ActiveCfg = Release|Any CPU 44 | {B260573F-5688-40F9-9CAE-0069781BB596}.Release|x64.Build.0 = Release|Any CPU 45 | {B260573F-5688-40F9-9CAE-0069781BB596}.Release|x86.ActiveCfg = Release|Any CPU 46 | {B260573F-5688-40F9-9CAE-0069781BB596}.Release|x86.Build.0 = Release|Any CPU 47 | EndGlobalSection 48 | EndGlobal 49 | --------------------------------------------------------------------------------