diff --git a/csharp/Platform.Converters.CLI.Tests/ConverterTests.cs b/csharp/Platform.Converters.CLI.Tests/ConverterTests.cs
new file mode 100644
index 0000000..7e21c6c
--- /dev/null
+++ b/csharp/Platform.Converters.CLI.Tests/ConverterTests.cs
@@ -0,0 +1,164 @@
+using System;
+using System.IO;
+using System.Text;
+using System.Threading.Tasks;
+using Platform.Converters.Plugins.Examples;
+using Xunit;
+
+namespace Platform.Converters.CLI.Tests
+{
+ ///
+ /// Tests for converter implementations.
+ /// Тесты для реализаций конвертеров.
+ ///
+ public class ConverterTests
+ {
+ ///
+ /// Tests JSON to XML conversion.
+ /// Тестирует конверсию JSON в XML.
+ ///
+ [Fact]
+ public async Task JsonToXmlConverter_ShouldConvertJsonToXml()
+ {
+ // Arrange
+ var converter = new JsonToXmlConverter();
+ var jsonContent = """{"name": "test", "value": 42, "active": true}""";
+ var sourceStream = new MemoryStream(Encoding.UTF8.GetBytes(jsonContent));
+ var targetStream = new MemoryStream();
+
+ // Act
+ await converter.ConvertAsync(sourceStream, targetStream, ".json", ".xml");
+
+ // Assert
+ targetStream.Position = 0;
+ var result = Encoding.UTF8.GetString(targetStream.ToArray());
+
+ Assert.Contains("test", result);
+ Assert.Contains("42", result);
+ Assert.Contains("true", result);
+ }
+
+ ///
+ /// Tests XML to JSON conversion.
+ /// Тестирует конверсию XML в JSON.
+ ///
+ [Fact]
+ public async Task XmlToJsonConverter_ShouldConvertXmlToJson()
+ {
+ // Arrange
+ var converter = new XmlToJsonConverter();
+ var xmlContent = """
+
+
+ test
+ 42
+ true
+
+ """;
+ var sourceStream = new MemoryStream(Encoding.UTF8.GetBytes(xmlContent));
+ var targetStream = new MemoryStream();
+
+ // Act
+ await converter.ConvertAsync(sourceStream, targetStream, ".xml", ".json");
+
+ // Assert
+ targetStream.Position = 0;
+ var result = Encoding.UTF8.GetString(targetStream.ToArray());
+
+ Assert.Contains("\"name\": \"test\"", result);
+ Assert.Contains("\"value\": 42", result);
+ Assert.Contains("\"active\": true", result);
+ }
+
+ ///
+ /// Tests text case conversion to uppercase.
+ /// Тестирует конверсию регистра текста в верхний.
+ ///
+ [Fact]
+ public async Task TextToUppercaseConverter_ShouldConvertToUppercase()
+ {
+ // Arrange
+ var converter = new TextToUppercaseConverter();
+ var textContent = "Hello World!\nThis is a test.";
+ var sourceStream = new MemoryStream(Encoding.UTF8.GetBytes(textContent));
+ var targetStream = new MemoryStream();
+
+ // Act
+ await converter.ConvertAsync(sourceStream, targetStream, ".txt", ".txt");
+
+ // Assert
+ targetStream.Position = 0;
+ var result = Encoding.UTF8.GetString(targetStream.ToArray());
+
+ Assert.Contains("HELLO WORLD!", result);
+ Assert.Contains("THIS IS A TEST.", result);
+ }
+
+ ///
+ /// Tests text case conversion to lowercase.
+ /// Тестирует конверсию регистра текста в нижний.
+ ///
+ [Fact]
+ public async Task TextToLowercaseConverter_ShouldConvertToLowercase()
+ {
+ // Arrange
+ var converter = new TextToLowercaseConverter();
+ var textContent = "HELLO WORLD!\nTHIS IS A TEST.";
+ var sourceStream = new MemoryStream(Encoding.UTF8.GetBytes(textContent));
+ var targetStream = new MemoryStream();
+
+ // Act
+ await converter.ConvertAsync(sourceStream, targetStream, ".txt", ".txt");
+
+ // Assert
+ targetStream.Position = 0;
+ var result = Encoding.UTF8.GetString(targetStream.ToArray());
+
+ Assert.Contains("hello world!", result);
+ Assert.Contains("this is a test.", result);
+ }
+
+ ///
+ /// Tests converter capability checking.
+ /// Тестирует проверку возможностей конвертера.
+ ///
+ [Fact]
+ public void Converters_CanConvert_ShouldReturnCorrectCapabilities()
+ {
+ // Arrange
+ var jsonToXml = new JsonToXmlConverter();
+ var xmlToJson = new XmlToJsonConverter();
+ var textToUpper = new TextToUppercaseConverter();
+
+ // Act & Assert
+ Assert.True(jsonToXml.CanConvert(".json", ".xml"));
+ Assert.False(jsonToXml.CanConvert(".xml", ".json"));
+ Assert.False(jsonToXml.CanConvert(".txt", ".xml"));
+
+ Assert.True(xmlToJson.CanConvert(".xml", ".json"));
+ Assert.False(xmlToJson.CanConvert(".json", ".xml"));
+ Assert.False(xmlToJson.CanConvert(".txt", ".json"));
+
+ Assert.True(textToUpper.CanConvert(".txt", ".txt"));
+ Assert.True(textToUpper.CanConvert(".text", ".txt"));
+ Assert.False(textToUpper.CanConvert(".json", ".txt"));
+ }
+
+ ///
+ /// Tests that converters throw exceptions for unsupported conversions.
+ /// Тестирует что конвертеры выбрасывают исключения для неподдерживаемых конверсий.
+ ///
+ [Fact]
+ public async Task Converters_UnsupportedConversion_ShouldThrowException()
+ {
+ // Arrange
+ var converter = new JsonToXmlConverter();
+ var sourceStream = new MemoryStream();
+ var targetStream = new MemoryStream();
+
+ // Act & Assert
+ await Assert.ThrowsAsync(
+ () => converter.ConvertAsync(sourceStream, targetStream, ".xml", ".json"));
+ }
+ }
+}
\ No newline at end of file
diff --git a/csharp/Platform.Converters.CLI.Tests/Platform.Converters.CLI.Tests.csproj b/csharp/Platform.Converters.CLI.Tests/Platform.Converters.CLI.Tests.csproj
new file mode 100644
index 0000000..53ce556
--- /dev/null
+++ b/csharp/Platform.Converters.CLI.Tests/Platform.Converters.CLI.Tests.csproj
@@ -0,0 +1,29 @@
+
+
+
+ net8
+ false
+ true
+ latest
+ enable
+
+
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers
+
+
+ all
+ runtime; build; native; contentfiles; analyzers
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/csharp/Platform.Converters.CLI.Tests/PluginLoaderTests.cs b/csharp/Platform.Converters.CLI.Tests/PluginLoaderTests.cs
new file mode 100644
index 0000000..1205256
--- /dev/null
+++ b/csharp/Platform.Converters.CLI.Tests/PluginLoaderTests.cs
@@ -0,0 +1,87 @@
+using System;
+using System.IO;
+using System.Linq;
+using Platform.Converters.CLI;
+using Platform.Converters.Plugins.Examples;
+using Xunit;
+
+namespace Platform.Converters.CLI.Tests
+{
+ ///
+ /// Tests for the PluginLoader class.
+ /// Тесты для класса PluginLoader.
+ ///
+ public class PluginLoaderTests
+ {
+ ///
+ /// Tests that converters can be found by file extensions.
+ /// Тестирует что конвертеры могут быть найдены по расширениям файлов.
+ ///
+ [Fact]
+ public void FindConverters_ShouldReturnCompatibleConverters()
+ {
+ // Arrange
+ var loader = new PluginLoader();
+ var jsonToXmlConverter = new JsonToXmlConverter();
+ var xmlToJsonConverter = new XmlToJsonConverter();
+ var textConverter = new TextToUppercaseConverter();
+
+ // Simulate loaded converters by using reflection to add them
+ var loadedConvertersField = typeof(PluginLoader)
+ .GetField("_loadedConverters", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
+ var loadedConverters = (System.Collections.Generic.List)loadedConvertersField!.GetValue(loader)!;
+
+ loadedConverters.Add(jsonToXmlConverter);
+ loadedConverters.Add(xmlToJsonConverter);
+ loadedConverters.Add(textConverter);
+
+ // Act
+ var jsonToXmlConverters = loader.FindConverters(".json", ".xml");
+ var xmlToJsonConverters = loader.FindConverters(".xml", ".json");
+ var textConverters = loader.FindConverters(".txt", ".txt");
+ var noConverters = loader.FindConverters(".pdf", ".doc");
+
+ // Assert
+ Assert.Single(jsonToXmlConverters);
+ Assert.Contains(jsonToXmlConverter, jsonToXmlConverters);
+
+ Assert.Single(xmlToJsonConverters);
+ Assert.Contains(xmlToJsonConverter, xmlToJsonConverters);
+
+ Assert.Single(textConverters);
+ Assert.Contains(textConverter, textConverters);
+
+ Assert.Empty(noConverters);
+ }
+
+ ///
+ /// Tests that plugin loading handles non-existent directories gracefully.
+ /// Тестирует что загрузка плагинов корректно обрабатывает несуществующие директории.
+ ///
+ [Fact]
+ public void LoadPlugins_WithNonExistentDirectory_ShouldNotThrow()
+ {
+ // Arrange
+ var loader = new PluginLoader();
+ var nonExistentPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString());
+
+ // Act & Assert
+ var exception = Record.Exception(() => loader.LoadPlugins(nonExistentPath));
+ Assert.Null(exception);
+ }
+
+ ///
+ /// Tests that the loader starts with no converters loaded.
+ /// Тестирует что загрузчик начинает без загруженных конвертеров.
+ ///
+ [Fact]
+ public void LoadedConverters_InitialState_ShouldBeEmpty()
+ {
+ // Arrange & Act
+ var loader = new PluginLoader();
+
+ // Assert
+ Assert.Empty(loader.LoadedConverters);
+ }
+ }
+}
\ No newline at end of file
diff --git a/csharp/Platform.Converters.CLI/Platform.Converters.CLI.csproj b/csharp/Platform.Converters.CLI/Platform.Converters.CLI.csproj
new file mode 100644
index 0000000..ed366d9
--- /dev/null
+++ b/csharp/Platform.Converters.CLI/Platform.Converters.CLI.csproj
@@ -0,0 +1,21 @@
+
+
+
+ Exe
+ net8
+ Platform.Converters.CLI
+ Platform.Converters.CLI
+ CLI tool for file conversion with plugin support
+ Konstantin Diachenko
+ Konstantin Diachenko
+ 0.1.0
+ latest
+ enable
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/csharp/Platform.Converters.CLI/PluginLoader.cs b/csharp/Platform.Converters.CLI/PluginLoader.cs
new file mode 100644
index 0000000..40fe1ae
--- /dev/null
+++ b/csharp/Platform.Converters.CLI/PluginLoader.cs
@@ -0,0 +1,129 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Reflection;
+using System.Runtime.Loader;
+
+namespace Platform.Converters.CLI
+{
+ ///
+ /// Loads file converter plugins from DLL files.
+ /// Загружает плагины конвертеров файлов из DLL файлов.
+ ///
+ public class PluginLoader
+ {
+ private readonly List _loadedConverters = new();
+
+ ///
+ /// Gets all loaded file converters.
+ /// Получает все загруженные конвертеры файлов.
+ ///
+ public IReadOnlyList LoadedConverters => _loadedConverters.AsReadOnly();
+
+ ///
+ /// Loads plugins from the specified directory.
+ /// Загружает плагины из указанной директории.
+ ///
+ /// The directory containing plugin DLL files.Директория, содержащая DLL файлы плагинов.
+ public void LoadPlugins(string pluginDirectory)
+ {
+ if (!Directory.Exists(pluginDirectory))
+ {
+ Console.WriteLine($"Plugin directory '{pluginDirectory}' does not exist.");
+ return;
+ }
+
+ var dllFiles = Directory.GetFiles(pluginDirectory, "*.dll");
+
+ foreach (var dllFile in dllFiles)
+ {
+ try
+ {
+ LoadPlugin(dllFile);
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"Failed to load plugin from '{dllFile}': {ex.Message}");
+ }
+ }
+
+ Console.WriteLine($"Loaded {_loadedConverters.Count} converter(s) from {dllFiles.Length} plugin file(s).");
+ }
+
+ ///
+ /// Loads a single plugin from the specified DLL file.
+ /// Загружает один плагин из указанного DLL файла.
+ ///
+ /// The path to the plugin DLL file.Путь к DLL файлу плагина.
+ public void LoadPlugin(string pluginPath)
+ {
+ if (!File.Exists(pluginPath))
+ {
+ throw new FileNotFoundException($"Plugin file '{pluginPath}' not found.");
+ }
+
+ var loadContext = new AssemblyLoadContext(Path.GetFileNameWithoutExtension(pluginPath), true);
+ var assembly = loadContext.LoadFromAssemblyPath(pluginPath);
+
+ var converterTypes = assembly.GetTypes()
+ .Where(t => t.IsClass && !t.IsAbstract && typeof(IFileConverter).IsAssignableFrom(t))
+ .ToList();
+
+ foreach (var converterType in converterTypes)
+ {
+ try
+ {
+ if (Activator.CreateInstance(converterType) is IFileConverter converter)
+ {
+ _loadedConverters.Add(converter);
+ Console.WriteLine($"Loaded converter: {converter.Name} - {converter.Description}");
+ }
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"Failed to instantiate converter '{converterType.Name}': {ex.Message}");
+ }
+ }
+ }
+
+ ///
+ /// Finds converters that can handle the specified file extension conversion.
+ /// Находит конвертеры, которые могут выполнить преобразование указанных расширений файлов.
+ ///
+ /// The source file extension.Расширение исходного файла.
+ /// The target file extension.Расширение целевого файла.
+ /// A list of compatible converters.Список совместимых конвертеров.
+ public List FindConverters(string sourceExtension, string targetExtension)
+ {
+ return _loadedConverters
+ .Where(c => c.CanConvert(sourceExtension, targetExtension))
+ .ToList();
+ }
+
+ ///
+ /// Lists all available converters with their supported formats.
+ /// Перечисляет все доступные конвертеры с их поддерживаемыми форматами.
+ ///
+ public void ListConverters()
+ {
+ if (_loadedConverters.Count == 0)
+ {
+ Console.WriteLine("No converters loaded.");
+ return;
+ }
+
+ Console.WriteLine("Available converters:");
+ Console.WriteLine();
+
+ foreach (var converter in _loadedConverters)
+ {
+ Console.WriteLine($"Name: {converter.Name}");
+ Console.WriteLine($"Description: {converter.Description}");
+ Console.WriteLine($"Source formats: {string.Join(", ", converter.SupportedSourceExtensions)}");
+ Console.WriteLine($"Target formats: {string.Join(", ", converter.SupportedTargetExtensions)}");
+ Console.WriteLine();
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/csharp/Platform.Converters.CLI/Program.cs b/csharp/Platform.Converters.CLI/Program.cs
new file mode 100644
index 0000000..1171f1f
--- /dev/null
+++ b/csharp/Platform.Converters.CLI/Program.cs
@@ -0,0 +1,198 @@
+using System;
+using System.IO;
+using System.Linq;
+using System.Threading.Tasks;
+
+namespace Platform.Converters.CLI
+{
+ ///
+ /// Main program class for the CLI tool.
+ /// Основной класс программы для CLI инструмента.
+ ///
+ public class Program
+ {
+ ///
+ /// Entry point for the CLI application.
+ /// Точка входа для CLI приложения.
+ ///
+ /// Command line arguments.Аргументы командной строки.
+ /// Exit code.Код завершения.
+ public static async Task Main(string[] args)
+ {
+ var pluginLoader = new PluginLoader();
+
+ // Load plugins from default directory or environment variable
+ var pluginDirectory = Environment.GetEnvironmentVariable("CONVERTERS_PLUGIN_DIR")
+ ?? Path.Combine(AppContext.BaseDirectory, "plugins");
+
+ if (Directory.Exists(pluginDirectory))
+ {
+ pluginLoader.LoadPlugins(pluginDirectory);
+ }
+
+ if (args.Length == 0)
+ {
+ ShowHelp();
+ return 0;
+ }
+
+ var command = args[0].ToLowerInvariant();
+
+ try
+ {
+ return command switch
+ {
+ "convert" => await HandleConvertCommand(args, pluginLoader),
+ "list" => HandleListCommand(pluginLoader),
+ "load" => HandleLoadCommand(args, pluginLoader),
+ "help" or "--help" or "-h" => ShowHelp(),
+ _ => ShowUnknownCommandError(command)
+ };
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"Error: {ex.Message}");
+ return 1;
+ }
+ }
+
+ private static async Task HandleConvertCommand(string[] args, PluginLoader pluginLoader)
+ {
+ if (args.Length < 3)
+ {
+ Console.WriteLine("Usage: convert [--converter ]");
+ return 1;
+ }
+
+ var source = args[1];
+ var target = args[2];
+ string? converterName = null;
+
+ // Parse optional converter argument
+ for (int i = 3; i < args.Length - 1; i++)
+ {
+ if (args[i] == "--converter" || args[i] == "-c")
+ {
+ converterName = args[i + 1];
+ break;
+ }
+ }
+
+ if (!File.Exists(source))
+ {
+ Console.WriteLine($"Source file '{source}' not found.");
+ return 1;
+ }
+
+ var sourceExtension = Path.GetExtension(source).ToLowerInvariant();
+ var targetExtension = Path.GetExtension(target).ToLowerInvariant();
+
+ var availableConverters = pluginLoader.FindConverters(sourceExtension, targetExtension);
+
+ if (availableConverters.Count == 0)
+ {
+ Console.WriteLine($"No converter found for {sourceExtension} -> {targetExtension}");
+ Console.WriteLine("Use 'list' command to see available converters.");
+ return 1;
+ }
+
+ var selectedConverter = availableConverters.First();
+
+ if (!string.IsNullOrEmpty(converterName))
+ {
+ var namedConverter = availableConverters.FirstOrDefault(c =>
+ c.Name.Equals(converterName, StringComparison.OrdinalIgnoreCase));
+
+ if (namedConverter == null)
+ {
+ Console.WriteLine($"Converter '{converterName}' not found or not compatible.");
+ Console.WriteLine("Available converters for this conversion:");
+ foreach (var conv in availableConverters)
+ {
+ Console.WriteLine($" - {conv.Name}: {conv.Description}");
+ }
+ return 1;
+ }
+
+ selectedConverter = namedConverter;
+ }
+
+ Console.WriteLine($"Converting '{source}' to '{target}' using '{selectedConverter.Name}'...");
+
+ try
+ {
+ // Ensure target directory exists
+ var targetDir = Path.GetDirectoryName(target);
+ if (!string.IsNullOrEmpty(targetDir) && !Directory.Exists(targetDir))
+ {
+ Directory.CreateDirectory(targetDir);
+ }
+
+ await selectedConverter.ConvertAsync(source, target);
+ Console.WriteLine("Conversion completed successfully.");
+ return 0;
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"Conversion failed: {ex.Message}");
+ return 1;
+ }
+ }
+
+ private static int HandleListCommand(PluginLoader pluginLoader)
+ {
+ pluginLoader.ListConverters();
+ return 0;
+ }
+
+ private static int HandleLoadCommand(string[] args, PluginLoader pluginLoader)
+ {
+ if (args.Length < 2)
+ {
+ Console.WriteLine("Usage: load ");
+ return 1;
+ }
+
+ var pluginFile = args[1];
+
+ if (!File.Exists(pluginFile))
+ {
+ Console.WriteLine($"Plugin file '{pluginFile}' not found.");
+ return 1;
+ }
+
+ pluginLoader.LoadPlugin(pluginFile);
+ Console.WriteLine($"Plugin loaded successfully from '{Path.GetFileName(pluginFile)}'");
+ return 0;
+ }
+
+ private static int ShowHelp()
+ {
+ Console.WriteLine("Platform Converters CLI - File conversion tool with plugin support");
+ Console.WriteLine();
+ Console.WriteLine("Usage:");
+ Console.WriteLine(" convert [--converter ] Convert a file");
+ Console.WriteLine(" list List available converters");
+ Console.WriteLine(" load Load a plugin DLL");
+ Console.WriteLine(" help Show this help");
+ Console.WriteLine();
+ Console.WriteLine("Examples:");
+ Console.WriteLine(" convert input.json output.xml");
+ Console.WriteLine(" convert input.json output.xml --converter \"JSON to XML Converter\"");
+ Console.WriteLine(" list");
+ Console.WriteLine(" load MyConverter.dll");
+ Console.WriteLine();
+ Console.WriteLine("Environment Variables:");
+ Console.WriteLine(" CONVERTERS_PLUGIN_DIR Directory to load plugins from (default: ./plugins)");
+
+ return 0;
+ }
+
+ private static int ShowUnknownCommandError(string command)
+ {
+ Console.WriteLine($"Unknown command: {command}");
+ Console.WriteLine("Use 'help' to see available commands.");
+ return 1;
+ }
+ }
+}
\ No newline at end of file
diff --git a/csharp/Platform.Converters.CLI/README.md b/csharp/Platform.Converters.CLI/README.md
new file mode 100644
index 0000000..d47539f
--- /dev/null
+++ b/csharp/Platform.Converters.CLI/README.md
@@ -0,0 +1,137 @@
+# Platform Converters CLI
+
+A command-line tool for file conversion with plugin support, implementing the solution for [issue #67](https://github.com/linksplatform/Converters/issues/67).
+
+## Features
+
+- **Plugin System**: Dynamically load converter DLL plugins
+- **Multiple Converters**: Support for various file format conversions
+- **CLI Interface**: Easy-to-use command-line interface
+- **Extensible**: Add new converters by implementing the `IFileConverter` interface
+
+## Installation
+
+Build the project using .NET 8:
+
+```bash
+dotnet build Platform.Converters.CLI.csproj
+```
+
+## Usage
+
+### Basic Commands
+
+```bash
+# Show help
+dotnet Platform.Converters.CLI.dll help
+
+# List available converters
+dotnet Platform.Converters.CLI.dll list
+
+# Convert a file
+dotnet Platform.Converters.CLI.dll convert input.json output.xml
+
+# Convert with specific converter
+dotnet Platform.Converters.CLI.dll convert input.json output.xml --converter "JSON to XML Converter"
+
+# Load a plugin
+dotnet Platform.Converters.CLI.dll load MyPlugin.dll
+```
+
+### Environment Variables
+
+- `CONVERTERS_PLUGIN_DIR`: Directory to load plugins from (default: `./plugins`)
+
+## Plugin Development
+
+### Creating a Converter Plugin
+
+1. Create a new .NET library project
+2. Reference the `Platform.Converters` project
+3. Implement the `IFileConverter` interface:
+
+```csharp
+using Platform.Converters;
+using System.IO;
+using System.Threading.Tasks;
+
+public class MyConverter : IFileConverter
+{
+ public string Name => "My Custom Converter";
+ public string Description => "Converts format A to format B";
+ public string[] SupportedSourceExtensions => new[] { ".a" };
+ public string[] SupportedTargetExtensions => new[] { ".b" };
+
+ public bool CanConvert(string sourceExtension, string targetExtension)
+ {
+ return sourceExtension == ".a" && targetExtension == ".b";
+ }
+
+ public async Task ConvertAsync(string sourcePath, string targetPath)
+ {
+ // File-based conversion implementation
+ }
+
+ public async Task ConvertAsync(Stream sourceStream, Stream targetStream,
+ string sourceExtension, string targetExtension)
+ {
+ // Stream-based conversion implementation
+ }
+}
+```
+
+4. Build the plugin DLL
+5. Copy to the plugins directory or load using the `load` command
+
+## Built-in Converters
+
+The example plugins project includes:
+
+- **JSON to XML Converter**: Converts JSON files to XML format
+- **XML to JSON Converter**: Converts XML files to JSON format
+- **Text to Uppercase Converter**: Converts text files to uppercase
+- **Text to Lowercase Converter**: Converts text files to lowercase
+
+## Architecture
+
+```
+Platform.Converters.CLI/
+├── Program.cs # Main CLI entry point
+├── PluginLoader.cs # Plugin loading system
+└── IFileConverter.cs # Converter interface (in Platform.Converters)
+
+Platform.Converters.Plugins.Examples/
+├── JsonToXmlConverter.cs # JSON ↔ XML conversion
+├── XmlToJsonConverter.cs
+├── TextCaseConverter.cs # Text case conversion
+└── ...
+
+Platform.Converters.CLI.Tests/
+├── PluginLoaderTests.cs # Unit tests for plugin loader
+├── ConverterTests.cs # Unit tests for converters
+└── ...
+```
+
+## Examples
+
+See the `examples/` directory for sample files and usage scenarios.
+
+## Testing
+
+Run the test suite:
+
+```bash
+dotnet test Platform.Converters.CLI.Tests/Platform.Converters.CLI.Tests.csproj
+```
+
+## Contributing
+
+1. Fork the repository
+2. Create a feature branch
+3. Add your converter implementation
+4. Add tests for your converter
+5. Submit a pull request
+
+## License
+
+This project follows the same license as the Platform.Converters project.
\ No newline at end of file
diff --git a/csharp/Platform.Converters.Plugins.Examples/JsonToXmlConverter.cs b/csharp/Platform.Converters.Plugins.Examples/JsonToXmlConverter.cs
new file mode 100644
index 0000000..e74723d
--- /dev/null
+++ b/csharp/Platform.Converters.Plugins.Examples/JsonToXmlConverter.cs
@@ -0,0 +1,133 @@
+using System;
+using System.IO;
+using System.Text.Json;
+using System.Threading.Tasks;
+using System.Xml;
+using System.Xml.Linq;
+
+namespace Platform.Converters.Plugins.Examples
+{
+ ///
+ /// Converts JSON files to XML format.
+ /// Конвертирует JSON файлы в XML формат.
+ ///
+ public class JsonToXmlConverter : IFileConverter
+ {
+ ///
+ public string Name => "JSON to XML Converter";
+
+ ///
+ public string Description => "Converts JSON files to XML format";
+
+ ///
+ public string[] SupportedSourceExtensions => new[] { ".json" };
+
+ ///
+ public string[] SupportedTargetExtensions => new[] { ".xml" };
+
+ ///
+ public bool CanConvert(string sourceExtension, string targetExtension)
+ {
+ return sourceExtension.Equals(".json", StringComparison.OrdinalIgnoreCase) &&
+ targetExtension.Equals(".xml", StringComparison.OrdinalIgnoreCase);
+ }
+
+ ///
+ public async Task ConvertAsync(string sourcePath, string targetPath)
+ {
+ using var sourceStream = File.OpenRead(sourcePath);
+ using var targetStream = File.Create(targetPath);
+
+ await ConvertAsync(sourceStream, targetStream, ".json", ".xml");
+ }
+
+ ///
+ public async Task ConvertAsync(Stream sourceStream, Stream targetStream, string sourceExtension, string targetExtension)
+ {
+ if (!CanConvert(sourceExtension, targetExtension))
+ {
+ throw new NotSupportedException($"Conversion from {sourceExtension} to {targetExtension} is not supported.");
+ }
+
+ var jsonDocument = await JsonDocument.ParseAsync(sourceStream);
+ var xmlDocument = ConvertJsonToXml(jsonDocument.RootElement, "root");
+
+ await using var writer = XmlWriter.Create(targetStream, new XmlWriterSettings
+ {
+ Async = true,
+ Indent = true,
+ IndentChars = " ",
+ CloseOutput = false
+ });
+
+ await xmlDocument.WriteToAsync(writer, default);
+ }
+
+ private static XDocument ConvertJsonToXml(JsonElement element, string elementName)
+ {
+ var xmlDoc = new XDocument();
+ xmlDoc.Add(CreateXElement(element, elementName));
+ return xmlDoc;
+ }
+
+ private static XElement CreateXElement(JsonElement element, string name)
+ {
+ var xElement = new XElement(SanitizeXmlName(name));
+
+ switch (element.ValueKind)
+ {
+ case JsonValueKind.Object:
+ foreach (var property in element.EnumerateObject())
+ {
+ xElement.Add(CreateXElement(property.Value, property.Name));
+ }
+ break;
+
+ case JsonValueKind.Array:
+ var index = 0;
+ foreach (var item in element.EnumerateArray())
+ {
+ xElement.Add(CreateXElement(item, $"item_{index++}"));
+ }
+ break;
+
+ case JsonValueKind.String:
+ xElement.Value = element.GetString() ?? "";
+ break;
+
+ case JsonValueKind.Number:
+ xElement.Value = element.ToString();
+ break;
+
+ case JsonValueKind.True:
+ case JsonValueKind.False:
+ xElement.Value = element.GetBoolean().ToString().ToLowerInvariant();
+ break;
+
+ case JsonValueKind.Null:
+ xElement.SetAttributeValue("null", "true");
+ break;
+
+ default:
+ xElement.Value = element.ToString();
+ break;
+ }
+
+ return xElement;
+ }
+
+ private static string SanitizeXmlName(string name)
+ {
+ // Replace invalid XML name characters with underscores
+ var sanitized = System.Text.RegularExpressions.Regex.Replace(name, @"[^\w\-_.]", "_");
+
+ // Ensure name starts with letter or underscore
+ if (!char.IsLetter(sanitized[0]) && sanitized[0] != '_')
+ {
+ sanitized = "_" + sanitized;
+ }
+
+ return sanitized;
+ }
+ }
+}
\ No newline at end of file
diff --git a/csharp/Platform.Converters.Plugins.Examples/Platform.Converters.Plugins.Examples.csproj b/csharp/Platform.Converters.Plugins.Examples/Platform.Converters.Plugins.Examples.csproj
new file mode 100644
index 0000000..3ab2a26
--- /dev/null
+++ b/csharp/Platform.Converters.Plugins.Examples/Platform.Converters.Plugins.Examples.csproj
@@ -0,0 +1,23 @@
+
+
+
+ net8
+ Platform.Converters.Plugins.Examples
+ Platform.Converters.Plugins.Examples
+ Example file converter plugins
+ Konstantin Diachenko
+ Konstantin Diachenko
+ 0.1.0
+ latest
+ enable
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/csharp/Platform.Converters.Plugins.Examples/TextCaseConverter.cs b/csharp/Platform.Converters.Plugins.Examples/TextCaseConverter.cs
new file mode 100644
index 0000000..0ab6ff2
--- /dev/null
+++ b/csharp/Platform.Converters.Plugins.Examples/TextCaseConverter.cs
@@ -0,0 +1,108 @@
+using System;
+using System.IO;
+using System.Threading.Tasks;
+
+namespace Platform.Converters.Plugins.Examples
+{
+ ///
+ /// Converts text files between different case formats (uppercase, lowercase).
+ /// Конвертирует текстовые файлы между различными форматами регистра (верхний, нижний).
+ ///
+ public class TextCaseConverter : IFileConverter
+ {
+ private readonly bool _toUpperCase;
+
+ ///
+ /// Initializes a new instance of the TextCaseConverter class.
+ /// Инициализирует новый экземпляр класса TextCaseConverter.
+ ///
+ /// If true, converts to uppercase; otherwise, converts to lowercase.Если true, конвертирует в верхний регистр; иначе в нижний регистр.
+ public TextCaseConverter(bool toUpperCase = false)
+ {
+ _toUpperCase = toUpperCase;
+ }
+
+ ///
+ public string Name => _toUpperCase ? "Text to Uppercase Converter" : "Text to Lowercase Converter";
+
+ ///
+ public string Description => _toUpperCase
+ ? "Converts text files to uppercase"
+ : "Converts text files to lowercase";
+
+ ///
+ public string[] SupportedSourceExtensions => new[] { ".txt", ".text", ".log" };
+
+ ///
+ public string[] SupportedTargetExtensions => new[] { ".txt", ".text", ".log" };
+
+ ///
+ public bool CanConvert(string sourceExtension, string targetExtension)
+ {
+ var supportedExtensions = new[] { ".txt", ".text", ".log" };
+
+ return Array.Exists(supportedExtensions, ext =>
+ ext.Equals(sourceExtension, StringComparison.OrdinalIgnoreCase)) &&
+ Array.Exists(supportedExtensions, ext =>
+ ext.Equals(targetExtension, StringComparison.OrdinalIgnoreCase));
+ }
+
+ ///
+ public async Task ConvertAsync(string sourcePath, string targetPath)
+ {
+ using var sourceStream = File.OpenRead(sourcePath);
+ using var targetStream = File.Create(targetPath);
+
+ await ConvertAsync(sourceStream, targetStream,
+ Path.GetExtension(sourcePath),
+ Path.GetExtension(targetPath));
+ }
+
+ ///
+ public async Task ConvertAsync(Stream sourceStream, Stream targetStream, string sourceExtension, string targetExtension)
+ {
+ if (!CanConvert(sourceExtension, targetExtension))
+ {
+ throw new NotSupportedException($"Conversion from {sourceExtension} to {targetExtension} is not supported.");
+ }
+
+ using var reader = new StreamReader(sourceStream, leaveOpen: true);
+ await using var writer = new StreamWriter(targetStream, leaveOpen: true);
+
+ string? line;
+ while ((line = await reader.ReadLineAsync()) != null)
+ {
+ var convertedLine = _toUpperCase ? line.ToUpperInvariant() : line.ToLowerInvariant();
+ await writer.WriteLineAsync(convertedLine);
+ }
+
+ await writer.FlushAsync();
+ }
+ }
+
+ ///
+ /// Converts text files to uppercase.
+ /// Конвертирует текстовые файлы в верхний регистр.
+ ///
+ public class TextToUppercaseConverter : TextCaseConverter
+ {
+ ///
+ /// Initializes a new instance of the TextToUppercaseConverter class.
+ /// Инициализирует новый экземпляр класса TextToUppercaseConverter.
+ ///
+ public TextToUppercaseConverter() : base(true) { }
+ }
+
+ ///
+ /// Converts text files to lowercase.
+ /// Конвертирует текстовые файлы в нижний регистр.
+ ///
+ public class TextToLowercaseConverter : TextCaseConverter
+ {
+ ///
+ /// Initializes a new instance of the TextToLowercaseConverter class.
+ /// Инициализирует новый экземпляр класса TextToLowercaseConverter.
+ ///
+ public TextToLowercaseConverter() : base(false) { }
+ }
+}
\ No newline at end of file
diff --git a/csharp/Platform.Converters.Plugins.Examples/XmlToJsonConverter.cs b/csharp/Platform.Converters.Plugins.Examples/XmlToJsonConverter.cs
new file mode 100644
index 0000000..234ff3e
--- /dev/null
+++ b/csharp/Platform.Converters.Plugins.Examples/XmlToJsonConverter.cs
@@ -0,0 +1,156 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text.Json;
+using System.Threading.Tasks;
+using System.Xml.Linq;
+
+namespace Platform.Converters.Plugins.Examples
+{
+ ///
+ /// Converts XML files to JSON format.
+ /// Конвертирует XML файлы в JSON формат.
+ ///
+ public class XmlToJsonConverter : IFileConverter
+ {
+ ///
+ public string Name => "XML to JSON Converter";
+
+ ///
+ public string Description => "Converts XML files to JSON format";
+
+ ///
+ public string[] SupportedSourceExtensions => new[] { ".xml" };
+
+ ///
+ public string[] SupportedTargetExtensions => new[] { ".json" };
+
+ ///
+ public bool CanConvert(string sourceExtension, string targetExtension)
+ {
+ return sourceExtension.Equals(".xml", StringComparison.OrdinalIgnoreCase) &&
+ targetExtension.Equals(".json", StringComparison.OrdinalIgnoreCase);
+ }
+
+ ///
+ public async Task ConvertAsync(string sourcePath, string targetPath)
+ {
+ using var sourceStream = File.OpenRead(sourcePath);
+ using var targetStream = File.Create(targetPath);
+
+ await ConvertAsync(sourceStream, targetStream, ".xml", ".json");
+ }
+
+ ///
+ public async Task ConvertAsync(Stream sourceStream, Stream targetStream, string sourceExtension, string targetExtension)
+ {
+ if (!CanConvert(sourceExtension, targetExtension))
+ {
+ throw new NotSupportedException($"Conversion from {sourceExtension} to {targetExtension} is not supported.");
+ }
+
+ var xmlDocument = await XDocument.LoadAsync(sourceStream, LoadOptions.None, default);
+ var jsonObject = ConvertXmlToJson(xmlDocument.Root!);
+
+ await using var writer = new Utf8JsonWriter(targetStream, new JsonWriterOptions
+ {
+ Indented = true
+ });
+
+ JsonSerializer.Serialize(writer, jsonObject);
+ }
+
+ private static object ConvertXmlToJson(XElement element)
+ {
+ var result = new Dictionary();
+
+ // Add attributes as properties
+ foreach (var attribute in element.Attributes())
+ {
+ result[$"@{attribute.Name}"] = attribute.Value;
+ }
+
+ // Group child elements by name
+ var childGroups = new Dictionary>();
+ foreach (var child in element.Elements())
+ {
+ var name = child.Name.LocalName;
+ if (!childGroups.ContainsKey(name))
+ {
+ childGroups[name] = new List();
+ }
+ childGroups[name].Add(child);
+ }
+
+ // Convert child elements
+ foreach (var group in childGroups)
+ {
+ if (group.Value.Count == 1)
+ {
+ // Single element
+ var child = group.Value[0];
+ if (child.HasElements || child.Attributes().Any())
+ {
+ result[group.Key] = ConvertXmlToJson(child);
+ }
+ else
+ {
+ result[group.Key] = ConvertValue(child.Value);
+ }
+ }
+ else
+ {
+ // Multiple elements with same name - create array
+ var array = new List