├── .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 [](https://ci.appveyor.com/project/NikiforovAll/xmindcsharp/branch/master) [](https://www.nuget.org/packages/xmindcsharp/) [](https://www.fuget.org/packages/XMindCSharp) [](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: 
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 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
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 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
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 |
--------------------------------------------------------------------------------